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-2296master
parent
203b25f10e
commit
2045c76f7e
|
@ -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 ] )
|
||||
)
|
||||
);
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -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();
|
||||
var causes = failure && failure.getCauses() || [];
|
||||
|
||||
for ( var cause_i in causes )
|
||||
// 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()
|
||||
{
|
||||
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 )
|
||||
// no fixes
|
||||
return false;
|
||||
} )
|
||||
.catch( function( result )
|
||||
{
|
||||
continue;
|
||||
if ( result instanceof Error )
|
||||
{
|
||||
throw result;
|
||||
}
|
||||
|
||||
// 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!)
|
||||
// looks like it has been resolved
|
||||
( 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;
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
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 ) )
|
||||
)
|
||||
{
|
||||
// looks like it has been resolved
|
||||
( fixed[ name ] = fixed[ name ] || [] )[ i ] =
|
||||
field[ cause_index ]
|
||||
|
||||
has_fixed = true;
|
||||
|
||||
delete past_fail[ i ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
found( field[ cause_index ] );
|
||||
return;
|
||||
}
|
||||
|
||||
// preparation for future use of Store, which is async
|
||||
return Promise.resolve( has_fixed );
|
||||
}
|
||||
// keep searching
|
||||
keepgoing( true );
|
||||
} )
|
||||
.catch( function( e )
|
||||
{
|
||||
// doesn't exist, so just keep searching (it
|
||||
// wasn't fixed)
|
||||
keepgoing( true );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
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' );
|
||||
|
@ -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' ]
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
@ -53,20 +56,26 @@ describe( 'ValidStateMonitor', function()
|
|||
describe( '#update', function()
|
||||
{
|
||||
it( 'does nothing with no data or failures', function()
|
||||
{
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'failure', nocall( 'failure' ) )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( {}, {} );
|
||||
.update( empty, {} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
it( 'does nothing with data but no failures', function()
|
||||
{
|
||||
return mkstore( { foo: mkfail( 'foo', [ 'bar' ] ) } ).then( store =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'failure', nocall( 'failure' ) )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( { foo: mkfail( 'foo', [ 'bar' ] ) }, {} );
|
||||
.update( store, {} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
|
@ -74,43 +83,61 @@ 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()
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return new Promise( accept =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'failure', function( failures )
|
||||
{
|
||||
expect( failures )
|
||||
.to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
|
||||
done();
|
||||
.to.deep.equal(
|
||||
{ foo: [ fail[ 0 ], fail[ 1 ] ] }
|
||||
);
|
||||
accept();
|
||||
} )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( {}, { foo: fail } );
|
||||
.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' ] );
|
||||
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return new Promise( accept =>
|
||||
{
|
||||
Sut()
|
||||
.on( 'failure', function( failures )
|
||||
{
|
||||
expect( failures )
|
||||
.to.deep.equal( { foo: [ undefined, fail[ 1 ] ] } );
|
||||
done();
|
||||
.to.deep.equal(
|
||||
{ foo: [ undefined, fail[ 1 ] ] }
|
||||
);
|
||||
accept();
|
||||
} )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( {}, { foo: fail } );
|
||||
.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' ] );
|
||||
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
var test_first = function( failures )
|
||||
{
|
||||
expect( failures )
|
||||
|
@ -124,38 +151,50 @@ describe( 'ValidStateMonitor', function()
|
|||
expect( failures )
|
||||
.to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
|
||||
|
||||
done();
|
||||
accept();
|
||||
};
|
||||
|
||||
sut
|
||||
mkstore( {} ).then( empty =>
|
||||
{
|
||||
return sut
|
||||
.once( 'failure', test_first )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( {}, { foo: [ undefined, fail[ 1 ] ] } )
|
||||
.update( empty, { foo: [ undefined, fail[ 1 ] ] } )
|
||||
.then( () =>
|
||||
{
|
||||
return sut.update( {}, { foo: [ fail[ 0 ] ] } );
|
||||
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()
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'failure', function( failures )
|
||||
{
|
||||
expect( failures )
|
||||
.to.deep.equal( { foo: fail } );
|
||||
done();
|
||||
|
||||
accept();
|
||||
} )
|
||||
.on( 'fix', nocall( 'fix' ) )
|
||||
.update( {}, { foo: fail } );
|
||||
.update( empty, { 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,29 +215,43 @@ 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 ] } )
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return sut
|
||||
.update( empty, { foo: [ fail1 ] } )
|
||||
.then( () =>
|
||||
{
|
||||
return sut.update( {}, { foo: [ fail2 ] } );
|
||||
return sut.update( empty, { foo: [ fail2 ] } );
|
||||
} )
|
||||
.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' ] } );
|
||||
expect( fixed ).to.deep.equal(
|
||||
{ foo: [ 'causefix1' ] }
|
||||
);
|
||||
|
||||
// and then we should have no failures
|
||||
expect( sut.hasFailures() ).to.be.false;
|
||||
expect( sut.hasFailures() )
|
||||
.to.be.false;
|
||||
|
||||
done();
|
||||
accept( true );
|
||||
} )
|
||||
.update(
|
||||
{ foo: [ 'moo' ], cause1: [ 'causefix1' ] },
|
||||
{}
|
||||
);
|
||||
.update( store, {} );
|
||||
} );
|
||||
} );
|
||||
} )
|
||||
.catch( e => reject( e ) );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
@ -206,40 +259,50 @@ describe( 'ValidStateMonitor', function()
|
|||
|
||||
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' ] );
|
||||
const fail = mkfail( 'foo', [ 'bar', 'baz' ] );
|
||||
const sut = Sut();
|
||||
|
||||
var sut = Sut();
|
||||
|
||||
sut
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( { foo: [ 'bardata', 'baz' ] } ).then( data =>
|
||||
{
|
||||
return sut
|
||||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { foo: [ 'bardata' ] } );
|
||||
done();
|
||||
accept();
|
||||
} )
|
||||
.update( data, { foo: [ fail[ 0 ], fail[ 1 ] ] } )
|
||||
.then( () =>
|
||||
{
|
||||
return sut.update( data, { foo: [ undefined, fail[ 1 ] ] } );
|
||||
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()
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( { bar: [ 'baz', 'quux' ] } ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { bar: [ 'baz', 'quux' ] } );
|
||||
done();
|
||||
accept();
|
||||
} )
|
||||
.update( data, {
|
||||
foo: fail_foo, // does not exist in data
|
||||
|
@ -249,6 +312,9 @@ describe( 'ValidStateMonitor', function()
|
|||
{
|
||||
return sut.update( data, {} );
|
||||
} );
|
||||
} )
|
||||
.catch( e => reject( e ) );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
|
@ -256,116 +322,143 @@ describe( 'ValidStateMonitor', function()
|
|||
{
|
||||
var called = 0;
|
||||
|
||||
Sut()
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'failure', function()
|
||||
{
|
||||
called++;
|
||||
} )
|
||||
.update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } )
|
||||
.update( empty, { foo: mkfail( 'foo', [ 'bar' ] ) } )
|
||||
.then( sut =>
|
||||
{
|
||||
return sut.update( {}, {} ); // do not trigger failure event
|
||||
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()
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( { cause: [ 'bar' ] } ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { foo: [ 'bar' ] } );
|
||||
|
||||
done();
|
||||
accept();
|
||||
} )
|
||||
.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()
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( update_data ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { foo: [ 'bar' ] } );
|
||||
|
||||
done();
|
||||
accept();
|
||||
} )
|
||||
.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(
|
||||
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 ]
|
||||
);
|
||||
|
||||
Sut()
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
return mkstore( update_data ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { foo: [ 'bar' ] } );
|
||||
|
||||
done();
|
||||
accept();
|
||||
} )
|
||||
.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(
|
||||
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 ]
|
||||
);
|
||||
|
||||
Sut()
|
||||
return mkstore( update_data ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.on( 'fix', nocall )
|
||||
.update( data, { foo: [ fail ] } )
|
||||
.then( sut =>
|
||||
|
@ -375,20 +468,24 @@ describe( 'ValidStateMonitor', function()
|
|||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
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' ] );
|
||||
var fail_foo = mkfail( 'foo', [ 'bar' ] );
|
||||
|
||||
Sut()
|
||||
return mkstore( { bar: [ 'baz', 'quux' ] } ).then( data =>
|
||||
{
|
||||
return Sut()
|
||||
.update( data, {
|
||||
bar: mkfail( 'bar', [ 'moo', 'cow' ] ) // fail
|
||||
} )
|
||||
.then( sut =>
|
||||
{
|
||||
return sut.on( 'failure', function( failed )
|
||||
return new Promise( ( accept, reject ) =>
|
||||
{
|
||||
sut.on( 'failure', function( failed )
|
||||
{
|
||||
expect( failed )
|
||||
.to.deep.equal( {
|
||||
|
@ -398,12 +495,24 @@ describe( 'ValidStateMonitor', function()
|
|||
.on( 'fix', function( fixed )
|
||||
{
|
||||
expect( fixed )
|
||||
.to.deep.equal( { bar: [ 'baz', 'quux' ] } );
|
||||
done();
|
||||
.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,9 +534,11 @@ describe( 'ValidStateMonitor', function()
|
|||
{
|
||||
var fail = mkfail( 'foo', [ 'fail' ] );
|
||||
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return expect(
|
||||
Sut()
|
||||
.update( {}, { foo: fail } )
|
||||
.update( empty, { foo: fail } )
|
||||
.then( sut =>
|
||||
{
|
||||
return sut.getFailures()
|
||||
|
@ -435,6 +546,7 @@ describe( 'ValidStateMonitor', function()
|
|||
).to.eventually.deep.equal( { foo: fail } );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
describe( '#hasFailures', function()
|
||||
|
@ -447,10 +559,12 @@ describe( 'ValidStateMonitor', function()
|
|||
|
||||
|
||||
it( 'is true when failures exist', function()
|
||||
{
|
||||
return mkstore( {} ).then( empty =>
|
||||
{
|
||||
return expect(
|
||||
Sut()
|
||||
.update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } )
|
||||
.update( empty, { foo: mkfail( 'foo', [ 'bar' ] ) } )
|
||||
.then( sut =>
|
||||
{
|
||||
return sut.hasFailures();
|
||||
|
@ -458,4 +572,15 @@ describe( 'ValidStateMonitor', function()
|
|||
).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 );
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue