From 14869d9041c4ed35f7cfd7d55a9edd56ea909c18 Mon Sep 17 00:00:00 2001 From: Corey Vollmer Date: Tue, 21 May 2019 11:41:18 -0400 Subject: [PATCH 1/3] [DEV-3514] Add more robust testing for BasicQuote Also, updated README to include instructions on `npm install.` --- README.md | 1 + test/quote/BaseQuoteTest.js | 212 ++++++++++++++++++++++++++++++++---- 2 files changed, 190 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 61e19a6..639132f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ If `configure` is not available, see the section "Configuring" above. ``` $ ./configure # see --help for optional arguments $ make # build + $ npm install # install js dependencies $ make check # run test cases ``` diff --git a/test/quote/BaseQuoteTest.js b/test/quote/BaseQuoteTest.js index 9c6b8cd..1607bb3 100644 --- a/test/quote/BaseQuoteTest.js +++ b/test/quote/BaseQuoteTest.js @@ -27,32 +27,198 @@ const { BaseQuote } = require( '../../' ).quote; describe( 'BaseQuote', () => { - [ - { - property: 'startDate', - value: 12345 - }, - { - property: 'initialRatedDate', - value: 12345 - }, - { - property: 'agentEntityId', - value: 12434300 - }, - ].forEach( testCase => + describe( 'accessors & mutators', () => { - const quote = BaseQuote( 123, {} ); - const property = testCase.property; - const title_cased = property.charAt( 0 ).toUpperCase() + property.slice( 1 ); - const setter = 'set' + title_cased; - const getter = 'get' + title_cased; + [ + { + 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' + } - it( property + ' can be mutated and accessed', () => + ].forEach( testCase => { - expect( quote[getter].call( null ) ).to.be.undefined; - quote[setter].call( null, testCase.value ); - expect( quote[getter].call( null ) ).to.equal( testCase.value ); + const quote = BaseQuote( 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 ); + } ); + } ); + } ); + + describe( 'locking mechanisms', () => + { + [ + { + description: 'default values', + reason: '', + step: 0, + bound: false, + imported: false, + locks: false + }, + { + description: 'quote with a reason', + reason: 'reason', + step: 0, + bound: false, + imported: false, + locks: true + }, + { + description: 'quote with a lock on step #2', + reason: '', + step: 2, + bound: false, + imported: false, + locks: false + }, + { + description: 'quote with a reason and a lock on step #2', + reason: 'reason', + step: 2, + bound: false, + imported: false, + locks: false + }, + { + description: 'bound quote', + reason: { given: '', expected: 'Quote has been bound' }, + step: 0, + bound: true, + imported: false, + locks: true + }, + { + description: 'imported quote', + reason: '', + step: 0, + bound: false, + imported: true, + locks: true + }, + { + description: 'bound and imported quote', + reason: { given: '', expected: 'Quote has been bound' }, + step: 0, + bound: true, + imported: true, + locks: true + }, + { + description: 'bound quote with a lock on step #2', + reason: { given: '', expected: 'Quote has been bound' }, + step: { given: 2, expected: 0 }, + bound: true, + imported: false, + locks: true + }, + { + description: 'imported quote with a lock on step #2', + reason: '', + step: 2, + bound: false, + imported: true, + locks: false + } + + ].forEach( testCase => + { + const quote = BaseQuote( 123, {} ); + const description = 'Locking is correct for ' + testCase.description; + const bound = !!testCase.bound; + const imported = !!testCase.imported; + const locks = !!testCase.locks; + + const givenReason = ( testCase.reason.given !== undefined ) ? + '' + testCase.reason.given : + '' + testCase.reason; + const expectedReason = ( testCase.reason.expected !== undefined ) ? + '' + testCase.reason.expected : + '' + testCase.reason; + + const givenStep = ( testCase.step.given !== undefined ) ? + +testCase.step.given : + +testCase.step; + const expectedStep = ( testCase.step.expected !== undefined ) ? + +testCase.step.expected : + +testCase.step; + + it( description, () => + { + expect( quote.getExplicitLockReason() ).to.equal( '' ); + expect( quote.getExplicitLockStep() ).to.equal( 0 ); + + quote.setBound( bound ) + .setImported( imported ) + .setExplicitLock( givenReason, givenStep ); + + expect( quote.getExplicitLockReason() ).to.equal( expectedReason ); + expect( quote.getExplicitLockStep() ).to.equal( expectedStep ); + expect( quote.isLocked() ).to.equal( locks ); + + quote.clearExplicitLock(); + expect( quote.getExplicitLockReason() ).to.equal( bound ? 'Quote has been bound' : '' ); + expect( quote.getExplicitLockStep() ).to.equal( 0 ); + } ); } ); } ); } ); From 5a5c2ca629603e8a0dfb6323a40ef7e2fda0216c Mon Sep 17 00:00:00 2001 From: Andrew Fanton Date: Tue, 21 May 2019 14:29:49 -0400 Subject: [PATCH 2/3] [DEV-3514] Lock quotes that exceed expiration date --- src/quote/BaseQuote.js | 70 +++++++- src/server/Server.js | 15 ++ src/server/daemon/controller.js | 8 +- src/server/quote/ServerSideQuote.js | 2 +- src/test/program/util.js | 29 +++- test/quote/BaseQuoteTest.js | 193 ++++++++++++++++++++++- test/server/quote/ServerSideQuoteTest.js | 125 +++++++++++++++ 7 files changed, 425 insertions(+), 17 deletions(-) create mode 100644 test/server/quote/ServerSideQuoteTest.js diff --git a/src/quote/BaseQuote.js b/src/quote/BaseQuote.js index 05df67b..e2a99d9 100644 --- a/src/quote/BaseQuote.js +++ b/src/quote/BaseQuote.js @@ -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 * diff --git a/src/server/Server.js b/src/server/Server.js index 60a965c..f30e306 100644 --- a/src/server/Server.js +++ b/src/server/Server.js @@ -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(), diff --git a/src/server/daemon/controller.js b/src/server/daemon/controller.js index 2206d5e..e23c670 100644 --- a/src/server/daemon/controller.js +++ b/src/server/daemon/controller.js @@ -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 */ diff --git a/src/server/quote/ServerSideQuote.js b/src/server/quote/ServerSideQuote.js index 3e6244b..8f35f42 100644 --- a/src/server/quote/ServerSideQuote.js +++ b/src/server/quote/ServerSideQuote.js @@ -178,6 +178,6 @@ module.exports = Class( 'ServerSideQuote' ) this._metabucket.setValues( data ); return this; - }, + } } ); diff --git a/src/test/program/util.js b/src/test/program/util.js index 00e49bf..7d7d30b 100644 --- a/src/test/program/util.js +++ b/src/test/program/util.js @@ -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 + } ); +}; diff --git a/test/quote/BaseQuoteTest.js b/test/quote/BaseQuoteTest.js index 1607bb3..5087431 100644 --- a/test/quote/BaseQuoteTest.js +++ b/test/quote/BaseQuoteTest.js @@ -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; +} \ No newline at end of file diff --git a/test/server/quote/ServerSideQuoteTest.js b/test/server/quote/ServerSideQuoteTest.js new file mode 100644 index 0000000..c496e8d --- /dev/null +++ b/test/server/quote/ServerSideQuoteTest.js @@ -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 . + */ + +'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 ); + } ); + } ); + } ); +} ); \ No newline at end of file From f569a7e94d4fd6da125c2f01fdcb3f80b88a1703 Mon Sep 17 00:00:00 2001 From: Andrew Fanton Date: Wed, 22 May 2019 11:09:12 -0400 Subject: [PATCH 3/3] [DEV-3514] Display message explaining why quote is locked Prior to this change, a single generic message was always shown simply stating that the quote had been locked. These changes now allow for different messages to be displayed in different circumstances. --- src/client/Client.js | 7 ++++--- src/quote/BaseQuote.js | 2 +- src/server/Server.js | 2 +- test/quote/BaseQuoteTest.js | 14 ++++++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index b510c43..bc909bd 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -2206,6 +2206,9 @@ module.exports = Class( 'Client' ) var client = this, explicit_step = this._quote.getExplicitLockStep(); + var lock_msg = this._quote.getExplicitLockReason() || + "The quote is locked and cannot be modified."; + // if the step is locked to step 1, then there is no noticable effect; // don't bother if ( explicit_step == 1 ) @@ -2222,9 +2225,7 @@ module.exports = Class( 'Client' ) { client.ui.showNotifyBar( $( '
' ).append( - $( '
' ).html( - "The quote is locked and cannot be modified." - ) + $( '
' ).html( lock_msg ) ) ); }, 25 ); diff --git a/src/quote/BaseQuote.js b/src/quote/BaseQuote.js index e2a99d9..471a79f 100644 --- a/src/quote/BaseQuote.js +++ b/src/quote/BaseQuote.js @@ -745,7 +745,7 @@ module.exports = Class( 'BaseQuote' ) 'public getExplicitLockReason': function() { return ( this.isBound() ) - ? 'Quote has been bound' + ? 'This quote has been bound and cannot be modified.' : this._explicitLock; }, diff --git a/src/server/Server.js b/src/server/Server.js index f30e306..d146ffd 100644 --- a/src/server/Server.js +++ b/src/server/Server.js @@ -737,7 +737,7 @@ module.exports = Class( 'Server' ) ) } ]; - lock = 'concurrent-access'; + lock = 'Quote is locked due to concurrent access.'; } // decrypt bucket contents, if necessary, and return diff --git a/test/quote/BaseQuoteTest.js b/test/quote/BaseQuoteTest.js index 5087431..f1ba018 100644 --- a/test/quote/BaseQuoteTest.js +++ b/test/quote/BaseQuoteTest.js @@ -27,6 +27,12 @@ const { BaseQuote } = require( '../../' ).quote; const Program = require( '../../src/program/Program' ).Program; const Util = require( '../../src/test/program/util' ); +const messages = { + expire: 'This quote has expired and cannot be modified. ' + + 'Please contact support with any questions.', + bound: 'This quote has been bound and cannot be modified.' +}; + describe( 'BaseQuote', () => { describe( 'accessors & mutators', () => @@ -143,7 +149,7 @@ describe( 'BaseQuote', () => }, { description: 'bound quote', - reason: { given: '', expected: 'Quote has been bound' }, + reason: { given: '', expected: messages.bound }, step: 0, bound: true, imported: false, @@ -159,7 +165,7 @@ describe( 'BaseQuote', () => }, { description: 'bound and imported quote', - reason: { given: '', expected: 'Quote has been bound' }, + reason: { given: '', expected: messages.bound }, step: 0, bound: true, imported: true, @@ -167,7 +173,7 @@ describe( 'BaseQuote', () => }, { description: 'bound quote with a lock on step #2', - reason: { given: '', expected: 'Quote has been bound' }, + reason: { given: '', expected: messages.bound }, step: { given: 2, expected: 0 }, bound: true, imported: false, @@ -218,7 +224,7 @@ describe( 'BaseQuote', () => expect( quote.isLocked() ).to.equal( locks ); quote.clearExplicitLock(); - expect( quote.getExplicitLockReason() ).to.equal( bound ? 'Quote has been bound' : '' ); + expect( quote.getExplicitLockReason() ).to.equal( bound ? messages.bound : '' ); expect( quote.getExplicitLockStep() ).to.equal( 0 ); } ); } );