<commit_before>/*
eslint-disable
no-empty-function,
no-array-constructor,
no-nested-ternary,
no-new-wrappers,
no-new-object,
no-undef
*/
import Exotic from './exotic.fixture';

export const datatype = (slug, type, name, Data, param) => ({
	text: `${name}: ${slug}`,
	slug: `${name}: ${slug}`,
	type: arguments.length > 4 ? Data : type,
	name: String(name || type.name).toLowerCase(),
	seal: `[object ${name}]`,
	data: (arguments.length > 4 && typeof Data === 'function' ?
		(param ? new Data(param) : new Data()) : Data
	),
});

export const common = [
	// undef
	datatype('#001', undefined, 'undefined'),

	// nil
	datatype('#002', null, 'null', null),

	// bool
	datatype('#003', Boolean, 'Boolean', new Boolean(true)),
	datatype('#004', Boolean, 'Boolean', new Boolean(false)),
	datatype('#005', Boolean, 'Boolean', true),
	datatype('#006', Boolean, 'Boolean', false),

	// string
	datatype('#007', String, 'String', new String('foo')),
	datatype('#008', String, 'String', Object('')),
	datatype('#009', String, 'String', 'bar'),
	datatype('#010', String, 'String', ''),

	// object
	datatype('#011', Object, 'Object', new Object()),
	datatype('#012', Object, 'Object', new Object({ foo: 'bar' })),
	datatype('#013', Object, 'Object', { foo: 'bar' }),
	datatype('#014', Object, 'Object', Object.create(null)),
	datatype('#015', Object, 'Object', {}),
	datatype('#016', Object, 'Object', { 0: NaN, length: 0 }),
	datatype('#017', Object, 'Object', { 0: 'foo', length: 1 }),
	datatype('#018', Object, 'Object', { length: 0 }),

	// array
	datatype('#019', Array, 'Array', new Array(4)),
	datatype('#020', Array, 'Array', [undefined, undefined, undefined]),
	datatype('#021', Array, 'Array', ['foo', 'bar', 'baz', 1, 2, 3]),
	datatype('#022', Array, 'Array', [0, 1, undefined]),
	datatype('#023', Array, 'Array', new Array()),
	datatype('#024', Array, 'Array', []),

	// fn
	// datatype('#025', Function, 'Function', function* foo() {}),
	datatype('#026', Function, 'Function', function foo() {}),
	datatype('#027', Function, 'Function', () => {}),

	// number
	datatype('#028', Number, 'Number', new Number(92)),
	datatype('#029', Number, 'Number', 0x12),
	datatype('#030', Number, 'Number', 12e1),
	datatype('#031', Number, 'Number', 13),
	datatype('#032', Number, 'Number', 10),
	datatype('#033', Number, 'Number', 5),
	datatype('#034', Number, 'Number', 2),
	datatype('#035', Number, 'Number', 3),
	datatype('#036', Number, 'Number', -10),
	datatype('#037', Number, 'Number', -5),
	datatype('#038', Number, 'Number', -2),
	datatype('#039', Number, 'Number', -3),
	datatype('#040', Number, 'Number', 0),
	datatype('#041', Number, 'Number', 1.1),
	datatype('#042', Number, 'Number', 1.2),
	datatype('#043', Number, 'Number', 1.3),
	datatype('#044', Number, 'Number', 1 / 0),
	datatype('#045', Number, 'Number', -Infinity),
	datatype('#046', Number, 'Number', Infinity),
	datatype('#047', Number, 'Number', parseFloat('FF2')),
	datatype('#048', Number, 'Number', -NaN),
	datatype('#049', Number, 'Number', NaN),

	// regexp
	datatype('#050', RegExp, 'RegExp', new RegExp('foo', 'gim')),
	datatype('#051', RegExp, 'RegExp', /bar/gim),

	// date
	datatype('#052', Date, 'Date', new Date()),

	// error
	datatype('#053', Error, 'Error', new Error('foo')),
	datatype('#054', EvalError, 'EvalError', new EvalError('foo')),
	datatype('#055', RangeError, 'RangeError', new RangeError('foo')),
	datatype('#056', ReferenceError, 'ReferenceError', new ReferenceError('foo')),
	datatype('#057', SyntaxError, 'SyntaxError', new SyntaxError('foo')),
	datatype('#058', TypeError, 'TypeError', new TypeError('foo')),
	datatype('#059', URIError, 'URIError', new URIError('foo')),
];

export const browser = !global.document ? [] : [
	// element
	datatype('#061', global.HTMLCollection, 'HTMLCollection', global.document.body.children),
	datatype('#062', global.HTMLDivElement, 'HTMLDivElement', global.document.createElement('div')),
];

export const custom = Exotic.supportsCustomization ? [] : [
	datatype('#064', Exotic, 'Custom', new Exotic('Custom')),
];

export const es5 = [
	datatype('#063', Exotic, 'Exotic', new Exotic()),
	datatype('#065', global.Map, 'Map', global.Map, undefined),
	datatype('#066', global.WeakMap, 'WeakMap', global.WeakMap, undefined),
	datatype('#067', global.Set, 'Set', global.Set, undefined),
	datatype('#068', global.WeakSet, 'WeakSet', global.WeakSet, undefined),
	datatype('#069', global.Int8Array, 'Int8Array', global.Int8Array, undefined),
	datatype('#070', global.Uint8Array, 'Uint8Array', global.Uint8Array, [21, 31]),
	datatype('#071', global.Uint8ClampedArray, 'Uint8ClampedArray', global.Uint8ClampedArray, 16),
	datatype('#072', global.Int16Array, 'Int16Array', global.Int16Array, 8),
	datatype('#073', global.Uint16Array, 'Uint16Array', global.Uint16Array, 32),
	datatype('#074', global.Int32Array, 'Int32Array', global.Int32Array, 4),
	datatype('#075', global.Uint32Array, 'Uint32Array', global.Uint32Array, 8),
	datatype('#076', global.Float32Array, 'Float32Array', global.Float32Array, 64),
	datatype('#077', global.Float64Array, 'Float64Array', global.Float64Array, 32),
	datatype('#078', global.ArrayBuffer, 'ArrayBuffer', global.ArrayBuffer, 4),
];

export const es6 = !global.Symbol ? [] : [
	// symbol
	datatype('#060', Symbol, 'Symbol', Symbol('foo')),
];

export default ([]).concat(common, browser, custom, es5, es6);
<commit_msg>fix: datatype.fixture -> datatype detection<commit_after>/*
eslint-disable
no-empty-function,
no-array-constructor,
no-nested-ternary,
no-new-wrappers,
no-new-object,
no-undef
*/
import Exotic from './exotic.fixture';

export const datatype = (slug, type, name, Data, ...rest) => ({
	text: `${name}: ${slug}`,
	slug: `${name}: ${slug}`,
	type: rest.length ? (typeof Data === 'function' ? Data : type) : type,
	name: String(name || type.name).toLowerCase(),
	seal: `[object ${name}]`,
	data: (rest.length && typeof Data !== 'undefined' ?
		(rest[0] !== undefined ? new Data(...rest) : new Data()) : Data
	),
});

export const common = [
	// undef
	datatype('#001', undefined, 'undefined'),

	// nil
	datatype('#002', null, 'null', null),

	// bool
	datatype('#003', Boolean, 'Boolean', new Boolean(true)),
	datatype('#004', Boolean, 'Boolean', new Boolean(false)),
	datatype('#005', Boolean, 'Boolean', true),
	datatype('#006', Boolean, 'Boolean', false),

	// string
	datatype('#007', String, 'String', new String('foo')),
	datatype('#008', String, 'String', Object('')),
	datatype('#009', String, 'String', 'bar'),
	datatype('#010', String, 'String', ''),

	// object
	datatype('#011', Object, 'Object', new Object()),
	datatype('#012', Object, 'Object', new Object({ foo: 'bar' })),
	datatype('#013', Object, 'Object', { foo: 'bar' }),
	datatype('#014', Object, 'Object', Object.create(null)),
	datatype('#015', Object, 'Object', {}),
	datatype('#016', Object, 'Object', { 0: NaN, length: 0 }),
	datatype('#017', Object, 'Object', { 0: 'foo', length: 1 }),
	datatype('#018', Object, 'Object', { length: 0 }),

	// array
	datatype('#019', Array, 'Array', new Array(4)),
	datatype('#020', Array, 'Array', [undefined, undefined, undefined]),
	datatype('#021', Array, 'Array', ['foo', 'bar', 'baz', 1, 2, 3]),
	datatype('#022', Array, 'Array', [0, 1, undefined]),
	datatype('#023', Array, 'Array', new Array()),
	datatype('#024', Array, 'Array', []),

	// fn
	// datatype('#025', Function, 'Function', function* foo() {}),
	datatype('#026', Function, 'Function', function foo() {}),
	datatype('#027', Function, 'Function', () => {}),

	// number
	datatype('#028', Number, 'Number', new Number(92)),
	datatype('#029', Number, 'Number', 0x12),
	datatype('#030', Number, 'Number', 12e1),
	datatype('#031', Number, 'Number', 13),
	datatype('#032', Number, 'Number', 10),
	datatype('#033', Number, 'Number', 5),
	datatype('#034', Number, 'Number', 2),
	datatype('#035', Number, 'Number', 3),
	datatype('#036', Number, 'Number', -10),
	datatype('#037', Number, 'Number', -5),
	datatype('#038', Number, 'Number', -2),
	datatype('#039', Number, 'Number', -3),
	datatype('#040', Number, 'Number', 0),
	datatype('#041', Number, 'Number', 1.1),
	datatype('#042', Number, 'Number', 1.2),
	datatype('#043', Number, 'Number', 1.3),
	datatype('#044', Number, 'Number', 1 / 0),
	datatype('#045', Number, 'Number', -Infinity),
	datatype('#046', Number, 'Number', Infinity),
	datatype('#047', Number, 'Number', parseFloat('FF2')),
	datatype('#048', Number, 'Number', -NaN),
	datatype('#049', Number, 'Number', NaN),

	// regexp
	datatype('#050', RegExp, 'RegExp', new RegExp('foo', 'gim')),
	datatype('#051', RegExp, 'RegExp', /bar/gim),

	// date
	datatype('#052', Date, 'Date', new Date()),

	// error
	datatype('#053', Error, 'Error', new Error('foo')),
	datatype('#054', EvalError, 'EvalError', new EvalError('foo')),
	datatype('#055', RangeError, 'RangeError', new RangeError('foo')),
	datatype('#056', ReferenceError, 'ReferenceError', new ReferenceError('foo')),
	datatype('#057', SyntaxError, 'SyntaxError', new SyntaxError('foo')),
	datatype('#058', TypeError, 'TypeError', new TypeError('foo')),
	datatype('#059', URIError, 'URIError', new URIError('foo')),
];

export const browser = typeof global.document === 'undefined' ? [] : [
	// element
	datatype('#061', global.HTMLCollection, 'HTMLCollection', global.document.body.children),
	datatype('#062', global.HTMLDivElement, 'HTMLDivElement', global.document.createElement('div')),
];

export const custom = Exotic.supportsCustomization ? [] : [
	// customized Function.prototype.name
	datatype('#063', Exotic, 'Custom', new Exotic('Custom')),
];

export const es5 = [
	datatype('#064', Exotic, 'Exotic', new Exotic()),
	datatype('#065', global.Map, 'Map', global.Map, undefined),
	datatype('#066', global.WeakMap, 'WeakMap', global.WeakMap, undefined),
	datatype('#067', global.Set, 'Set', global.Set, undefined),
	datatype('#068', global.WeakSet, 'WeakSet', global.WeakSet, undefined),
	datatype('#069', global.Int8Array, 'Int8Array', global.Int8Array, undefined),
	datatype('#070', global.Uint8Array, 'Uint8Array', global.Uint8Array, [21, 31]),
	datatype('#071', global.Uint8ClampedArray, 'Uint8ClampedArray', global.Uint8ClampedArray, 16),
	datatype('#072', global.Int16Array, 'Int16Array', global.Int16Array, 8),
	datatype('#073', global.Uint16Array, 'Uint16Array', global.Uint16Array, 32),
	datatype('#074', global.Int32Array, 'Int32Array', global.Int32Array, 4),
	datatype('#075', global.Uint32Array, 'Uint32Array', global.Uint32Array, 8),
	datatype('#076', global.Float32Array, 'Float32Array', global.Float32Array, 64),
	datatype('#077', global.Float64Array, 'Float64Array', global.Float64Array, 32),
	datatype('#078', global.ArrayBuffer, 'ArrayBuffer', global.ArrayBuffer, 4),
];

export const es6 = typeof global.Symbol === 'undefined' ? [] : [
	// symbol
	datatype('#060', global.Symbol, 'Symbol', global.Symbol('foo')),
];

export default ([]).concat(common, browser, custom, es5, es6);
<|endoftext|>


<commit_before>var $require = require('proxyquire');
var expect = require('chai').expect;
var sinon = require('sinon');
var factory = require('../../com/http/transactionstore');
var TransactionStore = require('../../lib/transactionstore');

describe('transactionstore', function() {

  it('should be annotated', function() {
    expect(factory['@singleton']).to.equal(true);
  });

  describe('should construct TransactionStore', function() {
    var TransactionStoreSpy = sinon.spy(TransactionStore);
    var factory = $require('../../com/http/transactionstore', {
      '../../lib/transactionstore': TransactionStoreSpy
    });

    var store = factory();

    expect(TransactionStoreSpy).to.have.been.calledOnce;
    expect(TransactionStoreSpy).to.have.been.calledWithNew;
    expect(store).to.be.an.instanceOf(TransactionStore);
  }); // creating with defaults

  describe('TransactionStore', function() {
    var store = new TransactionStore();

    describe('#load', function() {

      describe('loading transaction', function(done) {
        var req = new Object();
        req.state = {
          responseType: 'code',
          client: {
            id: 's6BhdRkqt3',
            name: 'My Example Client'
          },
          redirectURI: 'https://client.example.com/cb',
          scope: undefined,
          state: 'xyz'
        };
        req.state.handle = 'XXXXXXXX';

        var txn;

        before(function(done) {
          store.load(req, function(err, t) {
            if (err) { return done(err); }
            txn = t;
            done();
          });
        });

        it('should load transaction', function() {
          expect(txn).to.deep.equal({
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            req: {
              type: 'code',
              scope: undefined,
              state: 'xyz'
            },
            locals: {
              issuer: undefined
            }
          });
        });
      }); // loading transaction

    }); // #load

    describe('#store', function() {

      describe('storing transaction', function(done) {
        var req = new Object();
        req.headers = {};
        req.state = new Object();
        req.pushState = sinon.spy();

        before(function(done) {
          var txn = {
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client',
              redirectURIs: [ 'https://client.example.com/cb' ]
            },
            redirectURI: 'https://client.example.com/cb',
            req: {
              type: 'code',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              scope: undefined,
              state: 'xyz'
            },
          };

          store.store(req, txn, function(err) {
            if (err) { return done(err); }
            done();
          });
        });

        it('should set state', function() {
          expect(req.pushState).to.be.calledWith({
            issuer: 'undefined://undefined',
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          });
        });
      }); // storing transaction

      describe('storing transaction with web origin', function(done) {
        var req = new Object();
        req.headers = {};
        req.state = new Object();

        req.pushState = sinon.spy();

        before(function(done) {
          var txn = {
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client',
              redirectURIs: [ 'https://client.example.com/cb' ]
            },
            redirectURI: 'https://client.example.com/cb',
            webOrigin: 'https://client.example.com',
            req: {
              type: 'code',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              scope: undefined,
              state: 'xyz'
            },
          };

          store.store(req, txn, function(err) {
            if (err) { return done(err); }
            done();
          });
        });

        it('should set state', function() {
          expect(req.pushState).to.be.calledWith({
            issuer: 'undefined://undefined',
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            webOrigin: 'https://client.example.com',
            scope: undefined,
            state: 'xyz'
          });
        });
      }); // storing transaction with web origin

    }); // #store

    describe('#update', function() {

      describe('updating transaction', function(done) {
        var req = new Object();
        req.state = new Object();

        before(function(done) {
          var txn = {
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client',
              redirectURIs: [ 'https://client.example.com/cb' ]
            },
            redirectURI: 'https://client.example.com/cb',
            req: {
              type: 'code',
              clientID: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              scope: undefined,
              state: 'xyz'
            },
          };

          store.update(req, 'XXXXXXXX', txn, function(err) {
            if (err) { return done(err); }
            done();
          });
        });

        it('should set state', function() {
          expect(req.state).to.deep.equal({
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          });
        });
      }); // storing transaction

    }); // #update

  }); // TransactionStore

});
<commit_msg>Clean up tests.<commit_after>var $require = require('proxyquire');
var expect = require('chai').expect;
var sinon = require('sinon');
var factory = require('../../com/http/transactionstore');
var TransactionStore = require('../../lib/transactionstore');

describe('transactionstore', function() {

  it('should be annotated', function() {
    expect(factory['@singleton']).to.equal(true);
  });

  describe('should construct TransactionStore', function() {
    var TransactionStoreSpy = sinon.spy(TransactionStore);
    var factory = $require('../../com/http/transactionstore', {
      '../../lib/transactionstore': TransactionStoreSpy
    });

    var store = factory();

    expect(TransactionStoreSpy).to.have.been.calledOnce;
    expect(TransactionStoreSpy).to.have.been.calledWithNew;
    expect(store).to.be.an.instanceOf(TransactionStore);
  }); // creating with defaults

  describe('TransactionStore', function() {
    var store = new TransactionStore();

    describe('#load', function() {

      it('loading transaction', function(done) {
        var req = new Object();
        req.state = {
          responseType: 'code',
          client: {
            id: 's6BhdRkqt3',
            name: 'My Example Client'
          },
          redirectURI: 'https://client.example.com/cb',
          scope: undefined,
          state: 'xyz'
        };
        req.state.handle = 'XXXXXXXX';

        store.load(req, function(err, txn) {
          if (err) { return done(err); }

          expect(txn).to.deep.equal({
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            req: {
              type: 'code',
              scope: undefined,
              state: 'xyz'
            },
            locals: {
              issuer: undefined
            }
          });

          done();
        });
      }); // loading transaction

    }); // #load

    describe('#store', function() {

      it('storing transaction', function(done) {
        var req = new Object();
        req.headers = {};
        req.state = new Object();
        req.pushState = sinon.spy();

        var txn = {
          client: {
            id: 's6BhdRkqt3',
            name: 'My Example Client',
            redirectURIs: [ 'https://client.example.com/cb' ]
          },
          redirectURI: 'https://client.example.com/cb',
          req: {
            type: 'code',
            clientID: 's6BhdRkqt3',
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          },
        };

        store.store(req, txn, function(err) {
          if (err) { return done(err); }

          expect(req.pushState).to.be.calledWith({
            issuer: 'undefined://undefined',
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          });

          done();
        });
      }); // storing transaction

      it('storing transaction with web origin', function(done) {
        var req = new Object();
        req.headers = {};
        req.state = new Object();

        req.pushState = sinon.spy();

        var txn = {
          client: {
            id: 's6BhdRkqt3',
            name: 'My Example Client',
            redirectURIs: [ 'https://client.example.com/cb' ]
          },
          redirectURI: 'https://client.example.com/cb',
          webOrigin: 'https://client.example.com',
          req: {
            type: 'code',
            clientID: 's6BhdRkqt3',
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          },
        };

        store.store(req, txn, function(err) {
          if (err) { return done(err); }

          expect(req.pushState).to.be.calledWith({
            issuer: 'undefined://undefined',
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            webOrigin: 'https://client.example.com',
            scope: undefined,
            state: 'xyz'
          });

          done();
        });
      }); // storing transaction with web origin

    }); // #store

    describe('#update', function() {

      it('updating transaction', function(done) {
        var req = new Object();
        req.state = new Object();

        var txn = {
          client: {
            id: 's6BhdRkqt3',
            name: 'My Example Client',
            redirectURIs: [ 'https://client.example.com/cb' ]
          },
          redirectURI: 'https://client.example.com/cb',
          req: {
            type: 'code',
            clientID: 's6BhdRkqt3',
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          },
        };

        store.update(req, 'XXXXXXXX', txn, function(err) {
          if (err) { return done(err); }

          expect(req.state).to.deep.equal({
            responseType: 'code',
            client: {
              id: 's6BhdRkqt3',
              name: 'My Example Client'
            },
            redirectURI: 'https://client.example.com/cb',
            scope: undefined,
            state: 'xyz'
          });

          done();
        });

      }); // storing transaction

    }); // #update

  }); // TransactionStore

});
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var assert = require('assert');
var httpHelper = require('./helpers/httpHelper.js');
var appHelper = require('./helpers/appHelper');
var path = require('path');
var fs = require('fs');
var _ = require('@sailshq/lodash');
var tmp = require('tmp');

var Sails = require('../../lib').constructor;

describe('CSRF ::', function() {

  describe('Basic CSRF config ::', function() {

    var sailsConfig = {};

    var sailsApp;
    beforeEach(function(done) {
      var _config = _.merge({
        hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},
        log: {level: 'error'},
        routes: {
          '/csrfToken': {action: 'security/grant-csrf-token'},
          'ALL /viewtest/csrf': function(req, res) {
            var template = _.template('csrf=\'<%-_csrf%>\'');
            res.send(template(res.locals));
          },
          'GET /user': function(req, res) {
            return res.send(200);
          },
          'POST /user': function(req, res) {
            return res.send(201);
          },
          'POST /user/:id': function(req, res) {
            return res.send(200);
          }
        }
      }, sailsConfig);
      (new Sails()).load(_config, function(err, _sails) {
          sailsApp = _sails;
          return done(err);
        }
      );
    });

    afterEach(function(done) {
      sailsApp.lower(done);
    });

    describe('with CSRF set to `false`', function() {

      before(function() {
        sailsConfig = {};
      });

      it('a blank CSRF token should be present in view locals', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body.indexOf('csrf=\'\'') !== -1, response.body);
          done();
        });
      });

    });

    describe('with CSRF set to `true`', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          }
        };

      });

      it('a HEAD request to a route with the CSRF used as a view local should not result in an error', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'head'}, function(err, response) {
          if (err) {
            return done(err);
          }
          done();
        });
      });

      it('an OPTIONS request to a route with the CSRF used as a view local should not result in an error', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'options'}, function(err, response) {
          if (err) {
            return done(err);
          }
          done();
        });
      });

      it('a CSRF token should be present in view locals', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body.match(/csrf='.{36}'/), response.body);
          done();
        });
      });

      it('a request to /csrfToken should respond with a _csrf token', function(done) {
        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body._csrf, response.body);
          return done();
        });
      });

      it('a POST request without a CSRF token should result in a 403 response', function(done) {

        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {

          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));

        });

      });

      it('a POST request with a valid CSRF token should result in a 201 response', function(done) {

        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          try {
            var body = response.body;
            var sid = response.headers['set-cookie'][0].split(';')[0].substr(10);
            sailsApp.request({
              method: 'post',
              url: '/user',
              headers: {
                'Content-type': 'application/json',
                'cookie': 'sails.sid=' + sid
              },
              data: {_csrf: body._csrf}
            }, function(err, response) {

              if (err) {
                return done(err);
              }

              assert.equal(response.statusCode, 201);
              done();

            });
          } catch (e) {
            done(e);
          }
        });

      });
    });

    describe('with CSRF set to true, blacklisting \'POST /foo/:id, /user\'}', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          },
          routes: {
            'POST /foo/:id': {csrf: false},
            '/user': {csrf: false}
          }
        };

      });

      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {
        sailsApp.request({
          method: 'post',
          url: '/user'
        }, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 201);
          done();
        });
      });

      it('a POST request on /foo/12 without a CSRF token should result in a 404 response', function(done) {
        sailsApp.request({url: '/foo/12', method: 'post'}, function(err, response) {
          if (err && err.status === 404) {
            return done();
          }
          done(new Error('Expected a 404 error, instead got: ' + err || response.body));
        });
      });

      it('a PUT request on /foo/12 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/foo/12', method: 'put'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /test without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/test', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /foo without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/foo', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

    });

    describe('with CSRF set to true, blacklisting \'POST /user\\/\\d+/\'', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          },
          routes: {
            'POST r|user/\\d+|': {csrf: false}
          }
        };

      });

      it('a POST request on /user/1 without a CSRF token should result in a 200 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });
      });

      it('a PUT request on /user/1 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'put'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user/a without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

    });

    describe('with CSRF set to false, whitelisting \'/user\\/\\d+/\'', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: false
          },
          routes: {
            'r|user/\\d+|': {csrf: true}
          }
        };

      });

      it('a POST request on /user/1 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user/a without a CSRF token should result in a 200 response', function(done) {
        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });
      });

      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 201);
          done();
        });
      });

    });

    describe('with CSRF set to true and sessions disabled', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          },
          hooks: {session: false}
        };

      });

      it('a GET request on /user should result in a 200 response', function(done) {
        sailsApp.request({url: '/user', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });

      });

      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          assert(err);
          assert.equal(err.status, 403);
          done();
        });

      });

    });

  }); //</describe('CSRF config ::')>

});
<commit_msg>Fix "CSRF-enabled-routes with sessions disabled" test<commit_after>/**
 * Module dependencies
 */

var assert = require('assert');
var httpHelper = require('./helpers/httpHelper.js');
var appHelper = require('./helpers/appHelper');
var path = require('path');
var fs = require('fs');
var _ = require('@sailshq/lodash');
var tmp = require('tmp');

var Sails = require('../../lib').constructor;

describe('CSRF ::', function() {

  describe('Basic CSRF config ::', function() {

    var sailsConfig = {};

    var sailsApp;
    beforeEach(function(done) {
      var _config = _.merge({
        hooks: {grunt: false, views: false, blueprints: false, policies: false, i18n: false},
        log: {level: 'error'},
        routes: {
          '/csrfToken': {action: 'security/grant-csrf-token'},
          'ALL /viewtest/csrf': function(req, res) {
            var template = _.template('csrf=\'<%-_csrf%>\'');
            res.send(template(res.locals));
          },
          'GET /user': function(req, res) {
            return res.send(200);
          },
          'POST /user': function(req, res) {
            return res.send(201);
          },
          'POST /user/:id': function(req, res) {
            return res.send(200);
          }
        }
      }, sailsConfig);
      (new Sails()).load(_config, function(err, _sails) {
          sailsApp = _sails;
          return done(err);
        }
      );
    });

    afterEach(function(done) {
      sailsApp.lower(done);
    });

    describe('with CSRF set to `false`', function() {

      before(function() {
        sailsConfig = {};
      });

      it('a blank CSRF token should be present in view locals', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body.indexOf('csrf=\'\'') !== -1, response.body);
          done();
        });
      });

    });

    describe('with CSRF set to `true`', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          }
        };

      });

      it('a HEAD request to a route with the CSRF used as a view local should not result in an error', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'head'}, function(err, response) {
          if (err) {
            return done(err);
          }
          done();
        });
      });

      it('an OPTIONS request to a route with the CSRF used as a view local should not result in an error', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'options'}, function(err, response) {
          if (err) {
            return done(err);
          }
          done();
        });
      });

      it('a CSRF token should be present in view locals', function(done) {
        sailsApp.request({url: '/viewtest/csrf', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body.match(/csrf='.{36}'/), response.body);
          done();
        });
      });

      it('a request to /csrfToken should respond with a _csrf token', function(done) {
        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert(response.body._csrf, response.body);
          return done();
        });
      });

      it('a POST request without a CSRF token should result in a 403 response', function(done) {

        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {

          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));

        });

      });

      it('a POST request with a valid CSRF token should result in a 201 response', function(done) {

        sailsApp.request({url: '/csrftoken', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          try {
            var body = response.body;
            var sid = response.headers['set-cookie'][0].split(';')[0].substr(10);
            sailsApp.request({
              method: 'post',
              url: '/user',
              headers: {
                'Content-type': 'application/json',
                'cookie': 'sails.sid=' + sid
              },
              data: {_csrf: body._csrf}
            }, function(err, response) {

              if (err) {
                return done(err);
              }

              assert.equal(response.statusCode, 201);
              done();

            });
          } catch (e) {
            done(e);
          }
        });

      });
    });

    describe('with CSRF set to true, blacklisting \'POST /foo/:id, /user\'}', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          },
          routes: {
            'POST /foo/:id': {csrf: false},
            '/user': {csrf: false}
          }
        };

      });

      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {
        sailsApp.request({
          method: 'post',
          url: '/user'
        }, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 201);
          done();
        });
      });

      it('a POST request on /foo/12 without a CSRF token should result in a 404 response', function(done) {
        sailsApp.request({url: '/foo/12', method: 'post'}, function(err, response) {
          if (err && err.status === 404) {
            return done();
          }
          done(new Error('Expected a 404 error, instead got: ' + err || response.body));
        });
      });

      it('a PUT request on /foo/12 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/foo/12', method: 'put'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /test without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/test', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /foo without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/foo', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

    });

    describe('with CSRF set to true, blacklisting \'POST /user\\/\\d+/\'', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: true
          },
          routes: {
            'POST r|user/\\d+|': {csrf: false}
          }
        };

      });

      it('a POST request on /user/1 without a CSRF token should result in a 200 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });
      });

      it('a PUT request on /user/1 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'put'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user/a without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

    });

    describe('with CSRF set to false, whitelisting \'/user\\/\\d+/\'', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: false
          },
          routes: {
            'r|user/\\d+|': {csrf: true}
          }
        };

      });

      it('a POST request on /user/1 without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user/1', method: 'post'}, function(err, response) {
          if (err && err.status === 403) {
            return done();
          }
          done(new Error('Expected a 403 error, instead got: ' + err || response.body));
        });
      });

      it('a POST request on /user/a without a CSRF token should result in a 200 response', function(done) {
        sailsApp.request({url: '/user/a', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });
      });

      it('a POST request on /user without a CSRF token should result in a 201 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 201);
          done();
        });
      });

    });

    describe('with CSRF set to true and sessions disabled', function() {

      before(function() {

        sailsConfig = {
          security: {
            csrf: false
          },
          hooks: {session: false},
          routes: {
            'GET /user': {csrf: true, target: function(req, res) {
              return res.send(200);
            }},
            'POST /user': {csrf: true, target: function(req, res) {
              return res.send(201);
            }}
          }
        };

      });

      it('a GET request on /user should result in a 200 response', function(done) {
        sailsApp.request({url: '/user', method: 'get'}, function(err, response) {
          if (err) {
            return done(err);
          }
          assert.equal(response.statusCode, 200);
          done();
        });

      });

      it('a POST request on /user without a CSRF token should result in a 403 response', function(done) {
        sailsApp.request({url: '/user', method: 'post'}, function(err, response) {
          assert(err);
          assert.equal(err.status, 403);
          done();
        });

      });

    });

  }); //</describe('CSRF config ::')>

});
<|endoftext|>


<commit_before>// Copyright 2014 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview CSS base light weight popup menu.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.ui.FlyoutMenu');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.style');

/**
 * Popup menu
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>=} opt_menu_items
 * @constructor
 * @struct
 */
ydn.ui.FlyoutMenu = function(opt_option, opt_menu_items) {
  /**
   * @type {Element}
   * @private
   */
  this.el_ = null;
  /**
   * @type {ydn.ui.FlyoutMenu.Option}
   * @private
   */
  this.option_ = opt_option || /** @type {ydn.ui.FlyoutMenu.Option} */ ({});
  /**
   * @type {Array.<?ydn.ui.FlyoutMenu.ItemOption>}
   * @private
   */
  this.items_option_ = ydn.object.clone(opt_menu_items || []);
};

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS = 'more-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_MENU = 'flyout-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_LEFT = 'left-menu';

/**
 * @param {Element} el
 */
ydn.ui.FlyoutMenu.prototype.render = function(el) {
  goog.asserts.assert(!this.el_, 'Already rendered.');
  this.el_ = document.createElement('div');
  this.el_.className = ydn.ui.FlyoutMenu.CSS_CLASS;
  var icon_name = this.option_.className || 'more-vert';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var button = document.createElement('div');
  button.className = 'svg-button';
  button.appendChild(svg);
  var menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  this.el_.appendChild(button);
  this.el_.appendChild(menu);
  el.appendChild(this.el_);
};

/**
 * @param {Element} el
 * @param {string} names
 * @return {string}
 * @private
 */
ydn.ui.FlyoutMenu.listItemName_ = function(el, names) {
  if (el && el instanceof Element) {
    if (el.classList.contains('goog-menuitem')) {
      var name = el.getAttribute('name');
      if (!names) {
        names = name;
      } else {
        names = name + ',' + names;
      }
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    } else if (el.classList.contains('goog-menu')) {
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    }
  }
  return names;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent|Event} e
 * @return {?string} if menu item is click, it will be return item name. For
 * hierichal menu, item name are separated by comma from root t leave, i.e.,
 * 'sync,contact'.
 */
ydn.ui.FlyoutMenu.handleClick = function(e) {
  // todo: e.target or e.target.parentElement instead.
  var item = null;

  if (e.target instanceof Element) {
    if (e.target.classList.contains('goog-menuitem')) {
      item = e.target;
    } else if (e.target.parentElement &&
        e.target.parentElement.classList.contains('goog-menuitem')) {
      item = e.target.parentElement;
    }
  }
  if (item) {
    e.preventDefault();
    e.stopPropagation();
    var is_disable = item.classList.contains('goog-menuitem-disabled');
    var el = goog.dom.getAncestorByClass(item, ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    goog.style.setElementShown(el, false);
    setTimeout(function() {
      // menu show/hide status is determine by hover state
      goog.style.setElementShown(el, true);
    }, 1000);
    return is_disable ? null : ydn.ui.FlyoutMenu.listItemName_(item, '');
  }
  return null;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent} e
 * @return {?string} if menu item is click, it will be return item name.
 */
ydn.ui.FlyoutMenu.prototype.handleClick = function(e) {
  return ydn.ui.FlyoutMenu.handleClick(e);
};

/**
 * Set menu items.
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} item
 */
ydn.ui.FlyoutMenu.prototype.setItems = function(item) {
  this.items_option_ = ydn.object.clone(item);
  if (this.el_) {
    var menu = this.el_.querySelector('.' + ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    this.el_.removeChild(menu);
    menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
    this.el_.appendChild(menu);
  }
};

/**
 * @return {Element}
 */
ydn.ui.FlyoutMenu.prototype.getElement = function() {
  return this.el_;
};

/**
 * @typedef {{
 *   iconName: (string|undefined),
 *   className: (string|undefined),
 *   title: (string|undefined),
 *   isRightMenu: (boolean|undefined)
 * }}
 * iconName: svg icon name 'more-vert', 'more-horiz' or 'menu'
 * className: css class name.
 * isRightMenu: menu render to right side. By default, render to left.
 * title: div title for the menu item.
 */
ydn.ui.FlyoutMenu.Option;

/**
 * @typedef {{
 *   label: string,
 *   name: (string),
 *   value: *,
 *   title: (string|undefined),
 *   type: (string|undefined),
 *   disabled: (boolean|undefined),
 *   children: (Array.<ydn.ui.FlyoutMenu.ItemOption>|undefined)
 * }}
 * label: menu text.
 * name: commend in dispatching event.
 * type: SugarCrm.ModuleField#type for input type, default to type='text', for
 * 'bool', it becomes type='checkbox'
 * When type is 'bool', value can be 'true' or 'false'.
 */
ydn.ui.FlyoutMenu.ItemOption;

/**
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @return {Element}
 */
ydn.ui.FlyoutMenu.renderMenu = function(items_option) {
  // console.log(options);
  var dom = goog.dom.getDomHelper();
  var items = [];
  for (var i = 0; i < items_option.length; i++) {
    var opt = items_option[i];
    if (!opt) {
      // render as menu separator
      var rep = dom.createDom('div', {
        'class': 'goog-menuseparator',
        'role': 'separator'
      });
      items.push(rep);
      continue;
    }

    var menu_content = [dom.createDom('div', {
      'class': 'goog-menuitem-content'
    }, opt.label)];
    if (opt.type == 'bool') {
      var chk = dom.createDom('div', {
        'class': 'goog-menuitem-checkbox',
        'role': 'menuitem'
      });
      menu_content.unshift(chk);
    }
    if (opt.children) {
      var svg_arrow = ydn.crm.ui.createSvgIcon('arrow-drop-right');
      svg_arrow.classList.add('left-arrow');
      menu_content.unshift(svg_arrow);
    }
    var div_options = {
      'class': 'goog-menuitem',
      'role': opt.type == 'bool' ? 'goog-menuitem-checkbox' : 'menuitem'
    };
    if (opt.title) {
      div_options['title'] = opt.title;
    }
    var menuitem = dom.createDom('div', div_options, menu_content);
    menuitem.setAttribute('name', opt.name);
    if (opt.value) {
      menuitem.classList.add('goog-option-selected');
    }
    if (opt.children) {
      var sub_menu = ydn.ui.FlyoutMenu.renderMenu(opt.children);
      menuitem.appendChild(sub_menu);
    }
    if (opt.disabled) {
      menuitem.classList.add('goog-menuitem-disabled');
    }
    items.push(menuitem);
  }

  var menu = dom.createDom('div', {
    'class': 'goog-menu goog-menu-vertical',
    'role': 'menu'
  },
  items);
  return menu;
};

/**
 *

 *   var el = document.querySelector('#menu');
 *   var menu = ydn.ui.FlyoutMenu.decoratePopupMenu(el, items);
 *   el.onclick = function(e) {
 *     var name = ydn.ui.FlyoutMenu.handleClick(e);
 *     if (name) {
 *       // menu item with the name click.
 *     }
 *   }
 *

 * @param {Element} el
 * @param {Array<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 */
ydn.ui.FlyoutMenu.decoratePopupMenu = function(el, items_option, opt_option) {
  // console.log(options);
  var icon_name = 'more-vert';
  if (opt_option) {
    if (opt_option.iconName) {
      icon_name = opt_option.iconName;
    }
  }
  var button = document.createElement('span');
  button.className = 'flyout-button';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var menu = ydn.ui.FlyoutMenu.renderMenu(items_option);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  if (!opt_option || !opt_option.isRightMenu) {
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  }
  button.appendChild(svg);
  button.appendChild(menu);
  el.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS);
  el.appendChild(button);
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setEnableMenuItem = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  if (val) {
    el.classList.remove('goog-menuitem-disabled');
  } else {
    el.classList.add('goog-menuitem-disabled');
  }
};

/**
 * Set menu label.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setMenuItemLabel = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  var content = el.querySelector('.goog-menuitem-content');
  content.textContent = val;
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setEnableMenuItem = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setEnableMenuItem(el, name, val);
};

/**
 * Set menu label.
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setMenuItemLabel = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setMenuItemLabel(el, name, val);
};
<commit_msg>regorg<commit_after>// Copyright 2014 YDN Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview CSS base light weight popup menu.
 *
 * @author kyawtun@yathit.com (Kyaw Tun)
 */

goog.provide('ydn.ui.FlyoutMenu');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.style');
goog.require('ydn.crm.ui');

/**
 * Popup menu
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>=} opt_menu_items
 * @constructor
 * @struct
 */
ydn.ui.FlyoutMenu = function(opt_option, opt_menu_items) {
  /**
   * @type {Element}
   * @private
   */
  this.el_ = null;
  /**
   * @type {ydn.ui.FlyoutMenu.Option}
   * @private
   */
  this.option_ = opt_option || /** @type {ydn.ui.FlyoutMenu.Option} */ ({});
  /**
   * @type {Array.<?ydn.ui.FlyoutMenu.ItemOption>}
   * @private
   */
  this.items_option_ = ydn.object.clone(opt_menu_items || []);
};

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS = 'more-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_MENU = 'flyout-menu';

/**
 * @const
 * @type {string}
 */
ydn.ui.FlyoutMenu.CSS_CLASS_LEFT = 'left-menu';

/**
 * @param {Element} el
 */
ydn.ui.FlyoutMenu.prototype.render = function(el) {
  goog.asserts.assert(!this.el_, 'Already rendered.');
  this.el_ = document.createElement('div');
  this.el_.className = ydn.ui.FlyoutMenu.CSS_CLASS;
  var icon_name = this.option_.className || 'more-vert';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var button = document.createElement('div');
  button.className = 'svg-button';
  button.appendChild(svg);
  var menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  this.el_.appendChild(button);
  this.el_.appendChild(menu);
  el.appendChild(this.el_);
};

/**
 * @param {Element} el
 * @param {string} names
 * @return {string}
 * @private
 */
ydn.ui.FlyoutMenu.listItemName_ = function(el, names) {
  if (el && el instanceof Element) {
    if (el.classList.contains('goog-menuitem')) {
      var name = el.getAttribute('name');
      if (!names) {
        names = name;
      } else {
        names = name + ',' + names;
      }
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    } else if (el.classList.contains('goog-menu')) {
      return ydn.ui.FlyoutMenu.listItemName_(el.parentElement, names);
    }
  }
  return names;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent|Event} e
 * @return {?string} if menu item is click, it will be return item name. For
 * hierichal menu, item name are separated by comma from root t leave, i.e.,
 * 'sync,contact'.
 */
ydn.ui.FlyoutMenu.handleClick = function(e) {
  // todo: e.target or e.target.parentElement instead.
  var item = null;

  if (e.target instanceof Element) {
    if (e.target.classList.contains('goog-menuitem')) {
      item = e.target;
    } else if (e.target.parentElement &&
        e.target.parentElement.classList.contains('goog-menuitem')) {
      item = e.target.parentElement;
    }
  }
  if (item) {
    e.preventDefault();
    e.stopPropagation();
    var is_disable = item.classList.contains('goog-menuitem-disabled');
    var el = goog.dom.getAncestorByClass(item, ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    goog.style.setElementShown(el, false);
    setTimeout(function() {
      // menu show/hide status is determine by hover state
      goog.style.setElementShown(el, true);
    }, 1000);
    return is_disable ? null : ydn.ui.FlyoutMenu.listItemName_(item, '');
  }
  return null;
};

/**
 * Handle click event to root menu.
 * @param {goog.events.BrowserEvent} e
 * @return {?string} if menu item is click, it will be return item name.
 */
ydn.ui.FlyoutMenu.prototype.handleClick = function(e) {
  return ydn.ui.FlyoutMenu.handleClick(e);
};

/**
 * Set menu items.
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} item
 */
ydn.ui.FlyoutMenu.prototype.setItems = function(item) {
  this.items_option_ = ydn.object.clone(item);
  if (this.el_) {
    var menu = this.el_.querySelector('.' + ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    this.el_.removeChild(menu);
    menu = ydn.ui.FlyoutMenu.renderMenu(this.items_option_);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
    this.el_.appendChild(menu);
  }
};

/**
 * @return {Element}
 */
ydn.ui.FlyoutMenu.prototype.getElement = function() {
  return this.el_;
};

/**
 * @typedef {{
 *   iconName: (string|undefined),
 *   className: (string|undefined),
 *   title: (string|undefined),
 *   isRightMenu: (boolean|undefined)
 * }}
 * iconName: svg icon name 'more-vert', 'more-horiz' or 'menu'
 * className: css class name.
 * isRightMenu: menu render to right side. By default, render to left.
 * title: div title for the menu item.
 */
ydn.ui.FlyoutMenu.Option;

/**
 * @typedef {{
 *   label: string,
 *   name: (string),
 *   value: *,
 *   title: (string|undefined),
 *   type: (string|undefined),
 *   disabled: (boolean|undefined),
 *   children: (Array.<ydn.ui.FlyoutMenu.ItemOption>|undefined)
 * }}
 * label: menu text.
 * name: commend in dispatching event.
 * type: SugarCrm.ModuleField#type for input type, default to type='text', for
 * 'bool', it becomes type='checkbox'
 * When type is 'bool', value can be 'true' or 'false'.
 */
ydn.ui.FlyoutMenu.ItemOption;

/**
 * @param {Array.<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @return {Element}
 */
ydn.ui.FlyoutMenu.renderMenu = function(items_option) {
  // console.log(options);
  var dom = goog.dom.getDomHelper();
  var items = [];
  for (var i = 0; i < items_option.length; i++) {
    var opt = items_option[i];
    if (!opt) {
      // render as menu separator
      var rep = dom.createDom('div', {
        'class': 'goog-menuseparator',
        'role': 'separator'
      });
      items.push(rep);
      continue;
    }

    var menu_content = [dom.createDom('div', {
      'class': 'goog-menuitem-content'
    }, opt.label)];
    if (opt.type == 'bool') {
      var chk = dom.createDom('div', {
        'class': 'goog-menuitem-checkbox',
        'role': 'menuitem'
      });
      menu_content.unshift(chk);
    }
    if (opt.children) {
      var svg_arrow = ydn.crm.ui.createSvgIcon('arrow-drop-right');
      svg_arrow.classList.add('left-arrow');
      menu_content.unshift(svg_arrow);
    }
    var div_options = {
      'class': 'goog-menuitem',
      'role': opt.type == 'bool' ? 'goog-menuitem-checkbox' : 'menuitem'
    };
    if (opt.title) {
      div_options['title'] = opt.title;
    }
    var menuitem = dom.createDom('div', div_options, menu_content);
    menuitem.setAttribute('name', opt.name);
    if (opt.value) {
      menuitem.classList.add('goog-option-selected');
    }
    if (opt.children) {
      var sub_menu = ydn.ui.FlyoutMenu.renderMenu(opt.children);
      menuitem.appendChild(sub_menu);
    }
    if (opt.disabled) {
      menuitem.classList.add('goog-menuitem-disabled');
    }
    items.push(menuitem);
  }

  var menu = dom.createDom('div', {
    'class': 'goog-menu goog-menu-vertical',
    'role': 'menu'
  },
  items);
  return menu;
};

/**
 *

 *   var el = document.querySelector('#menu');
 *   var menu = ydn.ui.FlyoutMenu.decoratePopupMenu(el, items);
 *   el.onclick = function(e) {
 *     var name = ydn.ui.FlyoutMenu.handleClick(e);
 *     if (name) {
 *       // menu item with the name click.
 *     }
 *   }
 *

 * @param {Element} el
 * @param {Array<?ydn.ui.FlyoutMenu.ItemOption>} items_option
 * @param {ydn.ui.FlyoutMenu.Option=} opt_option
 */
ydn.ui.FlyoutMenu.decoratePopupMenu = function(el, items_option, opt_option) {
  // console.log(options);
  var icon_name = 'more-vert';
  if (opt_option) {
    if (opt_option.iconName) {
      icon_name = opt_option.iconName;
    }
  }
  var button = document.createElement('span');
  button.className = 'flyout-button';
  var svg = ydn.crm.ui.createSvgIcon(icon_name);
  var menu = ydn.ui.FlyoutMenu.renderMenu(items_option);
  menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_MENU);
  if (!opt_option || !opt_option.isRightMenu) {
    menu.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS_LEFT);
  }
  button.appendChild(svg);
  button.appendChild(menu);
  el.classList.add(ydn.ui.FlyoutMenu.CSS_CLASS);
  el.appendChild(button);
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setEnableMenuItem = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  if (val) {
    el.classList.remove('goog-menuitem-disabled');
  } else {
    el.classList.add('goog-menuitem-disabled');
  }
};

/**
 * Set menu label.
 * @param {Element} menu goog-menu element
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.setMenuItemLabel = function(menu, name, val) {
  var el = menu.querySelector('div.goog-menuitem[name="' + name + '"]');
  if (!el) {
    return;
  }
  var content = el.querySelector('.goog-menuitem-content');
  content.textContent = val;
};

/**
 * Decorate as enable or disable to a given named menu item.
 * @param {string} name menu item name.
 * @param {boolean} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setEnableMenuItem = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setEnableMenuItem(el, name, val);
};

/**
 * Set menu label.
 * @param {string} name menu item name.
 * @param {string} val true to enable, false to disable.
 */
ydn.ui.FlyoutMenu.prototype.setMenuItemLabel = function(name, val) {
  var el = this.getElement();
  ydn.ui.FlyoutMenu.setMenuItemLabel(el, name, val);
};
<|endoftext|>


<commit_before>/**
 * @fileoverview Tests of the veyron storage client.
 */
'use strict';

var streamToArray = function(stream) {
  var results = [];
  stream.on('data', function(val) {
    if (val) {
      results.push(val);
    }
  });

  return new Veyron.Promise(function(resolve, reject) {
    stream.on('end', function() {
      resolve(results);
    });
    stream.on('error', reject);
  });
};

var expectChanges = function(done, stream, expectedChanges) {
  var finish;
  finish = function(err) {
    done(err);
    finish = function() {};
  };
  stream.on('data', function(changeBatch) {
    if (expectedChanges.length === 0) {
      return finish();
    }
    var changes = changeBatch.changes;
    for (var i = 0; i < changes.length; i++) {
      var change = changes[i];
      var expectedChange = expectedChanges.shift();
      expect(expectedChange.name).to.equal(change.name);
      expect(expectedChange.state).to.equal(change.state);
      if (expectedChange.state === Veyron.Watch.State.Exists) {
        expect(expectedChange.value).to.equal(change.value.value);
      }
      if (expectedChanges.length === 0) {
        return finish();
      }
    }
    return null;
  });
  stream.on('end', function() {
    if (expectedChanges.length > 0) {
      return finish('unexpected end of stream');
    }
  });
  stream.on('error', function(err) {
    finish(err);
  });
};

describe('storage/client.js', function() {
  describe('Transaction-less Methods', function() {
    var itemName = 'notx';
    var valToPutRoot = 'AString';
    var valToPut = {};
    var childName = 'x';
    var childVal = 5;
    var itemVeyronName, dirVeyronName, childVeyronName;

    var expectedMapVal = {};
    expectedMapVal[childName] = childVal;

    var store;
    beforeEach(function() {
      var veyron = new Veyron(TestHelper.veyronConfig);
      store = veyron.newStore();
      dirVeyronName = testconfig['STORED_ENDPOINT'] + '/';
      itemVeyronName = dirVeyronName + itemName;
      childVeyronName = itemVeyronName + '/' + childName;
    });

    it('put() root of store', function() {
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.put(null, valToPutRoot);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('put() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.put(null, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('put() succeeds', function() {
      var promise = store.bindTo(childVeyronName).then(function(s) {
        return s.put(null, childVal);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // 1 Item now exists in the store.

    it('exist() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(true);
    });

    // TODO(bprosnitz) Fix setAttr().
    /*
    it('setAttr() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.setAttr(null, []);
      });
      return expect(promise).to.eventually.be.fulfilled;
    });
    */

    // TODO(bprosnitz) Fix stat().
    /*
    it('stat() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.stat(null);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });*/

    it('get() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.get(null);
      });
      return expect(promise).to.eventually.have.property('value').
        and.eql(expectedMapVal);
    });

    it('query()', function() {
      var expectedResults = [
        {
          fields: {},
          name: '',
          nestedResult: 0,
          value: valToPutRoot
        },
        {
          fields: {},
          name: itemName,
          nestedResult: 0,
          value: expectedMapVal
        }
      ];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.query(null, {
          stmt: '*',
        });
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('globT()', function() {
      var expectedResults = [itemName];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.globT(null, '*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('glob()', function() {
      var expectedResults = [
        {
          name: itemName,
          servers: [],
        }
      ];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.glob('*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('remove() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.remove(null);
      });
      return expect(promise).to.be.fulfilled;
    });

    // 0 items now exist in the store.

    it('exist() of removed item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.eql(false);
    });

    it('watchGlob()', function(done) {
      var watchItemName = 'watch';
      var valToPutWatch = 'q';
      var watchItemVeyronName = dirVeyronName + watchItemName;
      store.bindTo(dirVeyronName).then(function(s) {
        return s.watchGlob('*', Veyron.Watch.ResumeMarkers.Now);
      }).then(function(stream) {
        // Put itemName.
        store.bindTo(watchItemVeyronName).then(function(s) {
          s.put(null, valToPutWatch).then(function() {
            s.remove(null);
          });
        });
        // Expect 3 changes. The first announces that the initial state has
        // been skipped, the second updates the item, and the third removes it.
        var expectedChanges = [
          {
            name: '',
            state: Veyron.Watch.State.InitialStateSkipped,
          },
          {
            name: watchItemName,
            state: Veyron.Watch.State.Exists,
            value: valToPutWatch
          },
          {
            name: watchItemName,
            state: Veyron.Watch.State.DoesNotExist
          },
        ];
        expectChanges(done, stream, expectedChanges);
      });
    });

    // TODO(bprosnitz,tilaks) Add watchQuery test once it's possible.
  });

  describe('Methods w/ Transactions', function() {
    var itemName = 'tx';
    var abortedItemName = 'abandoned';
    var valToPutRoot = 'Str';
    var valToPut = [1, 'A'];
    var itemVeyronName;
    var abortedItemVeyronName;
    var dirVeyronName;

    var store;
    beforeEach(function() {
      var veyron = new Veyron(TestHelper.veyronConfig);
      store = veyron.newStore();
      dirVeyronName = testconfig['STORED_ENDPOINT'] + '/';
      itemVeyronName = dirVeyronName + itemName;
      abortedItemVeyronName = dirVeyronName + abortedItemName;
    });

    // Test committing a transaction:
    var trCommit;
    it('committed tr: can create transaction', function() {
      trCommit = Veyron.Transaction();
      expect(trCommit).to.be.not.null;
    });

    it('committed tr: put() root of store', function() {
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.put(trCommit, valToPutRoot);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('committed tr: check item didnt\'t exist() before put', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(trCommit);
      });
      return expect(promise).to.eventually.equal(false);
    });

    it('committed tr: put() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.put(trCommit, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // The item now exists in the store in the context of trCommit.
    it('committed tr: exist() of created item in transaction', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(trCommit);
      });
      return expect(promise).to.eventually.equal(true);
    });

    it('committed tr: commit() succeeds', function() {
      var promise = trCommit.commit();
      return expect(promise).to.eventually.be.fulfilled;
    });

    // The item now is committed to the store.
    it('committed tr: exist() of item created in transaction', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(true);
    });

    // Test aborting a transaction:
    var trAbort;
    it('aborted tr: can create transaction', function() {
      trAbort = Veyron.Transaction();
      expect(trAbort).to.be.not.null;
    });

    it('aborted tr: check item didnt\'t exist() before put', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(trAbort);
      });
      return expect(promise).to.eventually.equal(false);
    });

    it('aborted tr: put() succeeds', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.put(trAbort, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // The item should now exist in the context of trAbort.
    it('aborted tr: exist() of created item in transaction', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(trAbort);
      });
      return expect(promise).to.eventually.equal(true);
    });

    it('aborted tr: abort() succeeds', function() {
      var promise = trAbort.abort();
      return expect(promise).to.eventually.be.fulfilled;
    });

    it('aborted tr: item created in transaction does not exist', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(false);
    });

    // TODO(bprosnitz) Fix setAttr().
    /*
    it('setAttr() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.setAttr(null, []);
      });
      return expect(promise).to.eventually.be.fulfilled;
    });
    */

    // TODO(bprosnitz) Fix stat().
    /*
    it('stat() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.stat(null);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });*/

    it('get() of created item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.get(tr);
      });
      return expect(promise).to.eventually.property('value').and.eql(valToPut);
    });

    it('query()', function() {
      var expectedResults = [
        {
          fields: {},
          name: '',
          nestedResult: 0,
          value: valToPutRoot
        },
        {
          fields: {},
          name: itemName,
          nestedResult: 0,
          value: valToPut
        }
      ];
      var tr = Veyron.Transaction();
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.query(tr, {
          stmt: '*',
        });
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('globT()', function() {
      var tr = Veyron.Transaction();
      var expectedResults = [itemName];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.globT(tr, '*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('remove() of created item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.remove(tr);
      }).then(function() {
        return tr.commit();
      });
      return expect(promise).to.be.fulfilled;
    });

    // 0 items now exist in the store.

    it('exist() of removed item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(tr);
      });
      return expect(promise).to.eventually.equal(false);
    });
  });

  // TODO(bprosnitz) Add tests of using the store with a number of data types.
});
<commit_msg>{go/src/veyron2/storage,go/src/veyron2/services/store,go/src/veyron/services/store}: new transaction API<commit_after>/**
 * @fileoverview Tests of the veyron storage client.
 */
'use strict';

var streamToArray = function(stream) {
  var results = [];
  stream.on('data', function(val) {
    if (val) {
      results.push(val);
    }
  });

  return new Veyron.Promise(function(resolve, reject) {
    stream.on('end', function() {
      resolve(results);
    });
    stream.on('error', reject);
  });
};

var expectChanges = function(done, stream, expectedChanges) {
  var finish;
  finish = function(err) {
    done(err);
    finish = function() {};
  };
  stream.on('data', function(changeBatch) {
    if (expectedChanges.length === 0) {
      return finish();
    }
    var changes = changeBatch.changes;
    for (var i = 0; i < changes.length; i++) {
      var change = changes[i];
      var expectedChange = expectedChanges.shift();
      expect(expectedChange.name).to.equal(change.name);
      expect(expectedChange.state).to.equal(change.state);
      if (expectedChange.state === Veyron.Watch.State.Exists) {
        expect(expectedChange.value).to.equal(change.value.value);
      }
      if (expectedChanges.length === 0) {
        return finish();
      }
    }
    return null;
  });
  stream.on('end', function() {
    if (expectedChanges.length > 0) {
      return finish('unexpected end of stream');
    }
  });
  stream.on('error', function(err) {
    finish(err);
  });
};

// TODO(sadovsky): Fix JS store API/impl and re-enable this test.
describe.skip('storage/client.js', function() {
  describe('Transaction-less Methods', function() {
    var itemName = 'notx';
    var valToPutRoot = 'AString';
    var valToPut = {};
    var childName = 'x';
    var childVal = 5;
    var itemVeyronName, dirVeyronName, childVeyronName;

    var expectedMapVal = {};
    expectedMapVal[childName] = childVal;

    var store;
    beforeEach(function() {
      var veyron = new Veyron(TestHelper.veyronConfig);
      store = veyron.newStore();
      dirVeyronName = testconfig['STORED_ENDPOINT'] + '/';
      itemVeyronName = dirVeyronName + itemName;
      childVeyronName = itemVeyronName + '/' + childName;
    });

    it('put() root of store', function() {
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.put(null, valToPutRoot);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('put() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.put(null, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('put() succeeds', function() {
      var promise = store.bindTo(childVeyronName).then(function(s) {
        return s.put(null, childVal);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // 1 Item now exists in the store.

    it('exist() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(true);
    });

    // TODO(bprosnitz) Fix setAttr().
    /*
    it('setAttr() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.setAttr(null, []);
      });
      return expect(promise).to.eventually.be.fulfilled;
    });
    */

    // TODO(bprosnitz) Fix stat().
    /*
    it('stat() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.stat(null);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });*/

    it('get() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.get(null);
      });
      return expect(promise).to.eventually.have.property('value').
        and.eql(expectedMapVal);
    });

    it('query()', function() {
      var expectedResults = [
        {
          fields: {},
          name: '',
          nestedResult: 0,
          value: valToPutRoot
        },
        {
          fields: {},
          name: itemName,
          nestedResult: 0,
          value: expectedMapVal
        }
      ];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.query(null, {
          stmt: '*',
        });
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('globT()', function() {
      var expectedResults = [itemName];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.globT(null, '*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('glob()', function() {
      var expectedResults = [
        {
          name: itemName,
          servers: [],
        }
      ];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.glob('*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('remove() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.remove(null);
      });
      return expect(promise).to.be.fulfilled;
    });

    // 0 items now exist in the store.

    it('exist() of removed item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.eql(false);
    });

    it('watchGlob()', function(done) {
      var watchItemName = 'watch';
      var valToPutWatch = 'q';
      var watchItemVeyronName = dirVeyronName + watchItemName;
      store.bindTo(dirVeyronName).then(function(s) {
        return s.watchGlob('*', Veyron.Watch.ResumeMarkers.Now);
      }).then(function(stream) {
        // Put itemName.
        store.bindTo(watchItemVeyronName).then(function(s) {
          s.put(null, valToPutWatch).then(function() {
            s.remove(null);
          });
        });
        // Expect 3 changes. The first announces that the initial state has
        // been skipped, the second updates the item, and the third removes it.
        var expectedChanges = [
          {
            name: '',
            state: Veyron.Watch.State.InitialStateSkipped,
          },
          {
            name: watchItemName,
            state: Veyron.Watch.State.Exists,
            value: valToPutWatch
          },
          {
            name: watchItemName,
            state: Veyron.Watch.State.DoesNotExist
          },
        ];
        expectChanges(done, stream, expectedChanges);
      });
    });

    // TODO(bprosnitz,tilaks) Add watchQuery test once it's possible.
  });

  describe('Methods w/ Transactions', function() {
    var itemName = 'tx';
    var abortedItemName = 'abandoned';
    var valToPutRoot = 'Str';
    var valToPut = [1, 'A'];
    var itemVeyronName;
    var abortedItemVeyronName;
    var dirVeyronName;

    var store;
    beforeEach(function() {
      var veyron = new Veyron(TestHelper.veyronConfig);
      store = veyron.newStore();
      dirVeyronName = testconfig['STORED_ENDPOINT'] + '/';
      itemVeyronName = dirVeyronName + itemName;
      abortedItemVeyronName = dirVeyronName + abortedItemName;
    });

    // Test committing a transaction:
    var trCommit;
    it('committed tr: can create transaction', function() {
      trCommit = Veyron.Transaction();
      expect(trCommit).to.be.not.null;
    });

    it('committed tr: put() root of store', function() {
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.put(trCommit, valToPutRoot);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    it('committed tr: check item didnt\'t exist() before put', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(trCommit);
      });
      return expect(promise).to.eventually.equal(false);
    });

    it('committed tr: put() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.put(trCommit, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // The item now exists in the store in the context of trCommit.
    it('committed tr: exist() of created item in transaction', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(trCommit);
      });
      return expect(promise).to.eventually.equal(true);
    });

    it('committed tr: commit() succeeds', function() {
      var promise = trCommit.commit();
      return expect(promise).to.eventually.be.fulfilled;
    });

    // The item now is committed to the store.
    it('committed tr: exist() of item created in transaction', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(true);
    });

    // Test aborting a transaction:
    var trAbort;
    it('aborted tr: can create transaction', function() {
      trAbort = Veyron.Transaction();
      expect(trAbort).to.be.not.null;
    });

    it('aborted tr: check item didnt\'t exist() before put', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(trAbort);
      });
      return expect(promise).to.eventually.equal(false);
    });

    it('aborted tr: put() succeeds', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.put(trAbort, valToPut);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });

    // The item should now exist in the context of trAbort.
    it('aborted tr: exist() of created item in transaction', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(trAbort);
      });
      return expect(promise).to.eventually.equal(true);
    });

    it('aborted tr: abort() succeeds', function() {
      var promise = trAbort.abort();
      return expect(promise).to.eventually.be.fulfilled;
    });

    it('aborted tr: item created in transaction does not exist', function() {
      var promise = store.bindTo(abortedItemVeyronName).then(function(s) {
        return s.exists(null);
      });
      return expect(promise).to.eventually.equal(false);
    });

    // TODO(bprosnitz) Fix setAttr().
    /*
    it('setAttr() of created item', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.setAttr(null, []);
      });
      return expect(promise).to.eventually.be.fulfilled;
    });
    */

    // TODO(bprosnitz) Fix stat().
    /*
    it('stat() succeeds', function() {
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.stat(null);
      });
      return expect(promise).to.eventually.include.keys(
        ['attrs', 'iD', 'mTimeNS']);
    });*/

    it('get() of created item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.get(tr);
      });
      return expect(promise).to.eventually.property('value').and.eql(valToPut);
    });

    it('query()', function() {
      var expectedResults = [
        {
          fields: {},
          name: '',
          nestedResult: 0,
          value: valToPutRoot
        },
        {
          fields: {},
          name: itemName,
          nestedResult: 0,
          value: valToPut
        }
      ];
      var tr = Veyron.Transaction();
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.query(tr, {
          stmt: '*',
        });
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('globT()', function() {
      var tr = Veyron.Transaction();
      var expectedResults = [itemName];
      var promise = store.bindTo(dirVeyronName).then(function(s) {
        return s.globT(tr, '*');
      }).then(streamToArray);
      return expect(promise).to.eventually.eql(expectedResults);
    });

    it('remove() of created item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.remove(tr);
      }).then(function() {
        return tr.commit();
      });
      return expect(promise).to.be.fulfilled;
    });

    // 0 items now exist in the store.

    it('exist() of removed item', function() {
      var tr = Veyron.Transaction();
      var promise = store.bindTo(itemVeyronName).then(function(s) {
        return s.exists(tr);
      });
      return expect(promise).to.eventually.equal(false);
    });
  });

  // TODO(bprosnitz) Add tests of using the store with a number of data types.
});
<|endoftext|>