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', () => {} );
+ } );
+ } );
+} );