/** * Manages DataAPI requests and return data * * Copyright (C) 2010-2019 R-T Specialty, LLC. * * This file is part of the Liza Data Collection Framework. * * liza is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { DataProcessor as Sut } from "../../../src/server/request/DataProcessor"; import { expect, use as chai_use } from 'chai'; import { DocumentId } from "../../../src/document/Document"; import { PositiveInteger } from "../../../src/numeric"; import { UserRequest } from "../../../src/server/request/UserRequest"; import { ServerSideQuote } from "../../../src/server/quote/ServerSideQuote"; import { QuoteDataBucket } from "../../../src/bucket/QuoteDataBucket"; chai_use( require( 'chai-as-promised' ) ); describe( 'DataProcessor', () => { [ { label: "strips internal field data when not internal", data: { internal: [ "foo", "bar" ], foo: [ "bar", "baz" ], }, internals: { internal: true }, internal: false, expected: { foo: [ "bar", "baz" ], }, }, { label: "keeps internal field data when internal", data: { internal: [ "foo", "bar" ], foo: [ "bar", "baz" ], }, internals: { internal: true }, internal: true, expected: { internal: [ "foo", "bar" ], foo: [ "bar", "baz" ], }, }, ].forEach( ( { label, internal, data, internals, expected } ) => { const { request, program, sut, quote } = createSutFromStubs( internal, internals ); const bucket = createStubBucket( data ); it( label, () => { expect( sut.processDiff( data, request, program, bucket, quote ).filtered ).to.deep.equal( expected ); } ); } ); [ { label: "Original data is saved to the delta, not new data", old_data: { foo: [ "bar_old", "baz" ], }, new_data: { foo: [ "bar_new", "baz" ], }, expected_data: { foo: [ "bar_old", "baz" ], }, }, ].forEach( ( { label, old_data, new_data, expected_data } ) => { const { request, program, quote, filter, dapi_constructor, meta_source } = createStubs(); const sut = new Sut( filter, dapi_constructor, meta_source, createStubStagingBucket ); const bucket = createStubBucket( old_data ); it( label, () => { const actual = sut.processDiff( new_data, request, program, bucket, quote, ); expect( actual.rdiff ).to.deep.equal( expected_data ); } ); } ); it( "#processDiff.rdelta_data is undefined with empty staging diff", () => { const { request, program, quote, filter, dapi_constructor, meta_source } = createStubs(); const sut = new Sut( filter, dapi_constructor, meta_source, createStubStagingBucket ); const data = { foo: [ "bar", "baz" ], }; const diff = {}; const bucket = createStubBucket( data ); const actual = sut.processDiff( diff, request, program, bucket, quote ); expect( actual.rdelta_data ).to.deep.equal( undefined ); } ); it( "passes data to bucket filter", () => { const { request, program, meta_source, dapi_constructor, quote, } = createStubs(); const data: { filtered?: boolean } = {}; const types = {}; program.meta.qtypes = types; const filter = { filter( given_data: Record, given_types: Record, given_ignore: any, given_null: boolean, ) { expect( given_data ).to.equal( data ); expect( given_types ).to.equal( types ); expect( given_null ).to.equal( true ); // not used expect( given_ignore ).to.deep.equal( {} ); data.filtered = true; return data; }, filterValues( values: string[], _filter: string, _permit_null: boolean, ) { return values; } }; const bucket = createStubBucket( data ); new Sut( filter, dapi_constructor, meta_source, createStubStagingBucket, ).processDiff( data, request, program, bucket, quote ); expect( data.filtered ).to.equal( true ); } ); it( "instantiates dapi manager using program and session", done => { const { filter, request, program, meta_source, quote } = createStubs(); let dapi_constructor = ( given_apis: any, given_request: UserRequest, _quote: ServerSideQuote ) => { expect( given_apis ).to.equal( program.apis ); expect( given_request ).to.equal( request ); done(); return createStubDataApiManager(); }; const bucket = createStubBucket( {} ); new Sut( filter, dapi_constructor, meta_source, createStubStagingBucket ).processDiff( {}, request, program, bucket, quote ); } ); it( "invokes dapi manager when monitored bucket value changes", () => { const triggered: { [key: string]: any[] } = {}; // g prefix = "given" const meta_source = { getFieldData( _gfield: any, gindex: PositiveInteger, _gdapim: any, gdapi: { name: string }, _gdata: any, ) { triggered[ gdapi.name ] = triggered[ gdapi.name ] || []; triggered[ gdapi.name ][ gindex ] = arguments; return Promise.resolve( true ); } } const dapi_manager = createStubDataApiManager(); const { request, program, filter, quote, } = createStubs( false ); const sut = new Sut( filter, () => dapi_manager, meta_source, createStubStagingBucket, ); program.meta.fields = { foo: { dapi: { name: 'dapi_foo', mapsrc: { ina: 'src', inb: 'src1' }, }, }, bar: { dapi: { name: 'dapi_bar', mapsrc: { ina: 'src1' }, }, }, baz: { dapi: { name: 'dapi_no_call', mapsrc: {}, }, }, }; program.mapis = { src: [ 'foo', 'bar' ], // change src1: [ 'foo' ], // change src2: [ 'baz' ], // do not change }; // data changed const data = { src: [ 'src0', 'src1' ], src1: [ undefined, 'src11' ], }; const bucket = createStubBucket( { src: [ 'bsrc0', 'bsrc1' ], src1: [ 'bsrc10', 'bsrc11' ], } ); const { dapis, meta_clear } = sut.processDiff( data, request, program, bucket, quote ); const expected: { [key: string]: any[] } = { dapi_foo: [ { name: 'foo', data: { ina: data.src[ 0 ], inb: bucket.data.src1[ 0 ], }, }, { name: 'foo', data: { ina: data.src[ 1 ], inb: data.src1[ 1 ], }, }, ], dapi_bar: [ undefined, { name: 'bar', data: { ina: data.src1[ 1 ], }, }, ], }; const expected_clear = { foo: [ "", "" ], bar: [ "", "" ], }; for ( let dapi_name in expected ) { let expected_call = expected[ dapi_name ]; for ( let i in expected_call ) { let chk = expected_call[ i ]; if ( chk === undefined ) { continue; } let [ gfield, gindex, gdapi_manager, gdapi, gdata ] = triggered[ dapi_name ][ i ]; expect( gfield ).to.equal( chk.name ); expect( gdapi.name ).to.equal( dapi_name ); expect( +gindex ).to.equal( +i ); expect( gdapi_manager ).to.equal( dapi_manager ); // see mapsrc expect( gdata ).to.deep.equal( chk.data ); } } expect( triggered.dapi_no_call ).to.equal( undefined ); expect( meta_clear ).to.deep.equal( expected_clear ); return Promise.all( dapis ); } ); it( "check _mapDapiData default values", () => { const triggered: { [key: string]: any[] }= {}; // g prefix = "given" const meta_source = { getFieldData( _gfield: any, gindex: any, _gdapim: any, gdapi: any, gdata: any, ) { triggered[ gdapi.name ] = triggered[ gdapi.name ] || []; triggered[ gdapi.name ][ gindex ] = arguments; expect( gdata ).to.deep.equal( { ina: '', inb: [] } ); return Promise.resolve( true ); } } const { request, program, filter, quote, } = createStubs( false ); const sut = new Sut( filter, createStubDataApiManager, meta_source, createStubStagingBucket ); program.meta.fields = { foo: { dapi: { name: 'dapi_foo', mapsrc: { ina: 'src', inb: 'src1' }, }, }, }; program.mapis = { src1: [ 'foo' ], // change }; // data changed const data = { src: [ 'src0', '' ], src1: [ undefined, '' ], }; const bucket = createStubBucket( { src: [ 'bsrc0', '' ], src1: [ 'bsrc10', undefined], } ); const { dapis } = sut.processDiff( data, request, program, bucket, quote ); return Promise.all( dapis ); } ); } ); function createSutFromStubs( internal: boolean = false, internals: { internal: boolean } = { internal: false }, ) { const { request, program, filter, meta_source, dapi_constructor, quote } = createStubs(internal, internals); return { request: request, program: program, filter: filter, meta_source: meta_source, quote: quote, sut: new Sut( filter, dapi_constructor, meta_source, createStubStagingBucket ), }; } function createStubs( internal: boolean = false, internals: { internal: boolean } = { internal: false }, ) { return { request: createStubUserRequest( internal ), program: createStubProgram( internals ), filter: createStubFilter(), dapi_constructor: createStubDataApiContructor(), meta_source: createStubDapiMetaSource(), quote: createStubQuote(), }; } function createStubUserRequest( internal: boolean ) { return { getSession: () => ( { isInternal: () => internal } ) }; } function createStubProgram( internals: { internal: boolean } ) { return { ineligibleLockCount: 0, internal: internals, meta: { arefs: {}, fields: {}, groups: {}, qdata: {}, qtypes: {}, }, mapis: {}, apis: {}, getId(){ return 'Foo'; }, initQuote() {}, }; } function createStubFilter() { return { filter( data: Record, _key_types: Record, _ignore_types: Record, _permit_null: boolean, ) { return data; }, filterValues( values: string[], _filter: string, _permit_null: boolean, ) { return values; } } } function createStubDataApiContructor() { return ( _apis: any, _request: UserRequest, _quote: ServerSideQuote ) => { return createStubDataApiManager(); }; } function createStubDataApiManager() { return { setApis( _apis: any ) { return this; }, getApiData( _api: string, _data: any, _callback: any, _name: string, _index: PositiveInteger, _bucket: any, _fc: any, ){ return this; }, getPendingApiCalls() { return {}; }, fieldStale( _field: string, _index: PositiveInteger, _stale?: boolean ) { return this; }, fieldNotReady( _id: any, _i: PositiveInteger, _bucket: any ) { return; }, processFieldApiCalls() { return this; }, setFieldData( _name: string, _index: PositiveInteger, _data: Record, _value: string, _label: string, _unchanged: boolean, ) { return this; }, triggerFieldUpdate( _name: string, _index: PositiveInteger, _value: string, _label: string, _unchanged: boolean, ) { return false; }, hasFieldData( _name: string, _index: PositiveInteger ) { return true; }, clearFieldData( _name: string, _index: PositiveInteger, _trigger_event: boolean, ) { return this; }, clearPendingApiCall( _id: string ) { return this; }, expandFieldData( _name: string, _index: PositiveInteger, _bucket: any, _map: any, _predictive: boolean, _diff: any, ) { return this; }, getDataExpansion( _name: string, _index: PositiveInteger, bucket: any, _map: any, _predictive: boolean, _diff: any, ) { return bucket; }, }; } function createStubQuote() { let quote_data: Record = {}; return { getRatedDate() { return 1572292453; }, setRatedDate( _timestamp: UnixTimestamp ) { return this; }, getProgram() { return createStubProgram( { internal: false } ); }, getProgramId() { return 'Bar'; }, getId() { return 123; }, getCurrentStepId() { return 1; }, setExplicitLock( _reason: string, _step: number ) { return this; }, setLastPremiumDate( _timestamp: UnixTimestamp ) { return this; }, getLastPremiumDate() { return 1572292453; }, setRateBucket( _bucket: any ) { return this; }, setRatingData( data: Record ) { quote_data = data; return this; }, getRatingData() { return quote_data; }, getBucket() { return new QuoteDataBucket(); }, getMetabucket(){ return new QuoteDataBucket(); }, getProgramVersion(){ return 'Foo'; }, getExplicitLockReason(){ return 'Reason'; }, getExplicitLockStep() { return 1; }, isImported() { return true; }, isBound() { return true; }, getTopVisitedStepId() { return 1; }, getTopSavedStepId() { return 1; }, }; } function createStubDapiMetaSource() { return { getFieldData( _field: string, _index: PositiveInteger, _dapi_manager: any, _dapi: any, _data: Record, ) { return new Promise( () => {} ); }, }; } function createStubBucket( data: Record ) { return { data: data, getDataByName( name: string ) { return data[ name ]; }, }; } function createStubStagingBucket( bucket: any ) { let bucket_data = {}; return { setCommittedValues( _data: Record ) { return this; }, forbidBypass() { return this; }, setValues( values: Record ) { bucket_data = values; return this; }, overwriteValues( _data: Record ) { return this; }, getDiff() { return bucket_data; }, getFilledDiff() { return bucket.data || { foo: 'Bar' }; }, revert( _evented?: boolean ) { return this; }, commit( _store?: { old: Record } ) { return this; }, clear() { return this; }, each( _callback: ( value: any, name: string ) => void ) { return this; }, getDataByName( name: string ) { return bucket.getDataByName( name ); }, getOriginalDataByName( name: string ) { return bucket.getDataByName( name ); }, getDataJson() { return 'Foo'; }, getData() { return [ ( _Foo123: string ) => 'Bar']; }, filter( _pred: ( name: string ) => boolean, _c: ( value: any, name: string ) => void ) { return this; }, hasIndex( _name: string, _i: PositiveInteger ) { return true; }, isDirty() { return false; }, }; }