diff --git a/doc/server.texi b/doc/server.texi index 10a3229..8463adf 100644 --- a/doc/server.texi +++ b/doc/server.texi @@ -13,7 +13,9 @@ @maintenance{The @srcrefjs{server/daemon,Daemon} monolith and @srcrefjs{server,Server}, among other things, - need refactoring.} + need refactoring. + Quote initialization code should be moved into + @srcrefjs{server,ProgramInit}.} @helpwanted{} diff --git a/src/program/ProgramInit.js b/src/program/ProgramInit.js new file mode 100644 index 0000000..e363c9f --- /dev/null +++ b/src/program/ProgramInit.js @@ -0,0 +1,69 @@ +/** + * Initialize document data for a given Program + * + * Copyright (C) 2017 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 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' ); + + +/** + * Initialize document bucket data for given Programs + * + * A default bucket is initialized considering certain aspects of a given + * Program (see `#init`). + * + * TODO: This should really contain _all_ of the init code, extracted from + * Server, but time did not permit. Refactoring can continue at a later date. + */ +module.exports = Class( 'ProgramInit', +{ + /** + * Initialize document bucket data for `program` + * + * The original object `doc_data` will be modified by reference and + * returned. If `doc_data` evaluates to `false`, an empty object will + * be returned. Any other input results in undefined behavior. + * + * Note: This implementation used to cache default bucket objects, but + * doing so risks causing subtle and nasty bugs if the system modifies + * the default bucket object somewhere down the line, thereby affecting + * all documents going forward. + * + * @param {Program} program source program + * @param {Object} doc_data existing document data, if any + * + * @return {Object} `doc_data` modified + */ + 'public init'( program, doc_data ) + { + const defaults = program.defaults || {}; + + // initialize to an array with a single element of the default value + return Promise.resolve( + Object.keys( defaults ).reduce( + ( data, key ) => ( data[ key ] === undefined ) + ? ( data[ key ] = [ defaults[ key ] ], data ) + : data, + doc_data || {} + ) + ); + }, +} ); diff --git a/src/server/DocumentServer.js b/src/server/DocumentServer.js index 7d450c9..7ed6279 100644 --- a/src/server/DocumentServer.js +++ b/src/server/DocumentServer.js @@ -32,6 +32,10 @@ const { DataApiManager, }, + program: { + ProgramInit, + }, + server: { Server, @@ -74,6 +78,7 @@ module.exports = Class( 'DocumentServer', ), DapiMetaSource( QuoteDataBucket ), StagingBucket - ) + ), + ProgramInit() ) ) } ); diff --git a/src/server/Server.js b/src/server/Server.js index 5b70bde..ae10d76 100644 --- a/src/server/Server.js +++ b/src/server/Server.js @@ -118,9 +118,15 @@ module.exports = Class( 'Server' ) */ 'private _dataProcessor': null, + /** + * Initializes program + * @type {ProgramInit} + */ + 'private _progInit': null, + 'public __construct': function( - response, dao, logger, encsvc, data_processor + response, dao, logger, encsvc, data_processor, init ) { if ( !Class.isA( DataProcessor, data_processor ) ) @@ -133,6 +139,7 @@ module.exports = Class( 'Server' ) this.logger = logger; this._encService = encsvc; this._dataProcessor = data_processor; + this._progInit = init; }, @@ -287,7 +294,10 @@ module.exports = Class( 'Server' ) } // we're good - init_finish( program ); + server._getDefaultBucket( program, quote_data ) + .then( default_bucket => + init_finish( program, default_bucket ) + ); }); }); } @@ -297,20 +307,21 @@ module.exports = Class( 'Server' ) server.getProgram( quote_data.programId ) .then( function( quote_program ) { - init_finish( quote_program ); + server._getDefaultBucket( quote_program, quote_data ) + .then( default_bucket => + init_finish( quote_program, default_bucket ) + ); } ); } - function init_finish( quote_program ) + function init_finish( quote_program, default_bucket ) { // fill in the quote data (with reasonable defaults if the quote // does not yet exist); IMPORTANT: do not set pver to the // current version here; the quote will be repaired if it is not // set quote - .setData( - server._getDefaultBucket( quote_program, quote_data ) - ) + .setData( default_bucket ) .setMetadata( quote_data.meta || {} ) .setQuickSaveData( quote_data.quicksave || {} ) .setAgentId( quote_data.agentId || agent_id ) @@ -523,41 +534,7 @@ module.exports = Class( 'Server' ) */ 'private _getDefaultBucket': function( program, quote_data ) { - var defaults = program.defaults, - bucket = quote_data.data || {}, - pre = this._defaultBuckets[ program.getId() ]; - - // we only want to merge in the defaults if this is the first visit to - // the quote - if ( quote_data.currentStepId > 0 ) - { - // todo: uncomment later; for now we want older quotes to still work - //return bucket; - } - - // if we already generated the default bucket data and have no - // quote-specific data, return it - if ( pre && ( quote_data.data === undefined ) ) - { - return pre; - } - - // generate - for ( item in program.defaults ) - { - if ( bucket[ item ] === undefined ) - { - bucket[ item ] = [ defaults[ item ] ]; - } - } - - // set as default bucket only if we didn't merge - if ( quote_data.data === undefined ) - { - this._defaultBuckets[ program.getId() ] = bucket; - } - - return bucket; + return this._progInit.init( program, quote_data.data ); }, diff --git a/test/program/ProgramInitTest.js b/test/program/ProgramInitTest.js new file mode 100644 index 0000000..581a5c4 --- /dev/null +++ b/test/program/ProgramInitTest.js @@ -0,0 +1,95 @@ +/** + * Tests ProgramInit + * + * Copyright (C) 2017 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 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 chai = require( 'chai' ); +const expect = chai.expect; +const { ProgramInit: Sut } = require( '../../' ).program; + +chai.use( require( 'chai-as-promised' ) ); + + +describe( 'ProgramInit', () => +{ + [ + { + label: "initializes defaults", + defaults: { a: "one", b: "two" }, + doc_data: {}, + expected: { + a: [ "one" ], + b: [ "two" ], + }, + }, + { + label: "does nothing with no data or defaults", + defaults: {}, + doc_data: {}, + expected: {}, + }, + { + label: "produces empty object given undefined data", + defaults: {}, + doc_data: undefined, + expected: {}, + }, + { + label: "keeps existing data with defaults", + defaults: { foo: "init" }, + doc_data: { bar: [ "baz" ] }, + expected: { + foo: [ "init" ], + bar: [ "baz" ], + }, + }, + { + label: "keeps existing doc data with no defaults", + defaults: {}, + doc_data: { foo: [ "bar" ] }, + expected: { + foo: [ "bar" ], + }, + }, + { + label: "does not overwrite existing data with defaults", + defaults: { foo: "init" }, + doc_data: { foo: [ "bar" ] }, + expected: { + foo: [ "bar" ], + }, + }, + ].forEach( ({ label, doc_data, id, defaults, expected }) => + { + it( label, () => + { + const sut = Sut( null ); + + const program = { + id: "foo", + defaults: defaults, + }; + + return expect( sut.init( program, doc_data ) ) + .to.eventually.deep.equal( expected ); + } ); + } ); +} );