diff --git a/src/server/request/DataProcessor.js b/src/server/request/DataProcessor.ts similarity index 63% rename from src/server/request/DataProcessor.js rename to src/server/request/DataProcessor.ts index 3d9bbf6..3d549e0 100644 --- a/src/server/request/DataProcessor.js +++ b/src/server/request/DataProcessor.ts @@ -18,13 +18,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -'use strict'; - -const { Class } = require( 'easejs' ); - -const { QuoteDataBucket, StagingBucket } = require( '../../' ).bucket; - +import { PositiveInteger } from "../../numeric"; +import { UserRequest } from "./UserRequest"; /** * Process data provided by the client @@ -32,45 +27,25 @@ const { QuoteDataBucket, StagingBucket } = require( '../../' ).bucket; * TOOD: This contains Data API and bucket merging logic that is better done * elsewhere. */ -module.exports = Class( 'DataProcessor', +export class DataProcessor { - /** - * Bucket filter - * @type {Object} - */ - 'private _filter': null, - - /** - * Construct Data API manager - * @type {function()} - */ - 'private _dapif': null, - - /** - * Metadata source - * @type {DapiMetaSource} - */ - 'private _metaSource': null, - - /** * Initialize processor * * The staging bucket constructor will be used to wrap the bucket for * diff-related operations. * - * @param {Object} filter bucket filter - * @param {function()} dapif data API constructor - * @param {DapiMetaSource} meta_source metadata source - * @param {function(Bucket)} staging_ctor staging bucket constructor + * @param filter - bucket filter + * @param dapif - data API constructor + * @param meta_source - metadata source + * @param staging_ctor - staging bucket constructor */ - constructor( filter, dapif, meta_source, staging_ctor ) - { - this._filter = filter; - this._dapif = dapif; - this._metaSource = meta_source; - this._stagingCtor = staging_ctor; - }, + constructor( + private readonly _filter: any, + private readonly _dapif: any, + private readonly _meta_source: any, + private readonly _stagingCtor: any, + ) {} /** @@ -81,37 +56,52 @@ module.exports = Class( 'DataProcessor', * bucket values, preventing users from using us as their own personal * database. * - * @param {Object} data bucket diff data - * @param {UserRequest} request submitting request - * @param {Program} program active program + * @param data - bucket diff data + * @param request - submitting request + * @param program - active program * - * @return {Object} processed diff + * @return processed diff */ - 'public processDiff'( data, request, program, bucket, quote ) + processDiff( + data: Record, + request: UserRequest, + program: any, + bucket: any, + quote: any, + ): Record { const filtered = this.sanitizeDiff( data, request, program ); const dapi_manager = this._dapif( program.apis, request, quote ); const staging = this._stagingCtor( bucket ); // forbidBypass will force diff generation on initQuote - staging.setValues( filtered, true ); + staging.setValues( filtered ); staging.forbidBypass(); program.initQuote( staging, true ); + const diff = staging.getDiff(); + const rdiff: Record = {}; + // array of promises for any dapi requests const [ dapis, meta_clear ] = this._triggerDapis( - dapi_manager, program, staging.getDiff(), staging + dapi_manager, program, diff, staging ); + for( let diff_key in diff ) + { + rdiff[ diff_key ] = staging.getOriginalDataByName( diff_key ); + } + staging.commit(); return { filtered: filtered, dapis: dapis, meta_clear: meta_clear, + rdiff: rdiff, }; - }, + } /** @@ -124,22 +114,25 @@ module.exports = Class( 'DataProcessor', * `permit_null` should be used only in the case of bucket diffs, which * contain nulls as terminators. * - * @param {Object} data client-provided data - * @param {UserRequest} request client request - * @param {Program} program active program + * @param data - client-provided data + * @param request - client request + * @param program - active program * - * @return {Object} filtered data + * @return filtered data */ - 'public sanitizeDiff'( data, request, program ) + sanitizeDiff( + data: Record, + request: UserRequest, + program: any, + ): Record { if ( !request.getSession().isInternal() ) { this._cleanInternals( data, program ); } - const types = program.meta.qtypes; - return this._filter.filter( data, types, {}, true ); - }, + return this._filter.filter( data, program.meta.qtypes, {}, true ); + } /** @@ -147,31 +140,37 @@ module.exports = Class( 'DataProcessor', * * Internal fields are defined by the program `program`. * - * @param {Object} data bucket diff data - * @param {Program} program active program - * - * @return {undefined} + * @param data - bucket diff data + * @param program - active program */ - 'private _cleanInternals'( data, program ) + private _cleanInternals( + data: Record, + program: any, + ): void { for ( let id in program.internal ) { delete data[ id ]; } - }, + } /** * Trigger metadata Data API requests * - * @param {DataApiManager} dapi_manager dapi manager - * @param {Program} program active program - * @param {Object} data client-provided data - * @param {Bucket} bucket active bucket + * @param dapi_manager - dapi manager + * @param program - active program + * @param data - client-provided data + * @param bucket - active bucket * - * @return {undefined} + * @return an array containing the dapis and cleared meta values */ - 'private _triggerDapis'( dapi_manager, program, data, bucket ) + private _triggerDapis( + dapi_manager: any, + program: any, + data: Record, + bucket: any, + ): [ any, Record ] { const { mapis = {}, @@ -188,8 +187,8 @@ module.exports = Class( 'DataProcessor', const { dapi } = fields[ field ]; const indexes = dapi_fields[ field ]; - return indexes.map( i => - this._metaSource.getFieldData( + return indexes.map( ( i: PositiveInteger ) => + this._meta_source.getFieldData( field, i, dapi_manager, @@ -200,7 +199,7 @@ module.exports = Class( 'DataProcessor', } ).reduce( ( result, x ) => result.concat( x ), [] ); return [ dapis, clear ]; - }, + } /** @@ -211,15 +210,18 @@ module.exports = Class( 'DataProcessor', * lookup, it wouldn't be desirable to use an old rate even though data * used to retrieve it has since changed. * - * @param {Object.} fields field names and array of indexes + * @param fields - field names and array of indexes * - * @return {undefined} + * @return cleared values */ - 'private _genClearMetaValues'( fields ) + private _genClearMetaValues( + fields: Record + ): Record { - return Object.keys( fields ).reduce( ( result, field ) => + return Object.keys( fields ).reduce( + ( result: Record, field: string ) => { - result[ field ] = fields[ field ].reduce( ( values, i ) => + result[ field ] = fields[ field ].reduce( ( values: any, i: any ) => { values[ i ] = ""; return values; @@ -227,21 +229,24 @@ module.exports = Class( 'DataProcessor', return result; }, {} ); - }, + } /** * Determine which fields require a Data API to be triggered * - * @param {Object} mapis metadata dapi descriptors - * @param {Object} data client-provided data + * @param mapis - metadata dapi descriptors + * @param data - client-provided data * - * @return {Object} fields with indexes in need of dapi calls + * @return fields with indexes in need of dapi calls */ - 'private _determineDapiFields'( mapis, data ) + private _determineDapiFields( + mapis: Record, + data: Record + ): Record { return Object.keys( mapis ).reduce( - ( result, src_field ) => + ( result: any, src_field: string ) => { const fdata = data[ src_field ]; @@ -253,7 +258,7 @@ module.exports = Class( 'DataProcessor', const fields = mapis[ src_field ]; // get each index that changed - fields.forEach( field => + fields.forEach( (field: string) => { result[ field ] = result[ field ] || []; @@ -272,25 +277,30 @@ module.exports = Class( 'DataProcessor', }, {} ); - }, + } /** * Map data from bucket to dapi inputs * - * @param {Object} dapi Data API descriptor - * @param {Bucket} bucket active (source) bucket - * @param {number} index field index - * @param {Object} diff_data client-provided data + * @param dapi - Data API descriptor + * @param bucket - active (source) bucket + * @param index - field index + * @param diff_data - client-provided data * - * @return {Object} key/value dapi input data + * @return key/value dapi input data */ - 'private _mapDapiData'( dapi, bucket, index, diff_data ) + private _mapDapiData( + dapi: any, + bucket: any, + index: PositiveInteger, + diff_data: Record, + ): Record { const { mapsrc } = dapi; return Object.keys( mapsrc ).reduce( - ( result, srcid ) => + ( result: any, srcid: any ) => { const bucketid = mapsrc[ srcid ]; @@ -314,5 +324,5 @@ module.exports = Class( 'DataProcessor', }, {} ); - }, -} ); + } +}; diff --git a/test/server/request/DataProcessorTest.js b/test/server/request/DataProcessorTest.js deleted file mode 100644 index 70db17e..0000000 --- a/test/server/request/DataProcessorTest.js +++ /dev/null @@ -1,417 +0,0 @@ -/** - * 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 . - */ - -'use strict'; - -const { Class } = require( 'easejs' ); -const { expect } = require( 'chai' ); -const Sut = require( '../../../' ).server.request.DataProcessor; - - -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 } = - createSutFromStubs( internal, internals ); - - it( label, () => - { - expect( - sut.processDiff( data, request, program ).filtered - ).to.deep.equal( expected ); - } ); - } ); - - - it( "passes data to bucket filter", () => - { - const { request, program, meta_source } = createStubs(); - const data = {}; - const types = {}; - - program.meta.qtypes = types; - - const filter = { - filter( given_data, given_types, given_ignore, given_null ) - { - 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; - } - }; - - Sut( filter, () => {}, meta_source, createStubStagingBucket ) - .processDiff( data, request, program ); - - expect( data.filtered ).to.equal( true ); - } ); - - - it( "instantiates dapi manager using program and session", done => - { - const { filter, request, program } = createStubs(); - - const dapi_factory = ( given_apis, given_request ) => - { - expect( given_apis ).to.equal( program.apis ); - expect( given_request ).to.equal( request ); - - done(); - }; - - Sut( filter, dapi_factory, null, createStubStagingBucket ) - .processDiff( {}, request, program ); - } ); - - - it( "invokes dapi manager when monitored bucket value changes", () => - { - const triggered = {}; - - // g prefix = "given" - const getFieldData = function( gfield, gindex, gdapim, gdapi, gdata) - { - triggered[ gdapi.name ] = triggered[ gdapi.name ] || []; - triggered[ gdapi.name ][ gindex ] = arguments; - - return Promise.resolve( true ); - }; - - const dapi_manager = {}; - - const { - request, - program, - filter, - meta_source, - } = createStubs( false, {}, getFieldData ); - - const sut = 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 - ); - - const expected = { - 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 = {}; - - // g prefix = "given" - const getFieldData = function( gfield, gindex, gdapim, gdapi, gdata) - { - triggered[ gdapi.name ] = triggered[ gdapi.name ] || []; - triggered[ gdapi.name ][ gindex ] = arguments; - - expect( gdata ).to.deep.equal( { ina: '', inb: [] } ); - - return Promise.resolve( true ); - }; - - const dapi_manager = {}; - - const { - request, - program, - filter, - meta_source, - } = createStubs( false, {}, getFieldData ); - - const sut = Sut( - filter, - () => dapi_manager, - 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 - ); - - return Promise.all( dapis ); - } ); -} ); - - -function createSutFromStubs( /* see createStubs */ ) -{ - const { request, program, filter, meta_source } = - createStubs.apply( null, arguments ); - - return { - request: request, - program: program, - filter: filter, - meta_source: meta_source, - - sut: Sut( - filter, - () => {}, - meta_source, - createStubStagingBucket - ), - }; -} - - -function createStubs( internal, internals, getFieldData ) -{ - return { - request: createStubUserRequest( internal || false ), - program: createStubProgram( internals || {} ), - filter: { filter: _ => _ }, - meta_source: createStubDapiMetaSource( getFieldData ), - }; -} - - -function createStubUserRequest( internal ) -{ - return { - getSession: () => ( { - isInternal: () => internal - } ) - }; -} - - -function createStubProgram( internals ) -{ - return { - internal: internals, - meta: { qtypes: {}, fields: {} }, - apis: {}, - - initQuote() {}, - }; -} - - -function createStubDapiMetaSource( getFieldData ) -{ - return { - getFieldData: getFieldData || - function( field, index, dapi_manager, dapi, data ){}, - }; -} - - -function createStubBucket( data ) -{ - return { - data: data, - - getDataByName( name ) - { - return data[ name ]; - }, - }; -} - - -function createStubStagingBucket( bucket ) -{ - let data = {}; - - return { - getDataByName( name ) - { - return bucket.getDataByName( name ); - }, - - setValues( values ) - { - data = values; - }, - - forbidBypass() {}, - getDiff() - { - return data; - }, - commit() {}, - }; -} diff --git a/test/server/request/DataProcessorTest.ts b/test/server/request/DataProcessorTest.ts new file mode 100644 index 0000000..faf69bf --- /dev/null +++ b/test/server/request/DataProcessorTest.ts @@ -0,0 +1,804 @@ +/** + * 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"; + +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: any ) + { + 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: any ) + { + return this; + }, + + getLastPremiumDate() + { + return 1572292453; + }, + + setRateBucket( _bucket: any ) + { + return this; + }, + + setRatingData( data: Record ) + { + quote_data = data; + + return this; + }, + + getRatingData() + { + return quote_data; + }, + + getBucket() + { + return; + } + }; +} + + +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; }, + }; +}