DataValidator: Queue validations when incomplete
* src/validate/DataValidator.js (validate): If a validation is ongoing, queue requests. * test/validate/DataValidatorTest.js: Add test. DEV-2299master
parent
9225924e7e
commit
71024bd389
|
@ -61,6 +61,12 @@ module.exports = Class( 'DataValidator',
|
||||||
*/
|
*/
|
||||||
'private _stores': {},
|
'private _stores': {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending validation
|
||||||
|
* @type {Promise}
|
||||||
|
*/
|
||||||
|
'private _pending': null,
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize validator
|
* Initialize validator
|
||||||
|
@ -103,6 +109,10 @@ module.exports = Class( 'DataValidator',
|
||||||
/**
|
/**
|
||||||
* Validate diff and update field monitor
|
* Validate diff and update field monitor
|
||||||
*
|
*
|
||||||
|
* If a validation is pending completion, all further `#validate`
|
||||||
|
* requests will be queued to prevent unexpected/inconsistent system
|
||||||
|
* states and race conditions.
|
||||||
|
*
|
||||||
* The external validator `validatef` is a kluge while the system
|
* The external validator `validatef` is a kluge while the system
|
||||||
* undergoes refactoring.
|
* undergoes refactoring.
|
||||||
*
|
*
|
||||||
|
@ -116,6 +126,16 @@ module.exports = Class( 'DataValidator',
|
||||||
{
|
{
|
||||||
const _self = this;
|
const _self = this;
|
||||||
|
|
||||||
|
// queue requests if we're already processing
|
||||||
|
if ( this._pending )
|
||||||
|
{
|
||||||
|
this._pending.then(
|
||||||
|
() => this.validate( diff, classes, validatef )
|
||||||
|
);
|
||||||
|
|
||||||
|
return this._pending;
|
||||||
|
}
|
||||||
|
|
||||||
let failures = {};
|
let failures = {};
|
||||||
|
|
||||||
if ( diff !== undefined )
|
if ( diff !== undefined )
|
||||||
|
@ -132,8 +152,12 @@ module.exports = Class( 'DataValidator',
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this assumes that the above is synchronous
|
// XXX: this assumes that the above is synchronous
|
||||||
return this._populateStore( classes, this._stores.cstore, 'indexes' )
|
return this._pending =
|
||||||
.then( () => this.updateFailures( diff, failures ) );
|
this._populateStore(
|
||||||
|
classes, this._stores.cstore, 'indexes'
|
||||||
|
)
|
||||||
|
.then( () => this.updateFailures( diff, failures ) )
|
||||||
|
.then( () => this._pending = null );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,71 @@ describe( 'DataValidator', () =>
|
||||||
expect( cleared.b && cleared.c ).to.be.true
|
expect( cleared.b && cleared.c ).to.be.true
|
||||||
);
|
);
|
||||||
} ) );
|
} ) );
|
||||||
|
|
||||||
|
|
||||||
|
// otherwise system might get into an unexpected state
|
||||||
|
it( 'queues concurrent validations', () =>
|
||||||
|
{
|
||||||
|
const expected_failure = {};
|
||||||
|
|
||||||
|
let vcalled = 0;
|
||||||
|
|
||||||
|
const bvalidator = createMockBucketValidator( function( _, __, ___ )
|
||||||
|
{
|
||||||
|
vcalled++;
|
||||||
|
} );
|
||||||
|
|
||||||
|
const vmonitor = sinon.createStubInstance( ValidStateMonitor );
|
||||||
|
const dep_factory = createMockDependencyFactory();
|
||||||
|
const getStore = createStubStore();
|
||||||
|
|
||||||
|
const diff_a = { foo: [ 'a', 'b', 'c' ] };
|
||||||
|
const diff_b = { foo: [ 'd' ] };
|
||||||
|
|
||||||
|
const validatef = ( diff, failures ) =>
|
||||||
|
{
|
||||||
|
// not a real failure; just used to transfer state to stub
|
||||||
|
// (see below)
|
||||||
|
failures.failedon = diff;
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise( ( accept, reject ) =>
|
||||||
|
{
|
||||||
|
// by the time it gets to this the second time, store could
|
||||||
|
// be in any sort of state depending on what callbacks were
|
||||||
|
// invoked first (implementation details)
|
||||||
|
vmonitor.update = ( _, failures ) =>
|
||||||
|
{
|
||||||
|
const orig_diff = failures.failedon;
|
||||||
|
|
||||||
|
// if the external validator was called twice, then they
|
||||||
|
// didn't wait for us to finish
|
||||||
|
if ( ( orig_diff === diff_a ) && ( vcalled !== 1 ) )
|
||||||
|
{
|
||||||
|
reject( Error( "Request not queued" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this key doesn't exist, then the store has been
|
||||||
|
// cleared (which happens before it's re-populated with
|
||||||
|
// the new diff)
|
||||||
|
return expect( getStore().bstore.get( 'foo' ) )
|
||||||
|
.to.eventually.deep.equal( orig_diff.foo )
|
||||||
|
.then( () => {
|
||||||
|
// the second test, after which we're done
|
||||||
|
if ( orig_diff === diff_b )
|
||||||
|
{
|
||||||
|
accept();
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
.catch( e => reject( e ) );
|
||||||
|
};
|
||||||
|
|
||||||
|
const sut = Sut( bvalidator, vmonitor, dep_factory, getStore );
|
||||||
|
|
||||||
|
sut.validate( diff_a, {}, validatef );
|
||||||
|
sut.validate( diff_b, {}, validatef );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue