1
0
Fork 0

[DEV-3514] Lock quotes that exceed expiration date

master
Andrew Fanton 2019-05-21 14:29:49 -04:00
parent 14869d9041
commit 5a5c2ca629
7 changed files with 425 additions and 17 deletions

View File

@ -26,7 +26,6 @@ var Class = require( 'easejs' ).Class,
Program = require( '../program/Program' ).Program,
EventEmitter = require( 'events' ).EventEmitter;
/**
* Creates a new quote
*
@ -148,8 +147,8 @@ module.exports = Class( 'BaseQuote' )
*/
'public __construct': function( id, bucket )
{
this._id = id;
this._bucket = bucket;
this._id = id;
this._bucket = bucket;
},
@ -271,6 +270,71 @@ module.exports = Class( 'BaseQuote' )
},
/**
* Returns the quote's expiration date
*
* @return {number} quote's initial rated date
*/
'public getExpirationDate': function()
{
var post_rate = ( this._initialRatedDate > 0 );
// Don't attempt to calculate expiration date if expiration is not defined
if ( !this._program
|| !this._program.lockTimeout
|| ( !post_rate && !this._program.lockTimeout.preRateExpiration )
|| ( post_rate && !this._program.lockTimeout.postRateExpiration ))
{
return Number.POSITIVE_INFINITY;
}
var reference_date = ( post_rate ) ? this._initialRatedDate : this._startDate;
var expiration_period = ( post_rate )
? this._program.lockTimeout.postRateExpiration
: this._program.lockTimeout.preRateExpiration;
// Use Date.setDate to accommodate leap seconds, leap years, DST, etc.
var expiration_date = new Date( reference_date );
expiration_date.setDate( expiration_date.getDate() + expiration_period );
return expiration_date.getTime();
},
/**
* Returns whether the quote has expired or not
*
* @param {Date} current_date current date to determine if expiration date has passed
*
* @return {boolean} flag indicating if the quote has expired
*/
'public hasExpired': function( current_date )
{
var timeout = ( this._program && this._program.lockTimeout )
? this._program.lockTimeout
: { preRateGracePeriod: 0, postRateGracePeriod: 0 };
var grace_period = ( this._initialRatedDate > 0 )
? ( timeout.postRateGracePeriod || 0 )
: ( timeout.preRateGracePeriod || 0 );
var expiration_timestamp = this.getExpirationDate();
// If the timestamp is INFINITY, the quote will never expire
// NOTE: The Date constructor does not support INFINITY as the timestamp
if ( expiration_timestamp === Number.POSITIVE_INFINITY )
{
return false;
}
// Use Date.setDate to accommodate leap seconds, leap years, DST, etc.
var expiration_date = new Date( expiration_timestamp );
expiration_date.setDate( expiration_date.getDate() + grace_period );
return current_date.getTime() > expiration_date.getTime();
},
/**
* Sets id of agent that owns the quote
*

View File

@ -255,6 +255,12 @@ module.exports = Class( 'Server' )
/**
* Initializes a quote with any existing quote data
*
* @param Integer quote_id id of the quote
* @param Program program program that the quote will be a part of
* @param Object request request to create quote
* @param Function( quote ) callback function to call when quote is ready
* @param Function( quote ) callback function to call when an error occurs
*
* @return Server self to allow for method chaining
*/
initQuote: function( quote, program, request, callback, error_callback )
@ -696,6 +702,15 @@ module.exports = Class( 'Server' )
}
}
// Expire quote as needed
if ( quote.hasExpired( new Date() ) )
{
quote.setExplicitLock(
'This quote has expired and cannot be modified. ' +
'Please contact support with any questions.'
);
}
var bucket = quote.getBucket(),
lock = quote.getExplicitLockReason(),
lock_step = quote.getExplicitLockStep(),

View File

@ -633,9 +633,11 @@ function doRoute( program, request, data, resolve, reject )
/**
* Creates a new quote instance with the given quote id
*
* @param Integer quote_id id of the quote
* @param Program program program that the quote will be a part of
* @param Function( quote ) callback function to call when quote is ready
* @param Integer quote_id id of the quote
* @param Program program program that the quote will be a part of
* @param Object request request to create quote
* @param Function( quote ) callback function to call when quote is ready
* @param Function( quote ) callback function to call when an error occurs
*
* @return undefined
*/

View File

@ -178,6 +178,6 @@ module.exports = Class( 'ServerSideQuote' )
this._metabucket.setValues( data );
return this;
},
}
} );

View File

@ -245,8 +245,29 @@ exports.stubCmatch = ( cmatch_dfn ) =>
return cmatch;
};
exports.stubProgram = Program => Program.extend(
/**
* Produce a stub class that extends the abstract Program class
*
* @param {Program} ProgramSut class to extend
* @param {function} mockInitQuote optional: mock implementation of 'initQuote' function
*
* @return {Class} stub class for creating mock Program object(s)
*/
exports.stubProgram = ( ProgramSut, mockInitQuote ) =>
{
classifier: __dirname + '/DummyClassifier',
} );
// TODO: Make ProgramSut optional
if ( !ProgramSut )
{
throw new Error( "Class for Program stub must be specified." );
}
mockInitQuote = mockInitQuote ||
ProgramSut.initQuote ||
( ( bucket, store_only ) => {} );
return ProgramSut.extend(
{
classifier: __dirname + '/DummyClassifier',
initQuote: mockInitQuote
} );
};

View File

@ -21,9 +21,11 @@
'use strict';
const chai = require( 'chai' );
const expect = chai.expect;
const { BaseQuote } = require( '../../' ).quote;
const chai = require( 'chai' );
const expect = chai.expect;
const { BaseQuote } = require( '../../' ).quote;
const Program = require( '../../src/program/Program' ).Program;
const Util = require( '../../src/test/program/util' );
describe( 'BaseQuote', () =>
{
@ -97,9 +99,9 @@ describe( 'BaseQuote', () =>
it( property + ' can be mutated and accessed', () =>
{
expect( quote[getter].call( quote ) ).to.equal( testCase.default );
quote[setter].call( quote, testCase.value );
expect( quote[getter].call( quote ) ).to.equal( testCase.value );
expect( quote[ getter ].call( quote ) ).to.equal( testCase.default );
quote[ setter ].call( quote, testCase.value );
expect( quote[ getter ].call( quote ) ).to.equal( testCase.value );
} );
} );
} );
@ -221,4 +223,183 @@ describe( 'BaseQuote', () =>
} );
} );
} );
describe( 'quote expiration', () =>
{
[
{
description: 'default values',
currentDate: 0,
expired: false
},
{
description: 'quote immediately after start',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
expirationDate: 7862400000,
currentDate: 86400000,
expired: false
},
{
description: 'quote immediately after rate',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
initialRatedDate: 172800000,
expirationDate: 2764800000,
currentDate: 172800000,
expired: false
},
{
description: 'quote 31 days after rate',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400,
initialRatedDate: 172800000,
expirationDate: 2764800000,
currentDate: 2851200000,
expired: true
},
{
description: 'quote 31 days after rate (with grace period)',
lockTimeout:
{
preRateExpiration: 90,
preRateGracePeriod: 15,
postRateExpiration: 30,
postRateGracePeriod: 5
},
startDate: 86400,
initialRatedDate: 172800000,
expirationDate: 2764800000,
currentDate: 2851200000,
expired: false
},
{
description: 'quote 62 days after start',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
expirationDate: 7862400000,
currentDate: 5356800000,
expired: false
},
{
description: 'quote 61 days after rate',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
initialRatedDate: 172800000,
expirationDate: 2764800000,
currentDate: 5356800000,
expired: true
},
{
description: 'quote 91 days after start',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
expirationDate: 7862400000,
currentDate: 7948800000,
expired: true
},
{
description: 'quote 91 days after start (with grace period)',
lockTimeout:
{
preRateExpiration: 90,
preRateGracePeriod: 15,
postRateExpiration: 30,
postRateGracePeriod: 5
},
startDate: 86400000,
expirationDate: 7862400000,
currentDate: 7948800000,
expired: false
},
{
description: 'quote 121 days after start',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
expirationDate: 7862400000,
currentDate: 10540800000,
expired: true
},
{
description: 'quote 120 days after rate',
lockTimeout:
{
preRateExpiration: 90,
postRateExpiration: 30
},
startDate: 86400000,
initialRatedDate: 172800000,
expirationDate: 2764800000,
currentDate: 10540800000,
expired: true
}
].forEach( testCase =>
{
const quote = BaseQuote( 123, {} );
const description = "Expiration is correct for " + testCase.description;
const start_date = testCase.startDate;
const initial_rated_date = testCase.initialRatedDate;
const current_date = testCase.currentDate;
const expiration_date = testCase.expirationDate;
const expired = testCase.expired;
quote.setProgram( createStubProgram( testCase.lockTimeout ) );
it( description, () =>
{
if ( start_date !== undefined )
{
quote.setStartDate( start_date );
}
if ( initial_rated_date !== undefined )
{
quote.setInitialRatedDate( initial_rated_date );
}
if ( expiration_date !== undefined )
{
expect( quote.getExpirationDate() ).to.equal( +expiration_date );
}
expect( quote.hasExpired( new Date( current_date ) ) ).to.equal( !!expired );
} );
} );
} );
} );
function createStubProgram( lockTimeout )
{
let stubProgram = Util.stubProgram( Program )();
stubProgram.lockTimeout = lockTimeout || {};
return stubProgram;
}

View File

@ -0,0 +1,125 @@
/**
* Tests ServerSideQuote
*
* Copyright (C) 2017, 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 <http://www.gnu.org/licenses/>.
*/
'use strict';
const { expect } = require( 'chai' );
const Sut = require( '../../..' ).server.quote.ServerSideQuote;
describe( 'ServerSideQuote', () =>
{
describe( 'accessors & mutators', () =>
{
[
{
property: 'startDate',
default: 0,
value: 946684800
},
{
property: 'initialRatedDate',
default: 0,
value: 946684800
},
{
property: 'agentId',
default: 0,
value: 12345678
},
{
property: 'agentEntityId',
default: 0,
value: 12345678
},
{
property: 'agentName',
default: '',
value: 'name'
},
{
property: 'imported',
default: false,
value: true,
accessor: 'is'
},
{
property: 'bound',
default: false,
value: true,
accessor: 'is'
},
{
property: 'currentStepId',
default: 1,
value: 2
},
{
property: 'topVisitedStepId',
default: 1,
value: 2
},
{
property: 'topSavedStepId',
value: 1
},
{
property: 'error',
default: '',
value: 'ERROR'
},
{
property: 'programVersion',
default: '',
value: '1.0.0'
},
{
property: 'creditScoreRef',
default: 0,
value: 800
},
{
property: 'lastPremiumDate',
default: 0,
value: 946684800
},
{
property: 'ratedDate',
default: 0,
value: 946684800
}
].forEach( testCase =>
{
const quote = Sut( 123, {} );
const property = testCase.property;
const title_cased = property.charAt( 0 ).toUpperCase() + property.slice( 1 );
const setter = ( testCase.mutator || 'set' ) + title_cased;
const getter = ( testCase.accessor || 'get' ) + title_cased;
it( property + ' can be mutated and accessed', () =>
{
expect( quote[getter].call( quote ) ).to.equal( testCase.default );
quote[setter].call( quote, testCase.value );
expect( quote[getter].call( quote ) ).to.equal( testCase.value );
} );
} );
} );
} );