diff --git a/conf/vanilla-server.json b/conf/vanilla-server.json index 41e90b8..452dc4c 100644 --- a/conf/vanilla-server.json +++ b/conf/vanilla-server.json @@ -49,7 +49,8 @@ "remote": { "host": "localhost", "domain": "" - } + }, + "noResultsUrl": "" }, "c1export": { "host": "localhost", diff --git a/src/dapi/QuoteDataApi.js b/src/dapi/QuoteDataApi.js index 16e35b7..1c34eed 100644 --- a/src/dapi/QuoteDataApi.js +++ b/src/dapi/QuoteDataApi.js @@ -61,7 +61,7 @@ module.exports = Class( 'QuoteDataApi' ) if ( !( Class.isA( DataApi, dapi ) ) ) { throw TypeError( - 'Expected object of type DataApi; given: ' + data_api + 'Expected object of type DataApi; given: ' + dapi ); } diff --git a/src/dapi/http/HttpDataApi.js b/src/dapi/http/HttpDataApi.js index 0db81b1..cbb23e4 100644 --- a/src/dapi/http/HttpDataApi.js +++ b/src/dapi/http/HttpDataApi.js @@ -139,14 +139,33 @@ module.exports = Class( 'HttpDataApi' ) this._validateDataType( data ); - this._impl.requestData( - this._url, - this._method, + this.requestData( this._url, this._method, data, callback ); + + return this; + }, + + + /** + * Request data from underlying HttpImpl + * + * Subtypes may override this method to alter any aspect of the request + * before sending. + * + * @param {string} url destination URL + * @param {string} method RFC-2616-compliant HTTP method + * @param {Object|string} data request params + * @param {function(?Error, ?string)} callback server response callback + * + * @return {HttpDataApi} self + */ + 'virtual protected requestData'( url, method, data, callback ) + { + return this._impl.requestData( + url, + method, this.encodeData( data ), callback ); - - return this; }, diff --git a/src/dapi/http/HttpDataApiUrlData.js b/src/dapi/http/HttpDataApiUrlData.js new file mode 100644 index 0000000..52edfb9 --- /dev/null +++ b/src/dapi/http/HttpDataApiUrlData.js @@ -0,0 +1,127 @@ +/** + * Provide data as part of URL + * + * Copyright (C) 2018 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 { Trait } = require( 'easejs' ); +const HttpDataApi = require( './HttpDataApi' ); + + +/** + * Place fields from given data in the URL + * + * All remaining fields are passed to the underlying supertype. + */ +module.exports = Trait( 'HttpDataApiUrlData' ) + .extend( HttpDataApi, +{ + /** + * Fields to take from data and place in URL + * @type {string} + */ + 'private _fields': [], + + + /** + * Initialize with URL field list + * + * @param {Array} fields list of fields to include in URL + */ + __mixin( fields ) + { + this._fields = fields; + }, + + + /** + * Concatenate chosen fields with URL + * + * The previously specified fields will have their values delimited by '/' + * and will be concatenated with the URL. All used fields in DATA will be + * removed before being passed to the supertype. METHOD and CALLBACK are + * proxied as-is. + * + * @param {string} url destination URL + * @param {string} method RFC-2616-compliant HTTP method + * @param {Object|string} data request params + * @param {function(Error, Object)} callback server response callback + * + * @return {HttpImpl} self + */ + 'override public requestData'( url, method, data, callback ) + { + const [ values, filtered_data ] = this._getFieldValues( data ); + + const params = values.map( ( [ , value ] ) => value ); + const missing = values.filter( ( [ , value ] ) => value === undefined ); + + if ( missing.length > 0 ) + { + callback( + Error( + "Missing URL parameters: " + + missing.map( ( [ field ] ) => field ).join( ", " ) + ), + null + ); + + return this; + } + + const built_url = ( params.length > 0 ) + ? url + '/' + params.join( '/' ) + : url; + + return this.__super( built_url, method, filtered_data, callback ); + }, + + + /** + * Associate fields with their respective values from DATA + * + * The returned values are of the form `[ [ field, value ], ... ]`. + * The returned data object is a copy of the original and is stripped + * of the respective fields. + * + * @param {Object} data source data + * + * @return {Array} values and copy of data stripped of those fields + */ + 'private _getFieldValues'( data ) + { + const fieldset = new Set( this._fields ); + const values = this._fields.map( field => [ field, data[ field ] ] ); + + // copy of data with fields stripped + const new_data = Object.keys( data ).reduce( ( dest, key ) => + { + if ( fieldset.has( key ) ) + { + return dest; + } + + dest[ key ] = data[ key ]; + return dest; + }, {} ); + + return [ values, new_data ]; + }, +} ); diff --git a/src/dapi/http/NodeHttpImpl.js b/src/dapi/http/NodeHttpImpl.js index 35e687d..9f402f0 100644 --- a/src/dapi/http/NodeHttpImpl.js +++ b/src/dapi/http/NodeHttpImpl.js @@ -87,7 +87,7 @@ module.exports = Class( 'NodeHttpImpl' ) * * @return {HttpImpl} self */ - 'public requestData'( url, method, data, callback ) + 'virtual public requestData'( url, method, data, callback ) { const options = this._parseUrl( url ); const protocol = options.protocol.replace( /:$/, '' ); diff --git a/src/dapi/http/SpoofedNodeHttpImpl.js b/src/dapi/http/SpoofedNodeHttpImpl.js index ac68812..f8d05fb 100644 --- a/src/dapi/http/SpoofedNodeHttpImpl.js +++ b/src/dapi/http/SpoofedNodeHttpImpl.js @@ -37,7 +37,7 @@ module.exports = Trait( 'SpoofedNodeHttpImpl' ) { /** * Session to spoof - * @type {UserSession} + * @type {UserRequest} */ 'private _request': null, @@ -45,11 +45,11 @@ module.exports = Trait( 'SpoofedNodeHttpImpl' ) /** * Use session for spoofing requests * - * @param {UserSession} session session to spoof + * @param {UserRequest} request session to spoof */ - __mixin( session ) + __mixin( request ) { - this._request = session; + this._request = request; }, diff --git a/src/dapi/http/XhrHttpImpl.js b/src/dapi/http/XhrHttpImpl.js index 28eccac..b2e4d63 100644 --- a/src/dapi/http/XhrHttpImpl.js +++ b/src/dapi/http/XhrHttpImpl.js @@ -71,7 +71,7 @@ module.exports = Class( 'XhrHttpImpl' ) * * @return {HttpImpl} self */ - 'public requestData': function( url, method, data, callback ) + 'virtual public requestData': function( url, method, data, callback ) { if ( typeof data !== 'string' ) { diff --git a/src/server/daemon/Daemon.js b/src/server/daemon/Daemon.js index 282197f..7a13769 100644 --- a/src/server/daemon/Daemon.js +++ b/src/server/daemon/Daemon.js @@ -113,7 +113,8 @@ module.exports = AbstractClass( 'Daemon', this._createDebugLog(), this._createAccessLog(), this._conf.get( 'skey' ), - ] ).then( ([ debug_log, access_log, skey ]) => + this._conf.get( 'services.rating.noResultsUrl' ), + ] ).then( ([ debug_log, access_log, skey, no_results_url ]) => { this._debugLog = debug_log; this._accessLog = access_log; @@ -122,7 +123,7 @@ module.exports = AbstractClass( 'Daemon', this._rater = liza.server.rater.ProcessManager(); this._encService = this.getEncryptionService(); this._memcache = this.getMemcacheClient(); - this._routers = this.getRouters( skey ); + this._routers = this.getRouters( skey, no_results_url ); } ) .then( () => this._startDaemon() ); }, @@ -182,14 +183,16 @@ module.exports = AbstractClass( 'Daemon', }, - 'protected getProgramController': function( skey ) + 'protected getProgramController': function( skey, no_results_url ) { var controller = require( './controller' ); - controller.rater = this._rater; + + controller.rater = this._rater; + controller.no_results_url = no_results_url || ""; if ( skey ) { - controller.skey = skey; + controller.skey = skey; } return controller; @@ -276,10 +279,10 @@ module.exports = AbstractClass( 'Daemon', 'abstract protected getEncryptionService': [], - 'protected getRouters': function( skey ) + 'protected getRouters': function( skey, no_results_url ) { return [ - this.getProgramController( skey ), + this.getProgramController( skey, no_results_url ), this.getScriptsController(), this.getClientErrorController(), ]; diff --git a/src/server/daemon/controller.js b/src/server/daemon/controller.js index e6571f2..b8a0950 100644 --- a/src/server/daemon/controller.js +++ b/src/server/daemon/controller.js @@ -43,6 +43,12 @@ const { }, dapi: { + http: { + HttpDataApi, + HttpDataApiUrlData, + NodeHttpImpl, + SpoofedNodeHttpImpl, + }, DataApiFactory, DataApiManager, }, @@ -69,6 +75,7 @@ const { }, RatingService, + RatingServiceSubmitNotify, TokenedService, TokenDao, }, @@ -93,8 +100,9 @@ var sflag = {}; // TODO: kluge to get liza somewhat decoupled from lovullo (rating module) -exports.rater = {}; -exports.skey = ""; +exports.rater = {}; +exports.skey = ""; +exports.no_results_url = ""; exports.init = function( logger, enc_service, conf ) @@ -118,7 +126,31 @@ exports.init = function( logger, enc_service, conf ) server_cache = _createCache( server ); server.init( server_cache, exports.rater ); - rating_service = RatingService( logger, dao, server, exports.rater ); + // TODO: do none of this if no_results_url is provided + const createSubmitDapi = request => HttpDataApi + .use( HttpDataApiUrlData( [ 'quote_id' ] ) ) + ( + exports.no_results_url, + 'PUT', + + NodeHttpImpl + .use( SpoofedNodeHttpImpl( request ) ) + ( + { + http: require( 'http' ), + https: require( 'https' ), + }, + require( 'url' ), + this._origin + ), + '' + ); + + rating_service = RatingService + .use( RatingServiceSubmitNotify( createSubmitDapi, dao ) ) + ( + logger, dao, server, exports.rater + ); // TODO: exports.init needs to support callbacks; this will work, but // only because it's unlikely that we'll get a request within diff --git a/src/server/db/MongoServerDao.js b/src/server/db/MongoServerDao.js index 48d12df..20fe8be 100644 --- a/src/server/db/MongoServerDao.js +++ b/src/server/db/MongoServerDao.js @@ -783,5 +783,70 @@ module.exports = Class( 'MongoServerDao' ) } ); }, -} ); + + /** + * Set arbitrary data on a document + * + * @param {number} qid quote/document id + * @param {string} key field key + * @param {*} value field value + * @param {function(?Error)} callback completion callback + * + * @return {undefined} + */ + 'public setDocumentField'( qid, key, value, callback ) + { + this._collection.update( + { id: qid }, + { '$set': { [key]: value } }, + + // create record if it does not yet exist + { upsert: true }, + + // on complete + function( err ) + { + callback && callback( err ); + return; + } + ); + }, + + + /** + * Retrieve arbitrary data on a document + * + * @param {number} qid quote/document id + * @param {string} key field key + * @param {function(?Error)} callback completion callback + * + * @return {undefined} + */ + 'public getDocumentField'( qid, key, callback ) + { + this._collection.find( + { id: qid }, + { limit: 1 }, + function( err, cursor ) + { + if ( err !== null ) + { + callback( err, null ); + return; + } + + cursor.toArray( function( err, data ) + { + if ( err !== null ) + { + callback( err, null ); + return; + } + + callback( null, data[ key ] ); + } ); + } + ); + }, +} ); diff --git a/src/server/service/RatingService.js b/src/server/service/RatingService.js index 4df89fb..15fcbb0 100644 --- a/src/server/service/RatingService.js +++ b/src/server/service/RatingService.js @@ -154,8 +154,8 @@ module.exports = Class( 'RatingService', { actions = actions || []; - _self._postProcessRaterData( - rate_data, actions, program, quote + _self.postProcessRaterData( + request, rate_data, actions, program, quote ); const class_dest = {}; @@ -244,14 +244,17 @@ module.exports = Class( 'RatingService', /** * Process rater data returned from a rater * - * @param {Object} data rating data returned - * @param {Array} actions actions to send to client - * @param {Program} program program used to perform rating - * @param {Quote} quote quote used for rating + * @param {UserRequest} request user request to satisfy + * @param {Object} data rating data returned + * @param {Array} actions actions to send to client + * @param {Program} program program used to perform rating + * @param {Quote} quote quote used for rating * * @return {undefined} */ - _postProcessRaterData: function( data, actions, program, quote ) + 'virtual protected postProcessRaterData': function( + request, data, actions, program, quote + ) { var meta = data._cmpdata || {}; diff --git a/src/server/service/RatingServiceSubmitNotify.js b/src/server/service/RatingServiceSubmitNotify.js new file mode 100644 index 0000000..c333561 --- /dev/null +++ b/src/server/service/RatingServiceSubmitNotify.js @@ -0,0 +1,140 @@ +/** + * Notification on all submit + * + * Copyright (C) 2018 R-T Specialty, LLC. + * + * This file is part of liza. + * + * 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 { Trait } = require( 'easejs' ); +const DslRaterContext = require( '../rater/DslRaterContext' ) +const RatingService = require( './RatingService' ); + + +/** + * Triggers DataApi when no results are available + * + * This information is currently stored in `__prem_avail_count`. In the + * future, it may be worth accepting a parameter to configure this at + * runtime. + * + * Notification status will persist using the provided DAO. The next time + * such a notification is requested, it will only occur if the flag is not + * set. + */ +module.exports = Trait( 'RatingServiceSubmitNotify' ) + .extend( RatingService, +{ + /** + * Function returning DataApi to trigger + * @type {Function(UserSession):DataApi} + */ + 'private _dapif': null, + + /** + * Data store for notification flag + * @type {ServerDao} + */ + 'private _notifyDao': null, + + + /** + * Initialize mixin with DataApi to trigger + * + * @param {Function(UserSession):DataApi} dapif Function producing DataApi + * @param {ServerDao} dao store for notification flag + */ + __mixin( dapif, dao ) + { + this._dapif = dapif; + this._notifyDao = dao; + }, + + + /** + * Trigger previously provided DataApi when no results are available + * + * Result count is determined by DATA.__prem_avail_count. + * + * @param {UserRequest} request user request + * @param {Object} data rating data returned + * @param {Array} actions actions to send to client + * @param {Program} program program used to perform rating + * @param {Quote} quote quote used for rating + * + * @return {undefined} + */ + 'override protected postProcessRaterData'( + request, data, actions, program, quote + ) + { + const quote_id = quote.getId(); + const avail = ( data.__prem_avail_count || [ 0 ] )[ 0 ]; + + if ( avail === 0 ) + { + this._getNotifyState( quote_id, notified => + { + if ( notified === true ) + { + return; + } + + this._dapif( request ) + .request( { quote_id: quote_id }, () => {} ); + + this._setNotified( quote_id ); + } ); + } + + this.__super( request, data, actions, program, quote ); + }, + + + /** + * Get value of notification flag + * + * @param {number} quote_id id of quote + * @param {function(boolean)} callback callback to call when complete + * + * @return {undefined} + */ + 'private _getNotifyState'( quote_id, callback ) + { + this._notifyDao.getDocumentField( + quote_id, + 'submitNotified', + ( err, value ) => callback( value ) + ); + }, + + + /** + * Set notification flag + * + * @param {number} quote_id id of quote + * + * @return {undefined} + */ + 'private _setNotified'( quote_id ) + { + this._notifyDao.setDocumentField( + quote_id, 'submitNotified', true + ); + }, +} ); diff --git a/src/test/server/service/RatingServiceStub.js b/src/test/server/service/RatingServiceStub.js new file mode 100644 index 0000000..c58d1cf --- /dev/null +++ b/src/test/server/service/RatingServiceStub.js @@ -0,0 +1,87 @@ +/** + * Tests RatingService + * + * Copyright (C) 2018 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 Affero General Public License + * along with this program. If not, see . + */ + +'use strict' + + +exports.getStubs = function() +{ + const program_id = 'foo'; + const program = { + getId: () => program_id, + }; + + // rate reply + const stub_rate_data = {}; + + const rater = { + rate: ( quote, session, indv, callback ) => callback( stub_rate_data ), + }; + + const raters = { + byId: () => rater, + }; + + const logger = { + log: () => {}, + }; + + const server = { + sendResponse: () => {}, + sendError: () => {}, + }; + + const dao = { + mergeBucket: () => {}, + saveQuoteClasses: () => {}, + setWorksheets: () => {}, + }; + + const session = { + isInternal: () => false, + }; + + const request = { + getSession: () => session, + getSessionIdName: () => {}, + }; + const response = {}; + + const quote = { + getProgramId: () => program_id, + getProgram: () => program, + getId: () => 0, + }; + + return { + program: program, + stub_rate_data: stub_rate_data, + rater: rater, + raters: raters, + logger: logger, + server: server, + dao: dao, + session: session, + request: request, + response: response, + quote: quote, + }; +}; diff --git a/test/dapi/http/HttpDataApiUrlDataTest.js b/test/dapi/http/HttpDataApiUrlDataTest.js new file mode 100644 index 0000000..31673eb --- /dev/null +++ b/test/dapi/http/HttpDataApiUrlDataTest.js @@ -0,0 +1,123 @@ +/** + * Tests HttpDataApiUrlData + * + * Copyright (C) 2018 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 Affero General Public License + * along with this program. If not, see . + */ + +'use strict' + +const { Class } = require( 'easejs' ); +const { expect } = require( 'chai' ); + +const { + dapi: { + http: { + HttpDataApi, + HttpImpl, + HttpDataApiUrlData: Sut, + }, + }, +} = require( '../../../' ); + + +describe( 'HttpDataApiUrlData', () => +{ + [ + { + fields: [], + data: { + foo: "foodata", + bar: "bardata", + }, + base_url: "base", + after_data: "foo=foodata&bar=bardata", + expected: "base", + }, + { + fields: [ 'foo', 'bar' ], + data: { + foo: "foodata", + bar: "bardata", + baz: "shouldnotuse", + }, + base_url: "base2", + after_data: "baz=shouldnotuse", + expected: "base2/foodata/bardata", + }, + ].forEach( ( { fields, data, base_url, after_data, expected }, i ) => + it( `can include portion of data in url (#${i})`, done => + { + const expected_method = 'PUT'; + + const impl = Class.implement( HttpImpl ).extend( + { + 'virtual requestData'( url, method, given_data, callback ) + { + expect( url ).to.equal( expected ); + expect( method ).to.equal( expected_method ); + expect( given_data ).to.deep.equal( after_data ); + + // should not have mutated the original object + // (they shouldn't be the same object) + expect( data ).to.not.equal( given_data ); + + // should be done + callback(); + }, + + setOptions: () => {}, + } )(); + + const sut = HttpDataApi.use( Sut( fields ) )( + base_url, expected_method, impl + ); + + const result = sut.request( data, done ); + } ) + ); + + + it( "throws error if param is missing from data", done => + { + const impl = Class.implement( HttpImpl ).extend( + { + 'virtual requestData': ( url, method, data, callback ) => {}, + setOptions: () => {}, + } )(); + + const sut = HttpDataApi.use( Sut( [ 'foo' ] ) )( + '', 'PUT', impl + ); + + // missing `foo` in data + sut.request( { unused: "missing foo" }, ( err, data ) => + { + expect( err ).to.be.instanceof( Error ); + expect( err.message ).to.match( /\bfoo\b/ ); + + expect( data ).to.equal( null ); + + done(); + } ); + } ); +} ); + + + + + diff --git a/test/server/service/RatingServiceSubmitNotifyTest.js b/test/server/service/RatingServiceSubmitNotifyTest.js new file mode 100644 index 0000000..b4f5922 --- /dev/null +++ b/test/server/service/RatingServiceSubmitNotifyTest.js @@ -0,0 +1,164 @@ +/** + * Tests RatingServiceSubmitNotify + * + * Copyright (C) 2018 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 Affero General Public License + * along with this program. If not, see . + */ + +'use strict' + +const { Class } = require( 'easejs' ); +const { expect } = require( 'chai' ); + + +const { + dapi: { + DataApi, + }, + server: { + service: { + RatingServiceSubmitNotify: Sut, + RatingService, + }, + }, + test: { + server: { + service: { + RatingServiceStub, + }, + }, + }, +} = require( '../../../' ); + + +describe( 'RatingServiceSubmitNotify', () => +{ + [ + { + prem_avail_count: [ 0 ], + prev_called: false, + expected_request: true, + }, + { + prem_avail_count: [ 2 ], + prev_called: false, + expected_request: false, + }, + { + // this shouldn't happen; ignore all but first index + prem_avail_count: [ 2, 2 ], + prev_called: false, + expected_request: false, + }, + + // save as above, but already saved + { + prem_avail_count: [ 0 ], + prev_called: true, + expected_request: false, + }, + { + prem_avail_count: [ 2 ], + prev_called: true, + expected_request: false, + }, + { + // this shouldn't happen; ignore all but first index + prem_avail_count: [ 2, 2 ], + prev_called: true, + expected_request: false, + }, + ].forEach( ( { prem_avail_count, expected_request, prev_called }, i ) => + it( `sends request on post process if no premiums (#${i})`, done => + { + const { + dao, + logger, + quote, + raters, + request, + response, + server, + stub_rate_data, + } = RatingServiceStub.getStubs(); + + const quote_id = 1234; + let requested = false; + + const dapif = given_request => + Class.implement( DataApi ).extend( + { + // warning: if an expectation fails, because of how + // RatingService handles errors, it will cause the test to + // _hang_ rather than throw the assertion error + request( data, callback ) + { + expect( given_request ).to.equal( request ); + expect( data ).to.deep.equal( { quote_id: quote_id } ); + + requested = true; + }, + } )(); + + const sut = RatingService.use( Sut( dapif, dao ) )( + logger, dao, server, raters + ); + + quote.getId = () => quote_id; + + // one of the methods that is called by the supertype + let save_called = false; + dao.setWorksheets = () => save_called = true; + + // whether the notify flag is actually set + let notify_saved = false; + + // request for notification status + dao.getDocumentField = ( qid, key, callback ) => + { + expect( qid ).to.equal( quote_id ); + expect( key ).to.equal( 'submitNotified' ); + + callback( null, prev_called ); + }; + + dao.setDocumentField = ( qid, key, value, callback ) => + { + expect( qid ).to.equal( quote_id ); + expect( key ).to.equal( 'submitNotified' ); + expect( value ).to.equal( true ); + + notify_saved = true; + }; + + stub_rate_data.__prem_avail_count = prem_avail_count; + + sut.request( request, response, quote, 'something', () => + { + expect( requested ).to.equal( expected_request ); + expect( save_called ).to.be.true; + + // only save notification status if we're notifying + expect( notify_saved ).to.equal( + !prev_called && expected_request + ); + + done(); + } ); + } ) + ); +} ); diff --git a/test/server/service/RatingServiceTest.js b/test/server/service/RatingServiceTest.js new file mode 100644 index 0000000..948af9a --- /dev/null +++ b/test/server/service/RatingServiceTest.js @@ -0,0 +1,65 @@ +/** + * Tests RatingService + * + * Copyright (C) 2018 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 Affero General Public License + * along with this program. If not, see . + */ + +'use strict' + +const { expect } = require( 'chai' ); +const Sut = require( '../../../' ).server.service.RatingService; +const RatingServiceStub = require( '../../../' ).test.server.service.RatingServiceStub; + +describe( 'RatingService', () => +{ + describe( "protected API", () => + { + it( "calls #postProcessRaterData after rating before save", done => + { + let processed = false; + + const { + logger, + server, + raters, + dao, + request, + response, + quote, + } = RatingServiceStub.getStubs(); + + dao.mergeBucket = () => + { + expect( processed ).to.equal( true ); + done(); + }; + + const sut = Sut.extend( + { + 'override postProcessRaterData'( + request, data, actions, program, quote + ) + { + processed = true; + } + } )( logger, dao, server, raters ); + + sut.request( request, response, quote, 'something', () => {} ); + } ); + } ); +} );