/** * Tests RatingService * * 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 Affero General Public License * along with this program. If not, see . */ import { RatingService as Sut, RateRequestResult } from "../../../src/server/service/RatingService"; import { ClientActions } from "../../../src/client/action/ClientAction"; import { PriorityLog } from "../../../src/server/log/PriorityLog"; import { ProcessManager } from "../../../src/server/rater/ProcessManager"; import { Program } from "../../../src/program/Program"; import { QuoteId } from "../../../src/quote/Quote"; import { Rater, RateResult } from "../../../src/server/rater/Rater"; import { Server } from "../../../src/server/Server"; import { ServerSideQuote } from "../../../src/server/quote/ServerSideQuote"; import { UserRequest } from "../../../src/server/request/UserRequest"; import { UserResponse } from "../../../src/server/request/UserResponse"; import { UserSession } from "../../../src/server/request/UserSession"; import { QuoteDataBucket } from "../../../src/bucket/QuoteDataBucket"; import { PositiveInteger } from "../../../src/numeric"; import { Kv } from "../../../src/bucket/delta"; import { ServerDao, Callback as ServerDaoCallback } from "../../../src/server/db/ServerDao"; import { expect, use as chai_use } from 'chai'; chai_use( require( 'chai-as-promised' ) ); describe( 'RatingService', () => { it( "returns rating results", () => { const { logger, server, raters, dao, request, response, quote, stub_rate_data, createDelta, } = getStubs(); const sut = new Sut( logger, dao, server, raters, createDelta); const expected = { data: stub_rate_data, initialRatedDate: quote.getRatedDate(), lastRatedDate: quote.getLastPremiumDate(), }; return expect( sut.request( request, response, quote, "" ) ) .to.eventually.deep.equal( expected ); } ); it( "updates rating dates before serving to client", () => { const { logger, server, raters, dao, request, response, quote, stub_rate_data, createDelta, } = getStubs(); const sut = new Sut( logger, dao, server, raters, createDelta ); let last_prem_called = false; let rated_date_called = false; let stub_last_prem_ts = 12345; let stub_rated_date_ts = 23456; let sent = false; quote.setLastPremiumDate = () => { last_prem_called = true; return quote; }; quote.setRatedDate = () => { rated_date_called = true; return quote; }; quote.getLastPremiumDate = () => stub_last_prem_ts; quote.getRatedDate = () => stub_rated_date_ts; server.sendResponse = ( _request: any, _quote: any, resp: RateRequestResult, _actions: ClientActions ) => { expect( resp.initialRatedDate ).to.equal( stub_rated_date_ts ); expect( resp.lastRatedDate ).to.equal( stub_last_prem_ts ); expect( last_prem_called ).to.be.true; expect( rated_date_called ).to.be.true; sent = true; return server; }; const expected = { data: stub_rate_data, initialRatedDate: stub_rated_date_ts, lastRatedDate: stub_last_prem_ts, }; return expect( sut.request( request, response, quote, "" ) ) .to.eventually.deep.equal( expected ) .then( () => expect( sent ).to.be.true ); } ); it( "saves rate data to it's own field", () => { const { logger, server, raters, dao, request, response, quote, stub_rate_data, createDelta, } = getStubs(); let saved_rates = false; dao.saveQuote = ( quote: ServerSideQuote, success: ServerDaoCallback, _failure: ServerDaoCallback, save_data: Record, _push_data: Record, ) => { expect( save_data.ratedata ).to.deep.equal( stub_rate_data ); saved_rates = true; success( quote ); return dao; }; const sut = new Sut( logger, dao, server, raters, createDelta ); return sut.request( request, response, quote, "" ) .then( () => { expect( saved_rates ).to.be.true; } ); } ); it( "saves delta to it's own field", () => { const { logger, server, raters, dao, request, response, quote, stub_rate_delta, createDelta, } = getStubs(); let saved_quote = false; let timestamp = 0; quote.setLastPremiumDate = ( ts: UnixTimestamp ) => { timestamp = ts; return quote; }; dao.saveQuote = ( quote: ServerSideQuote, success: ServerDaoCallback, _failure: ServerDaoCallback, _save_data: Record, push_data: Record, ) => { stub_rate_delta[ "rdelta.ratedata" ].timestamp = timestamp; saved_quote = true; expect( push_data ).to.deep.equal( stub_rate_delta ); success( quote ); return dao; }; const sut = new Sut( logger, dao, server, raters, createDelta ); return sut.request( request, response, quote, "" ) .then( () => { expect( saved_quote ).to.be.true; } ); } ); it( "rejects and responds with error", () => { const { dao, logger, program, quote, rater, raters, request, response, server, createDelta, } = getStubs(); const expected_error = new Error( "expected error" ); rater.rate = () => { throw expected_error; }; const sut = new Sut( logger, dao, server, raters, createDelta ); let logged = false; logger.log = function( priority: number, _format: string, qid: QuoteId, program_id: string, message: string, ) { if ( typeof message === 'string' ) { expect( priority ).to.equal( logger.PRIORITY_ERROR ); expect( qid ).to.equal( quote.getId() ); expect( program_id ).to.equal( program.getId() ); expect( message ).to.contain( expected_error.message ); logged = true; } return logger; }; return expect( sut.request( request, response, quote, "" ) ) .to.eventually.rejectedWith( expected_error ) .then( () => expect( logged ).to.be.true ); } ); it( "returns error message from rater", () => { const { dao, logger, quote, rater, raters, request, response, server, createDelta, } = getStubs(); const expected_message = 'expected foo'; const sut = new Sut( logger, dao, server, raters, createDelta ); rater.rate = ( _quote: ServerSideQuote, _session: UserSession, _indv: string, _success: ( data: RateResult, actions: ClientActions ) => void, failure: ( message: string ) => void, ) => { failure( expected_message ); return rater; }; return expect( sut.request( request, response, quote, "" ) ) .to.eventually.rejectedWith( Error, expected_message ); } ); // this means of deferred rating is deprecated and is being superceded // in the near future by a better system; it will hopefully be removed // at some point it( "sends indvRate action for old-style deferred suppliers", () => { const { dao, logger, quote, raters, request, response, server, stub_rate_data, createDelta, } = getStubs(); let sent = false; stub_rate_data._cmpdata = { deferred: [ 'supp1', 'supp2' ], }; server.sendResponse = ( _request: any, _quote: any, _resp: any, actions: ClientActions ) => { expect( actions ).to.deep.equal( [ { action: 'indvRate', id: 'supp1' }, { action: 'indvRate', id: 'supp2' }, ] ); sent = true; return server; }; const sut = new Sut( logger, dao, server, raters, createDelta ); return sut.request( request, response, quote, "" ) .then( () => expect( sent ).to.be.true ); } ); describe( "protected API", () => { it( "calls #postProcessRaterData after rating before save", done => { let processed = false; const { logger, server, raters, dao, request, response, quote, createDelta, } = getStubs(); dao.mergeBucket = () => { expect( processed ).to.equal( true ); done(); return dao; }; const sut = new class extends Sut { postProcessRaterData() { processed = true; } }( logger, dao, server, raters, createDelta ); sut.request( request, response, quote, 'something' ); } ); it( "calls getLastPremiumDate during #_performRating", done => { let getLastPremiumDateCallCount = 0; const last_date = 1234; const initial_date = 2345; const { logger, server, raters, dao, request, response, quote, createDelta, } = getStubs(); quote.getLastPremiumDate = () => { getLastPremiumDateCallCount++; return last_date }; quote.getRatedDate = () => initial_date; const sut = new Sut( logger, dao, server, raters, createDelta ); server.sendResponse = ( _request: any, _quote: any, resp: any, _actions: any ) => { expect( getLastPremiumDateCallCount ).to.equal( 2 ); expect( resp.initialRatedDate ).to.equal( initial_date ); expect( resp.lastRatedDate ).to.equal( last_date ); done(); return server; }; sut.request( request, response, quote, "" ); } ); } ); } ); function getStubs() { const program_id = 'foo'; const program = { getId: () => program_id, ineligibleLockCount: 0, }; // rate reply const stub_rate_data: RateResult = { _unavailable_all: '0', }; const stub_rate_delta: any = { "rdelta.ratedata": { data: { _unavailable_all: [ undefined ] }, timestamp: 123 } }; const createDelta = ( _src: Kv, _dest: Kv ) => { return stub_rate_delta[ "rdelta.ratedata" ][ "data" ]; }; const rater = new class implements Rater { rate( _quote: ServerSideQuote, _session: UserSession, _indv: string, success: ( data: RateResult, actions: ClientActions ) => void, _failure: ( message: string ) => void, ) { // force to be async so that the tests resemble how the code // actually runs process.nextTick( () => success( stub_rate_data, [] ) ); return this; } }; const raters = { byId: () => rater, }; const logger = new class implements PriorityLog { readonly PRIORITY_ERROR: number = 0; readonly PRIORITY_IMPORTANT: number = 1; readonly PRIORITY_DB: number = 2; readonly PRIORITY_INFO: number = 3; readonly PRIORITY_SOCKET: number = 4; log( _priority: number, ..._args: Array ): this { return this; } }; const server = { sendResponse: () => server, sendError: () => server, }; const dao = new class implements ServerDao { saveQuote( quote: ServerSideQuote, success: ServerDaoCallback, _failure: ServerDaoCallback, _save_data: Record, _push_data: Record, ): this { success( quote ); return this; } mergeBucket(): this { return this; } saveQuoteClasses(): this { return this; } setWorksheets(): this { return this; } saveQuoteState(): this { throw new Error( "Unused method" ); } saveQuoteLockState(): this { throw new Error( "Unused method" ); } getWorksheet(): this { throw new Error( "Unused method" ); } }; const session = { isInternal: () => false, }; const request = { getSession: () => session, getSessionIdName: () => {}, }; const response = {}; const quote = { getProgramId: () => program_id, getProgram: () => program, getId: () => 0, setLastPremiumDate: () => quote, setRatedDate: () => quote, getRatedDate: () => 0, getLastPremiumDate: () => 0, getCurrentStepId: () => 0, setExplicitLock: () => quote, setRateBucket: () => quote, setRatingData: () => quote, getRatingData: () => stub_rate_data, getBucket: () => new QuoteDataBucket(), getMetabucket: () => new QuoteDataBucket(), getProgramVersion: () => 'Foo', getExplicitLockReason: () => 'Reason', getExplicitLockStep: () => 1, isImported: () => true, isBound: () => true, getTopVisitedStepId: () => 1, getTopSavedStepId: () => 1, }; return { program: program, stub_rate_data: stub_rate_data, stub_rate_delta: stub_rate_delta, createDelta: createDelta, rater: rater, raters: raters, logger: logger, server: server, dao: dao, session: session, request: request, response: response, quote: quote, }; };