1
0
Fork 0

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
master
Mike Gerwitz 2017-01-26 16:03:00 -05:00
parent 203b25f10e
commit 2045c76f7e
4 changed files with 576 additions and 312 deletions

View File

@ -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 ] )
)
);
},
} );

View File

@ -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 )
{
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 ) )
// 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;
has_fixed = true;
delete past_fail[ fail_i ];
return true;
} );
} ) ).then( function( result ) {
return result.some( function( val )
{
return val === true;
} );
} );
},
delete past_fail[ i ];
break;
}
}
}
// preparation for future use of Store, which is async
return Promise.resolve( has_fixed );
}
/**
* 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 ) )
)
{
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 );
} );
} );
} );
},
} );

View File

@ -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 );
return Sut( bvalidator, vmonitor, dep_factory, getStore )
.validate( diff )
.then( () =>
{
mock_vmonitor.verify();
mock_dep_factory.verify();
// 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();
// 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;
}

View File

@ -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 ] ] } );
sut.once( 'failure', test_second );
};
var test_second = function( failures )
{
expect( failures )
.to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
done();
};
sut
.once( 'failure', test_first )
.on( 'fix', nocall( 'fix' ) )
.update( {}, { foo: [ undefined, fail[ 1 ] ] } )
.then( () =>
var test_first = function( failures )
{
return sut.update( {}, { foo: [ fail[ 0 ] ] } );
} );
expect( failures )
.to.deep.equal( { foo: [ undefined, fail[ 1 ] ] } );
sut.once( 'failure', test_second );
};
var test_second = function( failures )
{
expect( failures )
.to.deep.equal( { foo: [ fail[ 0 ], fail[ 1 ] ] } );
accept();
};
mkstore( {} ).then( empty =>
{
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' ] );
const fail = mkfail( 'foo', [ 'bar', 'baz' ] );
const sut = Sut();
var 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();
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, {} );
} );
} )
.update( data, {
foo: fail_foo, // does not exist in data
bar: fail_bar,
} )
.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' ] } );
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, {} );
} );
} )
.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' ] } );
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, {} );
} );
} )
.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 ]
);
const update_data = { cause_fix: [ undefined, 'bar' ] };
const field = Field( 'foo', 0 );
const cause1 = Field( 'cause_no', 1 );
const cause2 = Field( 'cause_fix', 1 );
Sut()
.on( 'fix', function( fixed )
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' ] } );
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, {} );
} );
} )
.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 ]
);
const update_data = mkstore( { noncause: [ undefined, 'bar' ] } );
const field = Field( 'foo', 0 );
const cause1 = Field( 'cause', 1 );
const cause2 = Field( 'cause', 2 );
Sut()
.on( 'fix', nocall )
.update( data, { foo: [ fail ] } )
.then( sut =>
{
return sut.update( data, {} );
} );
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' ] );
var 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,
} );
} )
.on( 'fix', function( fixed )
{
expect( fixed )
.to.deep.equal( { bar: [ 'baz', 'quux' ] } );
done();
} )
return mkstore( { bar: [ 'baz', 'quux' ] } ).then( data =>
{
return Sut()
.update( data, {
foo: fail_foo, // fail
// fixes bar
bar: mkfail( 'bar', [ 'moo', 'cow' ] ) // fail
} )
.then( sut =>
{
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 );
}