Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <gerwitzm@lovullo.com>2017-01-26 16:03:00 -0500
committerMike Gerwitz <gerwitzm@lovullo.com>2017-01-29 22:44:27 -0500
commit2045c76f7e40878f2de308fb2579899a86fea80f (patch)
tree8491f925c7dec5f0ccec3ef6d2e5269d74e9efc1
parent203b25f10e0d446ae9971bd157b8b2a7cfc4ddc0 (diff)
downloadliza-2045c76f7e40878f2de308fb2579899a86fea80f.tar.gz
liza-2045c76f7e40878f2de308fb2579899a86fea80f.tar.bz2
liza-2045c76f7e40878f2de308fb2579899a86fea80f.zip
Integrate Store into DataValidator, ValidStateMonitor
ValidStateMonitor now uses a Store in place of the original primitive object-based diff format. The original format is translated by DataValidator. The code is in a transitional state, and considering the amount of time we spend on various areas of this project, will likely stay this way for a while. * src/validate/DataValidator.js (__construct): Accept Store factory parameter. (_store_factory): Add field. (_createStores): Add method. (_validate): Handle Store. (updateFailures): Add method. (_populateStore): Add method. * test/validate/DataValidatorTest.js: Add tests. * src/validate/ValidStateMonitor.js (update): Enforce Store diff. Wait to process failures until fixes are calculated. (_checkFailureFix): Handle asynchronous, Promise-based diff. (_checkCauseFix): Extract logic from _checkCauseFix. * test/validate/ValidStateMonitorTest.js: Modify test cases to be Promise-based and handle async calls where appropriate. That was a friggin' expensive mess. DEV-2296
-rw-r--r--src/validate/DataValidator.js82
-rw-r--r--src/validate/ValidStateMonitor.js152
-rw-r--r--test/validate/DataValidatorTest.js53
-rw-r--r--test/validate/ValidStateMonitorTest.js605
4 files changed, 578 insertions, 314 deletions
diff --git a/src/validate/DataValidator.js b/src/validate/DataValidator.js
index 0fead8d..fa56ec6 100644
--- a/src/validate/DataValidator.js
+++ b/src/validate/DataValidator.js
@@ -55,6 +55,12 @@ module.exports = Class( 'DataValidator',
*/
'private _factory': null,
+ /**
+ * Bucket diff store
+ * @type {Store}
+ */
+ 'private _store_factory': null,
+
/**
* Initialize validator
@@ -62,12 +68,35 @@ module.exports = Class( 'DataValidator',
* @param {BucketDataValidator} bucket_validator data validator
* @param {ValidStateMonitor} field_monitor field state monitor
* @param {ClientDependencyFactory} dep_factory REMOVE ME
+ * @param {function()} store_factory factory for diff store
*/
- __construct( bucket_validator, field_monitor, dep_factory )
+ __construct(
+ bucket_validator, field_monitor, dep_factory, store_factory
+ )
{
+ if ( typeof store_factory !== 'function' )
+ {
+ throw TypeError( "Expected function for parameter store_factory" );
+ }
+
this._bucket_validator = bucket_validator;
this._field_monitor = field_monitor;
this._factory = dep_factory;
+
+ this._createStores( store_factory );
+ },
+
+
+ /**
+ * Create internal diff stores
+ *
+ * @param {function()} store_factory function to produce stores
+ *
+ * @return {undefined}
+ */
+ 'private _createStores': function( store_factory )
+ {
+ this._bucket_store = store_factory();
},
@@ -89,7 +118,7 @@ module.exports = Class( 'DataValidator',
let failures = {};
- this._bucket_validator.validate( diff, ( name, value, i ) =>
+ _self._bucket_validator.validate( diff, ( name, value, i ) =>
{
diff[ name ][ i ] = undefined;
@@ -100,6 +129,53 @@ module.exports = Class( 'DataValidator',
validatef && validatef( diff, failures );
// XXX: this assumes that the above is synchronous
- return this._field_monitor.update( diff, failures );
+ return this.updateFailures( diff, failures );
+ },
+
+
+ /**
+ * Update failures from external validation
+ *
+ * TODO: This is a transitional API---we should handle all validations,
+ * not allow external systems to meddle in our affairs.
+ *
+ * @param {Object} diff bucket diff
+ * @param {Object} failures failures per field name and index
+ *
+ * @return {Promise} promise to populate internal store
+ */
+ 'public updateFailures'( diff, failures )
+ {
+ const _self = this;
+
+ return this._populateStore( diff ).then( () =>
+ {
+ return _self._field_monitor.update(
+ _self._bucket_store, failures
+ );
+ } );
+ },
+
+
+ /**
+ * Populate store with diff
+ *
+ * This effectively converts a basic array into a `Store`. This is
+ * surprisingly performant on v8. If the stores mix in traits, there
+ * may be a slight performance hit for trait-overridden methods.
+ *
+ * @param {Object} diff bucket diff
+ *
+ * @return {Promise} when all items have been added to the store
+ */
+ 'private _populateStore'( diff )
+ {
+ var bstore = this._bucket_store;
+
+ return Promise.all(
+ Object.keys( diff ).map(
+ key => bstore.add( key, diff[ key ] )
+ )
+ );
},
} );
diff --git a/src/validate/ValidStateMonitor.js b/src/validate/ValidStateMonitor.js
index a2b530f..5125967 100644
--- a/src/validate/ValidStateMonitor.js
+++ b/src/validate/ValidStateMonitor.js
@@ -19,9 +19,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-var Class = require( 'easejs' ).Class,
- EventEmitter = require( 'events' ).EventEmitter,
- Failure = require( './Failure' );
+var Class = require( 'easejs' ).Class;
+var EventEmitter = require( 'events' ).EventEmitter;
+var Failure = require( './Failure' );
+var Store = require( '../store/Store' );
/**
@@ -50,9 +51,8 @@ module.exports = Class( 'ValidStateMonitor' )
* should omitted from the value if they are not failures.
*
* The return value is a promise that is accepted once all fix checks
- * have been performed (after which the `fix` event is emitted if
- * appropriate). The `failure` event is emitted synchronously if any
- * additional failures are detected.
+ * have been performed. The `failure` event is always emitted _before_
+ * the fix event.
*
* @param {Object} data key-value field data
* @param {Object} failures key-value field errors
@@ -61,19 +61,25 @@ module.exports = Class( 'ValidStateMonitor' )
*/
'public update': function( data, failures )
{
- var _self = this;
-
- var fixed = this.detectFixes( data, this._failures, failures ),
- count_new = this.mergeFailures( this._failures, failures );
-
- if ( this.hasFailures() && ( count_new > 0 ) )
+ if ( !Class.isA( Store, data ) )
{
- this.emit( 'failure', this._failures );
+ throw TypeError(
+ 'Bucket diff data must be a Store; given ' + data
+ );
}
- // failures is synchronous, fixes async
+ var _self = this;
+ var fixed = this.detectFixes( data, this._failures, failures );
+
return fixed.then( function( fixes )
{
+ var count_new = _self.mergeFailures( _self._failures, failures );
+
+ if ( _self.hasFailures() && ( count_new > 0 ) )
+ {
+ _self.emit( 'failure', _self._failures );
+ }
+
if ( fixes !== null )
{
_self.emit( 'fix', fixes );
@@ -238,50 +244,98 @@ module.exports = Class( 'ValidStateMonitor' )
*/
'private _checkFailureFix': function( name, fail, past_fail, data, fixed )
{
- var has_fixed = false;
+ var _self = this;
// we must check each individual index because it is possible that
// not every index was modified or fixed (we must loop through like
// this because this is treated as a hash table, not an array)
- for ( var i in past_fail )
+ return Promise.all( past_fail.map( function( failure, fail_i )
{
- var causes = past_fail[ i ] && past_fail[ i ].getCauses();
-
- for ( var cause_i in causes )
- {
- var cause = causes[ cause_i ],
- cause_name = cause.getName(),
- cause_index = cause.getIndex(),
- field = data[ cause_name ];
-
- // if datum is unchanged, ignore it
- if ( field === undefined )
- {
- continue;
- }
-
- // to be marked as fixed, there must both me no failure and
- // there must be data for this index for the field in question
- // (if the field wasn't touched, then of course there's no
- // failure!)
- if ( ( fail === undefined )
- || ( !( fail[ cause_index ] )
- && ( field[ cause_index ] !== undefined ) )
+ var causes = failure && failure.getCauses() || [];
+
+ // to short-circuit checks, the promise will be _rejected_ once
+ // a match is found (see catch block)
+ return causes
+ .reduce(
+ _self._checkCauseFix.bind( _self, data, fail ),
+ Promise.resolve( true )
)
+ .then( function()
+ {
+ // no fixes
+ return false;
+ } )
+ .catch( function( result )
{
+ if ( result instanceof Error )
+ {
+ throw result;
+ }
+
// looks like it has been resolved
- ( fixed[ name ] = fixed[ name ] || [] )[ i ] =
- field[ cause_index ]
+ ( fixed[ name ] = fixed[ name ] || [] )[ fail_i ] = result;
+
+ delete past_fail[ fail_i ];
+ return true;
+ } );
+ } ) ).then( function( result ) {
+ return result.some( function( val )
+ {
+ return val === true;
+ } );
+ } );
+ },
- has_fixed = true;
- delete past_fail[ i ];
- break;
- }
- }
- }
+ /**
+ * Check past failure causes
+ *
+ * Each past failure in `fail` will be checked against the data in
+ * `diff` to determine whether it should be considered a possible
+ * fix. If so, the promise is fulfilled with the fix data. It is the
+ * responsibility of the caller to handle removing past failures.
+ *
+ * @param {Object} data validated data
+ * @param {Object} fail failure records
+ * @param {Promise} causep cause promise to chain onto
+ * @param {Field} cause field that caused the error
+ *
+ * @return {Promise} whether a field should be fixed
+ */
+ 'private _checkCauseFix': function( data, fail, causep, cause )
+ {
+ var cause_name = cause.getName();
+ var cause_index = cause.getIndex();
- // preparation for future use of Store, which is async
- return Promise.resolve( has_fixed );
- }
+ return causep.then( function()
+ {
+ return new Promise( function( keepgoing, found )
+ {
+ data.get( cause_name ).then( function( field )
+ {
+ // to be marked as fixed, there must both me no failure
+ // and there must be data for this index for the field
+ // in question (if the field wasn't touched, then of
+ // course there's no failure!)
+ if ( ( fail === undefined )
+ || ( !( fail[ cause_index ] )
+ && ( field[ cause_index ] !== undefined ) )
+ )
+ {
+ found( field[ cause_index ] );
+ return;
+ }
+
+ // keep searching
+ keepgoing( true );
+ } )
+ .catch( function( e )
+ {
+ // doesn't exist, so just keep searching (it
+ // wasn't fixed)
+ keepgoing( true );
+ } );
+ } );
+ } );
+ },
} );
diff --git a/test/validate/DataValidatorTest.js b/test/validate/DataValidatorTest.js
index a209486..62478ee 100644
--- a/test/validate/DataValidatorTest.js
+++ b/test/validate/DataValidatorTest.js
@@ -21,12 +21,13 @@
"use strict";
-const root = require( '../../' );
-const validate = root.validate;
-const Sut = validate.DataValidator;
-const chai = require( 'chai' );
-const expect = chai.expect;
-const sinon = require( 'sinon' );
+const root = require( '../../' );
+const validate = root.validate;
+const Sut = validate.DataValidator;
+const MemoryStore = root.store.MemoryStore;
+const chai = require( 'chai' );
+const expect = chai.expect;
+const sinon = require( 'sinon' );
const BucketDataValidator = validate.BucketDataValidator,
ValidStateMonitor = validate.ValidStateMonitor;
@@ -55,6 +56,7 @@ describe( 'DataValidator', () =>
const vmonitor = ValidStateMonitor();
const dep_factory = createMockDependencyFactory();
+ const getStore = createStubStore();
const mock_vmonitor = sinon.mock( vmonitor );
const mock_dep_factory = sinon.mock( dep_factory );
@@ -68,7 +70,7 @@ describe( 'DataValidator', () =>
mock_vmonitor.expects( 'update' )
.once()
- .withExactArgs( diff, expected_failures )
+ .withExactArgs( getStore(), expected_failures )
.returns( Promise.resolve( undefined ) );
mock_dep_factory.expects( 'createFieldFailure' )
@@ -76,19 +78,17 @@ describe( 'DataValidator', () =>
.withExactArgs( 'foo', 1, expected_value )
.returns( expected_failure );
- const retp = Sut( bvalidator, vmonitor, dep_factory )
- .validate( diff );
-
- // cleared on call to err in above mock validator
- expect( diff.foo ).to.deep.equal(
- [ 'a', undefined, 'c' ]
- );
-
- mock_vmonitor.verify();
- mock_dep_factory.verify();
+ return Sut( bvalidator, vmonitor, dep_factory, getStore )
+ .validate( diff )
+ .then( () =>
+ {
+ mock_vmonitor.verify();
+ mock_dep_factory.verify();
- // the promise
- return retp;
+ // cleared on call to err in above mock validator
+ return expect( getStore().get( 'foo' ) )
+ .to.eventually.deep.equal( [ 'a', undefined, 'c' ] );
+ } );
} );
@@ -106,6 +106,7 @@ describe( 'DataValidator', () =>
const vmonitor = ValidStateMonitor();
const dep_factory = createMockDependencyFactory();
+ const getStore = createStubStore();
const diff = { foo: [ 'a', 'b', 'c' ] };
const expected_failures = {
@@ -129,14 +130,14 @@ describe( 'DataValidator', () =>
sinon.mock( vmonitor )
.expects( 'update' )
.once()
- .withExactArgs( diff, expected_failures )
+ .withExactArgs( getStore(), expected_failures )
.returns( Promise.resolve( undefined ) );
sinon.mock( dep_factory )
.expects( 'createFieldFailure' )
.returns( expected_failure );
- return Sut( bvalidator, vmonitor, dep_factory )
+ return Sut( bvalidator, vmonitor, dep_factory, getStore )
.validate( diff, validatef );
} );
@@ -155,7 +156,7 @@ describe( 'DataValidator', () =>
.returns( Promise.reject( expected_e ) );
return expect(
- Sut( bvalidator, vmonitor, dep_factory )
+ Sut( bvalidator, vmonitor, dep_factory, createStubStore() )
.validate( {} )
).to.eventually.be.rejectedWith( expected_e );
} );
@@ -181,3 +182,11 @@ function createMockDependencyFactory( map )
createFieldFailure: () => {},
};
}
+
+
+function createStubStore()
+{
+ const store = MemoryStore();
+
+ return () => store;
+}
diff --git a/test/validate/ValidStateMonitorTest.js b/test/validate/ValidStateMonitorTest.js
index c129cdd..75dab73 100644
--- a/test/validate/ValidStateMonitorTest.js
+++ b/test/validate/ValidStateMonitorTest.js
@@ -19,17 +19,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-var root = require( '../../' ),
- Sut = root.validate.ValidStateMonitor,
- chai = require( 'chai' ),
- expect = chai.expect,
- Failure = root.validate.Failure,
- Field = root.field.BucketField;
+"use strict";
+
+const root = require( '../../' );
+const Sut = root.validate.ValidStateMonitor;
+const chai = require( 'chai' );
+const expect = chai.expect;
+const Failure = root.validate.Failure;
+const Field = root.field.BucketField;
+const MemoryStore = root.store.MemoryStore;
chai.use( require( 'chai-as-promised' ) );
-var nocall = function( type )
+const nocall = function( type )
{
return function()
{
@@ -37,7 +40,7 @@ var nocall = function( type )
};
};
-var mkfail = function( name, arr )
+const mkfail = function( name, arr )
{
return arr.map( function( value, i )
{
@@ -54,19 +57,25 @@ describe( 'ValidStateMonitor', function()
{
it( 'does nothing with no data or failures', function()
{
- return Sut()
- .on( 'failure', nocall( 'failure' ) )
- .on( 'fix', nocall( 'fix' ) )
- .update( {}, {} );
+ return mkstore( {} ).then( empty =>
+ {
+ return Sut()
+ .on( 'failure', nocall( 'failure' ) )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( empty, {} );
+ } );
} );
it( 'does nothing with data but no failures', function()
{
- return Sut()
- .on( 'failure', nocall( 'failure' ) )
- .on( 'fix', nocall( 'fix' ) )
- .update( { foo: mkfail( 'foo', [ 'bar' ] ) }, {} );
+ return mkstore( { foo: mkfail( 'foo', [ 'bar' ] ) } ).then( store =>
+ {
+ return Sut()
+ .on( 'failure', nocall( 'failure' ) )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( store, {} );
+ } );
} );
@@ -74,88 +83,118 @@ describe( 'ValidStateMonitor', function()
// need the data
describe( 'given failures', function()
{
- it( 'marks failures even when given no data', function( done )
+ it( 'marks failures even when given no data', function()
{
var fail = mkfail( 'foo', [ 'bar', 'baz' ] );
- Sut()
- .on( 'failure', function( failures )
+ return mkstore( {} ).then( empty =>
+ {
+ return new Promise( accept =>
{
- expect( failures )
- .to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
- done();
- } )
- .on( 'fix', nocall( 'fix' ) )
- .update( {}, { foo: fail } );
+ return Sut()
+ .on( 'failure', function( failures )
+ {
+ expect( failures )
+ .to.deep.equal(
+ { foo: [ fail[ 0 ], fail[ 1 ] ] }
+ );
+ accept();
+ } )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( empty, { foo: fail } );
+ } );
+ } );
} );
- it( 'marks failures with index gaps', function( done )
+ it( 'marks failures with index gaps', function()
{
var fail = mkfail( 'foo', [ undefined, 'baz' ] );
- Sut()
- .on( 'failure', function( failures )
+ return mkstore( {} ).then( empty =>
+ {
+ return new Promise( accept =>
{
- expect( failures )
- .to.deep.equal( { foo: [ undefined, fail[ 1 ] ] } );
- done();
- } )
- .on( 'fix', nocall( 'fix' ) )
- .update( {}, { foo: fail } );
+ Sut()
+ .on( 'failure', function( failures )
+ {
+ expect( failures )
+ .to.deep.equal(
+ { foo: [ undefined, fail[ 1 ] ] }
+ );
+ accept();
+ } )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( empty, { foo: fail } );
+ } );
+ } );
} );
- it( 'retains past failures when setting new', function( done )
+ it( 'retains past failures when setting new', function()
{
var sut = Sut(),
fail = mkfail( 'foo', [ 'bar', 'baz' ] );
- var test_first = function( failures )
+ return new Promise( ( accept, reject ) =>
{
- expect( failures )
- .to.deep.equal( { foo: [ undefined, fail[ 1 ] ] } );
+ var test_first = function( failures )
+ {
+ expect( failures )
+ .to.deep.equal( { foo: [ undefined, fail[ 1 ] ] } );
- sut.once( 'failure', test_second );
- };
+ sut.once( 'failure', test_second );
+ };
- var test_second = function( failures )
- {
- expect( failures )
- .to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
+ var test_second = function( failures )
+ {
+ expect( failures )
+ .to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
- done();
- };
+ accept();
+ };
- sut
- .once( 'failure', test_first )
- .on( 'fix', nocall( 'fix' ) )
- .update( {}, { foo: [ undefined, fail[ 1 ] ] } )
- .then( () =>
+ mkstore( {} ).then( empty =>
{
- return sut.update( {}, { foo: [ fail[ 0 ] ] } );
- } );
+ return sut
+ .once( 'failure', test_first )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( empty, { foo: [ undefined, fail[ 1 ] ] } )
+ .then( () =>
+ {
+ return sut.update( empty, { foo: [ fail[ 0 ] ] } );
+ } );
+ } ).catch( e => reject( e ) );
+ } );
} );
// deprecated
- it( 'accepts failures as string for BC', function( done )
+ it( 'accepts failures as string for BC', function()
{
var fail = [ 'foo', 'bar' ];
- Sut()
- .on( 'failure', function( failures )
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( {} ).then( empty =>
{
- expect( failures )
- .to.deep.equal( { foo: fail } );
- done();
+ return Sut()
+ .on( 'failure', function( failures )
+ {
+ expect( failures )
+ .to.deep.equal( { foo: fail } );
+
+ accept();
+ } )
+ .on( 'fix', nocall( 'fix' ) )
+ .update( empty, { foo: fail } );
} )
- .on( 'fix', nocall( 'fix' ) )
- .update( {}, { foo: fail } );
+ .catch( e => reject( e ) );
+ } );
} );
- it( 'does not discard existing failures', function( done )
+ it( 'does not discard existing failures', function()
{
var sut = Sut();
@@ -176,79 +215,106 @@ describe( 'ValidStateMonitor', function()
// the second failure has fewer causes than the first;
// we need to make sure that it doesn't overwrite,
// leading to fewer caues
- sut
- .update( {}, { foo: [ fail1 ] } )
- .then( () =>
- {
- return sut.update( {}, { foo: [ fail2 ] } );
- } )
- .then( () =>
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( {} ).then( empty =>
{
return sut
- .once( 'fix', function( fixed )
+ .update( empty, { foo: [ fail1 ] } )
+ .then( () =>
{
- expect( fixed )
- .to.deep.equal( { foo: [ 'causefix1' ] } );
-
- // and then we should have no failures
- expect( sut.hasFailures() ).to.be.false;
-
- done();
+ return sut.update( empty, { foo: [ fail2 ] } );
} )
- .update(
- { foo: [ 'moo' ], cause1: [ 'causefix1' ] },
- {}
- );
- } );
+ .then( () =>
+ {
+ const update = {
+ foo: [ 'moo' ],
+ cause1: [ 'causefix1' ]
+ };
+
+ return mkstore( update ).then( store =>
+ {
+ return sut
+ .once( 'fix', function( fixed )
+ {
+ expect( fixed ).to.deep.equal(
+ { foo: [ 'causefix1' ] }
+ );
+
+ // and then we should have no failures
+ expect( sut.hasFailures() )
+ .to.be.false;
+
+ accept( true );
+ } )
+ .update( store, {} );
+ } );
+ } );
+ } )
+ .catch( e => reject( e ) );
+ } );
} );
} );
describe( 'given data with absence of failure', function()
{
- it( 'removes non-failures if field is present', function( done )
+ it( 'removes non-failures if field is present', function()
{
- var data = { foo: [ 'bardata', 'baz' ] },
- fail = mkfail( 'foo', [ 'bar', 'baz' ] );
-
- var sut = Sut();
+ const fail = mkfail( 'foo', [ 'bar', 'baz' ] );
+ const sut = Sut();
- sut
- .on( 'fix', function( fixed )
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( { foo: [ 'bardata', 'baz' ] } ).then( data =>
{
- expect( fixed )
- .to.deep.equal( { foo: [ 'bardata' ] } );
- done();
+ return sut
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal( { foo: [ 'bardata' ] } );
+ accept();
+ } )
+ .update( data, { foo: [ fail[ 0 ], fail[ 1 ] ] } )
+ .then( () =>
+ {
+ return sut.update( data, {
+ foo: [ undefined, fail[ 1 ] ]
+ } );
+ } );
} )
- .update( data, { foo: [ fail[ 0 ], fail[ 1 ] ] } )
- .then( () =>
- {
- return sut.update( data, { foo: [ undefined, fail[ 1 ] ] } );
- } );
+ .catch( e => reject( e ) );
+ } );
} );
- it( 'keeps failures if field is missing', function( done )
+ it( 'keeps failures if field is missing', function()
{
- var data = { bar: [ 'baz', 'quux' ] },
- fail_foo = mkfail( 'foo', [ 'bar', 'baz' ] ),
- fail_bar = mkfail( 'bar', [ 'moo', 'cow' ] );
+ const fail_foo = mkfail( 'foo', [ 'bar', 'baz' ] );
+ const fail_bar = mkfail( 'bar', [ 'moo', 'cow' ] );
- Sut()
- .on( 'fix', function( fixed )
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( { bar: [ 'baz', 'quux' ] } ).then( data =>
{
- expect( fixed )
- .to.deep.equal( { bar: [ 'baz', 'quux' ] } );
- done();
- } )
- .update( data, {
- foo: fail_foo, // does not exist in data
- bar: fail_bar,
+ return Sut()
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal( { bar: [ 'baz', 'quux' ] } );
+ accept();
+ } )
+ .update( data, {
+ foo: fail_foo, // does not exist in data
+ bar: fail_bar,
+ } )
+ .then( sut =>
+ {
+ return sut.update( data, {} );
+ } );
} )
- .then( sut =>
- {
- return sut.update( data, {} );
- } );
+ .catch( e => reject( e ) );
+ } );
} );
@@ -256,156 +322,199 @@ describe( 'ValidStateMonitor', function()
{
var called = 0;
- Sut()
- .on( 'failure', function()
- {
- called++;
- } )
- .update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } )
- .then( sut =>
- {
- return sut.update( {}, {} ); // do not trigger failure event
- } )
- .then( sut =>
- {
- expect( called ).to.equal( 1 );
- } );
+ return mkstore( {} ).then( empty =>
+ {
+ return Sut()
+ .on( 'failure', function()
+ {
+ called++;
+ } )
+ .update( empty, { foo: mkfail( 'foo', [ 'bar' ] ) } )
+ .then( sut =>
+ {
+ return sut.update( empty, {} ); // do not trigger failure event
+ } )
+ .then( sut =>
+ {
+ expect( called ).to.equal( 1 );
+ } );
+ } );
} );
describe( 'given a cause', function()
{
- it( 'considers when recognizing fix', function( done )
+ it( 'considers when recognizing fix', function()
{
// same index
- var data = { cause: [ 'bar' ] },
- field = Field( 'foo', 0 ),
- cause = Field( 'cause', 0 ),
- fail = Failure( field, 'reason', [ cause ] );
+ const field = Field( 'foo', 0 );
+ const cause = Field( 'cause', 0 );
+ const fail = Failure( field, 'reason', [ cause ] );
- Sut()
- .on( 'fix', function( fixed )
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( { cause: [ 'bar' ] } ).then( data =>
{
- expect( fixed )
- .to.deep.equal( { foo: [ 'bar' ] } );
-
- done();
+ return Sut()
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal( { foo: [ 'bar' ] } );
+
+ accept();
+ } )
+ .update( data, { foo: [ fail ] } )
+ .then( sut =>
+ {
+ return sut.update( data, {} );
+ } );
} )
- .update( data, { foo: [ fail ] } )
- .then( sut =>
- {
- return sut.update( data, {} );
- } );
+ .catch( e => reject( e ) );
+ } );
} );
- it( 'considers different cause index', function( done )
+ it( 'considers different cause index', function()
{
// different index
- var data = { cause: [ undefined, 'bar' ] },
- field = Field( 'foo', 0 ),
- cause = Field( 'cause', 1 ),
- fail = Failure( field, 'reason', [ cause ] );
+ const update_data = { cause: [ undefined, 'bar' ] };
+ const field = Field( 'foo', 0 );
+ const cause = Field( 'cause', 1 );
+ const fail = Failure( field, 'reason', [ cause ] );
- Sut()
- .on( 'fix', function( fixed )
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( update_data ).then( data =>
{
- expect( fixed )
- .to.deep.equal( { foo: [ 'bar' ] } );
-
- done();
+ return Sut()
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal( { foo: [ 'bar' ] } );
+
+ accept();
+ } )
+ .update( data, { foo: [ fail ] } )
+ .then( sut =>
+ {
+ return sut.update( data, {} );
+ } );
} )
- .update( data, { foo: [ fail ] } )
- .then( sut =>
- {
- return sut.update( data, {} );
- } );
+ .catch( e => reject( e ) );
+ } );
} );
- it( 'considers any number of causes', function( done )
+ it( 'considers any number of causes', function()
{
// different index
- var data = { cause_fix: [ undefined, 'bar' ] },
- field = Field( 'foo', 0 ),
- cause1 = Field( 'cause_no', 1 ),
- cause2 = Field( 'cause_fix', 1 ),
- fail = Failure(
- field,
- 'reason',
- [ cause1, cause2 ]
- );
-
- Sut()
- .on( 'fix', function( fixed )
+ const update_data = { cause_fix: [ undefined, 'bar' ] };
+ const field = Field( 'foo', 0 );
+ const cause1 = Field( 'cause_no', 1 );
+ const cause2 = Field( 'cause_fix', 1 );
+
+ const fail = Failure(
+ field,
+ 'reason',
+ [ cause1, cause2 ]
+ );
+
+ return new Promise( ( accept, reject ) =>
+ {
+ return mkstore( update_data ).then( data =>
{
- expect( fixed )
- .to.deep.equal( { foo: [ 'bar' ] } );
-
- done();
+ return Sut()
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal( { foo: [ 'bar' ] } );
+
+ accept();
+ } )
+ .update( data, { foo: [ fail ] } )
+ .then( sut =>
+ {
+ return sut.update( data, {} );
+ } );
} )
- .update( data, { foo: [ fail ] } )
- .then( sut =>
- {
- return sut.update( data, {} );
- } );
+ .catch( e => reject( e ) );
+ } );
} );
it( 'recognizes non-fix', function()
{
// no cause data
- var data = { noncause: [ undefined, 'bar' ] },
- field = Field( 'foo', 0 ),
- cause1 = Field( 'cause', 1 ),
- cause2 = Field( 'cause', 2 ),
- fail = Failure(
- field,
- 'reason',
- [ cause1, cause2 ]
- );
-
- Sut()
- .on( 'fix', nocall )
- .update( data, { foo: [ fail ] } )
- .then( sut =>
- {
- return sut.update( data, {} );
- } );
+ const update_data = mkstore( { noncause: [ undefined, 'bar' ] } );
+ const field = Field( 'foo', 0 );
+ const cause1 = Field( 'cause', 1 );
+ const cause2 = Field( 'cause', 2 );
+
+ const fail = Failure(
+ field,
+ 'reason',
+ [ cause1, cause2 ]
+ );
+
+ return mkstore( update_data ).then( data =>
+ {
+ return Sut()
+ .on( 'fix', nocall )
+ .update( data, { foo: [ fail ] } )
+ .then( sut =>
+ {
+ return sut.update( data, {} );
+ } );
+ } );
} );
} );
} );
- it( 'can emit both failure and fix', function( done )
+ it( 'can emit both failure and fix', function()
{
- var data = { bar: [ 'baz', 'quux' ] },
- fail_foo = mkfail( 'foo', [ 'bar' ] );
-
- Sut()
- .update( data, {
- bar: mkfail( 'bar', [ 'moo', 'cow' ] ) // fail
- } )
- .then( sut =>
- {
- return sut.on( 'failure', function( failed )
- {
- expect( failed )
- .to.deep.equal( {
- foo: fail_foo,
- } );
+ var fail_foo = mkfail( 'foo', [ 'bar' ] );
+
+ return mkstore( { bar: [ 'baz', 'quux' ] } ).then( data =>
+ {
+ return Sut()
+ .update( data, {
+ bar: mkfail( 'bar', [ 'moo', 'cow' ] ) // fail
} )
- .on( 'fix', function( fixed )
+ .then( sut =>
{
- expect( fixed )
- .to.deep.equal( { bar: [ 'baz', 'quux' ] } );
- done();
- } )
- .update( data, {
- foo: fail_foo, // fail
- // fixes bar
+ return new Promise( ( accept, reject ) =>
+ {
+ sut.on( 'failure', function( failed )
+ {
+ expect( failed )
+ .to.deep.equal( {
+ foo: fail_foo,
+ } );
+ } )
+ .on( 'fix', function( fixed )
+ {
+ expect( fixed )
+ .to.deep.equal(
+ { bar: [ 'baz', 'quux' ] }
+ );
+
+ // note that the documentation for #update
+ // states that failure will always be
+ // emitted before fix
+ accept( true );
+ } )
+ .update( data, {
+ foo: fail_foo, // fail
+ // fixes bar
+ } )
+ .catch( e =>
+ {
+ reject( e );
+ } );
+ } );
} );
- } );
+ } );
} );
} );
@@ -425,14 +534,17 @@ describe( 'ValidStateMonitor', function()
{
var fail = mkfail( 'foo', [ 'fail' ] );
- return expect(
- Sut()
- .update( {}, { foo: fail } )
- .then( sut =>
- {
- return sut.getFailures()
- } )
- ).to.eventually.deep.equal( { foo: fail } );
+ return mkstore( {} ).then( empty =>
+ {
+ return expect(
+ Sut()
+ .update( empty, { foo: fail } )
+ .then( sut =>
+ {
+ return sut.getFailures()
+ } )
+ ).to.eventually.deep.equal( { foo: fail } );
+ } );
} );
} );
@@ -448,14 +560,27 @@ describe( 'ValidStateMonitor', function()
it( 'is true when failures exist', function()
{
- return expect(
- Sut()
- .update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } )
- .then( sut =>
- {
- return sut.hasFailures();
- } )
- ).to.eventually.be.true;
+ return mkstore( {} ).then( empty =>
+ {
+ return expect(
+ Sut()
+ .update( empty, { foo: mkfail( 'foo', [ 'bar' ] ) } )
+ .then( sut =>
+ {
+ return sut.hasFailures();
+ } )
+ ).to.eventually.be.true;
+ } );
} );
} );
} );
+
+
+function mkstore( data )
+{
+ let store = MemoryStore();
+
+ return Promise.all(
+ Object.keys( data ).map( key => store.add( key, data[ key ] ) )
+ ).then( () => store );
+}