From 4f39c754a2d9b2fc2e2caa611c6c38d80f4e509a Mon Sep 17 00:00:00 2001 From: Austin Schaffer Date: Thu, 7 Nov 2019 13:33:14 -0500 Subject: [PATCH] [DEV-6353] Add bucket definitions --- src/bucket/QuoteDataBucket.d.ts | 123 ++++++++++++++ src/bucket/StagingBucket.d.ts | 206 +++++++++++++++++++++++ src/bucket/bucket_filter.d.ts | 65 +++++++ src/program/Program.d.ts | 4 + src/quote/BaseQuote.d.ts | 9 + src/server/quote/ServerSideQuote.d.ts | 25 +++ src/server/quote/ServerSideQuote.js | 67 +++++++- src/server/request/DataProcessor.ts | 10 +- test/server/quote/ServerSideQuoteTest.js | 117 +++++++++---- test/server/request/DataProcessorTest.ts | 3 +- test/server/service/RatingServiceTest.ts | 7 +- 11 files changed, 595 insertions(+), 41 deletions(-) create mode 100644 src/bucket/QuoteDataBucket.d.ts create mode 100644 src/bucket/StagingBucket.d.ts create mode 100644 src/bucket/bucket_filter.d.ts diff --git a/src/bucket/QuoteDataBucket.d.ts b/src/bucket/QuoteDataBucket.d.ts new file mode 100644 index 0000000..7f1c8e6 --- /dev/null +++ b/src/bucket/QuoteDataBucket.d.ts @@ -0,0 +1,123 @@ +/** + * Key/value store + * + * Copyright (C) 2010-2019 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 . + */ + + +import { PositiveInteger } from "../numeric"; + + +/** + * General key/value store for document + * + * The term "Quote" here is an artifact from the initial design of the + * system used for insurance quoting. It will be renamed. + * + * @todo Rename to DocumentDataBucket + */ +export declare class QuoteDataBucket +{ + /** + * Triggered when data in the bucket is updated, before it's committed + */ + static readonly EVENT_UPDATE: string; + + + /** + * Explicitly sets the contents of the bucket + * + * @param data - associative array of the data + */ + setValues( data: Record ): this; + + + /** + * Alias of setValues + */ + setCommittedValues(): this; + + + /** + * Clears all data from the bucket + */ + clear(): this; + + + /** + * Overwrites values in the original bucket + * + * For this buckeet, overwriteValues() is an alias for setValues() without + * index merging. However, other Bucket implementations may handle it + * differently. + * + * @param data - associative array of the data + */ + overwriteValues( data: Record ): this; + + + /** + * Calls a function for each each of the values in the bucket + * + * Note: This format is intended to be consistent with Array.forEach() + * + * @param callback - function to call for each value in the bucket + */ + each( callback: ( val: any, key: string) => {} ): this; + + + /** + * Calls a function for each each of the values in the bucket matching the + * given predicate + * + * @param pred - predicate + * @param c - function to call for each value in the bucket + */ + filter( + pred: ( key: string ) => {}, + _c: ( val: any, key: string) => {} + ): this; + + + /** + * Returns the data for the requested field + * + * @param name - name of the field (with or without trailing brackets) + * + * @return data for the field, or empty array if none + */ + getDataByName( name: string ): any; + + + /** + * Returns the data as a JSON string + * + * @return data represented as JSON + */ + getDataJson(): string; + + + /** + * Return raw bucket data + * + * TODO: remove; breaks encapsulation + * + * @return raw bucket data + */ + getData(): Record; +} diff --git a/src/bucket/StagingBucket.d.ts b/src/bucket/StagingBucket.d.ts new file mode 100644 index 0000000..a96341f --- /dev/null +++ b/src/bucket/StagingBucket.d.ts @@ -0,0 +1,206 @@ +/** + * StagingBucket class + * + * 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 { PositiveInteger } from "../numeric"; + + +export type StagingBucketConstructor = ( + bucket: StagingBucket +) => StagingBucket; + + +/** + * Stages and merges values into underlying key/value store + */ +export declare class StagingBucket +{ + /** + * Analgous to setValues(), but immediately commits the changes + * + * This still calls setValues() to ensure all events are properly kicked + * off. + * + * @param data - data to set and commit + */ + setCommittedValues( data: Record ): this; + + + /** + * Prevent #setCommittedValues from bypassing staging + * + * When set, #setCommittedValues will act as an alias of #setValues. + */ + forbidBypass(): this; + + + /** + * Explicitly sets the contents of the bucket + * + * Because JSON serializes all undefined values to `null`, only the + * final null in a diff is considered terminating; the rest are + * converted into `undefined`. Therefore, it is important that all + * truncations include no elements in the vector after the truncating null. + * + * @param given_data - associative array of the data + */ + setValues( given_data: Record ): this; + + + /** + * Overwrites values in the original bucket + * + * @param data - associative array of the data + */ + overwriteValues( data: Record ): this; + + + /** + * Returns staged data + * + * @return staged data + */ + getDiff(): Record; + + + /** + * Returns a field-oriented diff filled with all values rather than a + * value-oriented diff + * + * Only the fields that have changed are returned. Each field contains its + * actual value---not the diff representation of what portions of the field + * have changed. + * + * @return filled diff + */ + getFilledDiff(): Record; + + + /** + * Reverts staged changes, preventing them from being committed + * + * This will also generate a diff and raise the same events that would be + * raised by setting values in the conventional manner, allowing reverts to + * transparently integrate with the remainder of the system. + * + * @param evented - whether to emit events as part of the revert + */ + revert( evented?: boolean ): this; + + + /** + * Commits staged changes, merging them with the bucket + * + * @param store - object to save old staged values to + */ + commit( store?: { old: Record } ): this + + + /** + * Clears all data from the bucket + */ + clear(): this; + + + /** + * Calls a function for each each of the values in the bucket + * + * @param callback - function to call for each value in the bucket + */ + each( callback: ( value: any, name: string ) => void ): this; + + + /** + * Returns the data for the requested field + * + * WARNING: This can be a potentially expensive operation if there is a + * great deal of staged data. The staged data is merged with the bucket data + * on each call. Do not make frequent calls to retrieve the same data. Cache + * it instead. + * + * @param name - field name (with or without trailing brackets) + * + * @return data for the field, or empty array if none + */ + getDataByName( name: string ): Record; + + + /** + * Returns original bucket data by name, even if there is data staged atop + * of it + * + * There is no additional overhead of this operation versus getDataByName() + * + * @param name - field name (with or without trailing brackets) + * + * @return data for the field, or empty array if none + */ + getOriginalDataByName( name: string ): Record; + + + /** + * Returns the data as a JSON string + * + * @return data represented as JSON + */ + getDataJson(): string; + + + /** + * Return raw bucket data + * + * todo: remove; breaks encapsulation + * + * @return raw bucket data + */ + getData(): Record; + + + /** + * Calls a function for each each of the values in the bucket matching the + * given predicate + * + * @param pred - predicate + * @param c - function to call for each value in the bucket + */ + filter( + pred: ( name: string ) => boolean, + c: ( value: any, name: string ) => void + ): this; + + + /** + * Returns true if the index for the given key exists + * + * @param name - the data key + * @param i - the index + * + * @return whether the key exists + */ + hasIndex( name: string, i: PositiveInteger ): boolean; + + + /** + * Returns true if the bucket has been changed and not saved + * + * @return true if the bucket has been changed and not saved + */ + isDirty(): boolean; +} diff --git a/src/bucket/bucket_filter.d.ts b/src/bucket/bucket_filter.d.ts new file mode 100644 index 0000000..f731956 --- /dev/null +++ b/src/bucket/bucket_filter.d.ts @@ -0,0 +1,65 @@ +/** + * Filters bucket data + * + * 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 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 . + */ + + +export declare module bucket_filter { + + export type filter = { + /** + * Filters bucket data based on the provided types + * + * If a type is not provided, the data is considered to be unwanted and is + * removed entirely. Otherwise, the filter is applied to every element in the + * array. + * + * The data is modified in place. + * + * @param data - data to filter + * @param key_types - filter types + * @param ignore_types - types to ignore + * @param permit_null - Allow nulls in results + * + * @return Object modified data + */ + filter( + data: Record, + key_types: Record, + ignore_types: Record, + permit_null: boolean, + ): Record + + + /** + * Filter bucket data based on values + * + * @param values - The values to filter + * @param filter - The filter to apply + * @param permit_null - Allow nulls in results + * + * @return the filtered values + */ + filterValues( + values: string[], + filter: string, + permit_null: boolean, + ): string[]; + }; +} diff --git a/src/program/Program.d.ts b/src/program/Program.d.ts index de3f45a..ec45efc 100644 --- a/src/program/Program.d.ts +++ b/src/program/Program.d.ts @@ -19,9 +19,13 @@ * along with this program. If not, see . */ +import { StagingBucket } from "../bucket/StagingBucket"; + export declare abstract class Program { readonly ineligibleLockCount: number; getId(): string; + + initQuote( bucket: StagingBucket, store_only: boolean ): void } diff --git a/src/quote/BaseQuote.d.ts b/src/quote/BaseQuote.d.ts index 9af47ee..f2271f4 100644 --- a/src/quote/BaseQuote.d.ts +++ b/src/quote/BaseQuote.d.ts @@ -23,6 +23,7 @@ import { Program } from "../program/Program"; import { Quote, QuoteId } from "./Quote"; +import { QuoteDataBucket } from "../bucket/QuoteDataBucket"; export declare class BaseQuote implements Quote @@ -90,4 +91,12 @@ export declare class BaseQuote implements Quote * @return last calculated time or 0 */ getLastPremiumDate(): UnixTimestamp; + + + /** + * Returns the bucket used to store the quote form data + * + * @return the data bucket + */ + getBucket(): QuoteDataBucket } diff --git a/src/server/quote/ServerSideQuote.d.ts b/src/server/quote/ServerSideQuote.d.ts index 98eff98..c8bdf2f 100644 --- a/src/server/quote/ServerSideQuote.d.ts +++ b/src/server/quote/ServerSideQuote.d.ts @@ -23,6 +23,7 @@ import { Program } from "../../program/Program"; import { BaseQuote } from "../../quote/BaseQuote"; +import { QuoteDataBucket } from "../../bucket/QuoteDataBucket"; export declare class ServerSideQuote extends BaseQuote @@ -43,4 +44,28 @@ export declare class ServerSideQuote extends BaseQuote * @return self */ setRatedDate( timestamp: UnixTimestamp ): this; + + + /** + * Set rating bucket + * + * @param bucket - the data bucket + */ + setRateBucket( bucket: QuoteDataBucket ): this; + + + /** + * Set rating data + * + * @param data - rating data + */ + setRatingData( data: Record ): this; + + + /** + * Get rating data + * + * @return rating data + */ + getRatingData(): Record; } diff --git a/src/server/quote/ServerSideQuote.js b/src/server/quote/ServerSideQuote.js index b1040c3..3d5f7c0 100644 --- a/src/server/quote/ServerSideQuote.js +++ b/src/server/quote/ServerSideQuote.js @@ -40,7 +40,6 @@ module.exports = Class( 'ServerSideQuote' ) */ 'private _creditScoreRef': 0, - /** * Unix timestamp containing date of first premium calculation * @type {number} @@ -53,6 +52,12 @@ module.exports = Class( 'ServerSideQuote' ) */ 'private _metabucket': null, + /** + * Rating Data Bucket + * @type {Bucket} + */ + 'private _rate_bucket': null, + 'public setProgramVersion': function( version ) { @@ -148,6 +153,64 @@ module.exports = Class( 'ServerSideQuote' ) this._metabucket.setValues( data ); return this; - } + }, + + + /** + * Set rating bucket + * + * @param {Bucket} bucket the rate bucket to set + */ + 'public setRateBucket': function( bucket ) + { + this._rate_bucket = bucket; + + return this; + }, + + + /** + * Get rating bucket + * + * @return {Bucket} + */ + 'public getRateBucket': function() + { + return this._rate_bucket; + }, + + + /** + * Set rating data + * + * @param {Object.} data rating data + */ + 'public setRatingData': function( data ) + { + if ( !this._rate_bucket ) + { + throw Error( "No rating bucket available for #setRatingData" ); + } + + this._rate_bucket.setValues( data ); + + return this; + }, + + + /** + * Get rating data + * + * @return {Object.} rating data + */ + 'public getRatingData': function() + { + if ( !this._rate_bucket ) + { + throw Error( "No rating bucket available for #setRatingData" ); + } + + return this._rate_bucket.getData(); + }, } ); diff --git a/src/server/request/DataProcessor.ts b/src/server/request/DataProcessor.ts index 3d549e0..7483132 100644 --- a/src/server/request/DataProcessor.ts +++ b/src/server/request/DataProcessor.ts @@ -18,7 +18,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +import { StagingBucket, StagingBucketConstructor } from "../../bucket/StagingBucket"; import { PositiveInteger } from "../../numeric"; +import { bucket_filter } from "../../bucket/bucket_filter"; import { UserRequest } from "./UserRequest"; /** @@ -41,10 +43,10 @@ export class DataProcessor * @param staging_ctor - staging bucket constructor */ constructor( - private readonly _filter: any, + private readonly _filter: bucket_filter.filter, private readonly _dapif: any, private readonly _meta_source: any, - private readonly _stagingCtor: any, + private readonly _stagingCtor: StagingBucketConstructor, ) {} @@ -169,7 +171,7 @@ export class DataProcessor dapi_manager: any, program: any, data: Record, - bucket: any, + bucket: StagingBucket, ): [ any, Record ] { const { @@ -292,7 +294,7 @@ export class DataProcessor */ private _mapDapiData( dapi: any, - bucket: any, + bucket: StagingBucket, index: PositiveInteger, diff_data: Record, ): Record diff --git a/test/server/quote/ServerSideQuoteTest.js b/test/server/quote/ServerSideQuoteTest.js index aea8cc8..8e92a67 100644 --- a/test/server/quote/ServerSideQuoteTest.js +++ b/test/server/quote/ServerSideQuoteTest.js @@ -21,8 +21,14 @@ 'use strict'; -const { expect } = require( 'chai' ); -const Sut = require( '../../..' ).server.quote.ServerSideQuote; +const root = require( '../../..' ); +const Sut = require( '../../..' ).server.quote.ServerSideQuote; +const expect = require( 'chai' ).expect; +const sinon = require( 'sinon' ); + +const { + QuoteDataBucket, +} = root.bucket; describe( 'ServerSideQuote', () => { @@ -31,80 +37,85 @@ describe( 'ServerSideQuote', () => [ { property: 'startDate', - default: 0, - value: 946684800 + default: 0, + value: 946684800 }, { property: 'initialRatedDate', - default: 0, - value: 946684800 + default: 0, + value: 946684800 }, { property: 'agentId', - default: 0, - value: 12345678 + default: 0, + value: 12345678 }, { property: 'agentEntityId', - default: 0, - value: 12345678 + default: 0, + value: 12345678 }, { property: 'agentName', - default: '', - value: 'name' + default: '', + value: 'name' }, { property: 'imported', - default: false, - value: true, + default: false, + value: true, accessor: 'is' }, { property: 'bound', - default: false, - value: true, + default: false, + value: true, accessor: 'is' }, { property: 'currentStepId', - default: 1, - value: 2 + default: 1, + value: 2 }, { property: 'topVisitedStepId', - default: 1, - value: 2 + default: 1, + value: 2 }, { property: 'topSavedStepId', - value: 1 + value: 1 }, { property: 'error', - default: '', - value: 'ERROR' + default: '', + value: 'ERROR' }, { property: 'programVersion', - default: '', - value: '1.0.0' + default: '', + value: '1.0.0' }, { property: 'creditScoreRef', - default: 0, - value: 800 + default: 0, + value: 800 }, { property: 'lastPremiumDate', - default: 0, - value: 946684800 + default: 0, + value: 946684800 }, { property: 'ratedDate', - default: 0, - value: 946684800 - } + default: 0, + value: 946684800 + }, + { + property: 'rateBucket', + default: null, + value: QuoteDataBucket() + }, ].forEach( testCase => { @@ -122,4 +133,44 @@ describe( 'ServerSideQuote', () => } ); } ); } ); -} ); \ No newline at end of file + + describe( 'rating data', () => + { + it( `#setRatingData throws an error if no bucket is set`, () => + { + const data = { foo: 'bar' }; + const sut = Sut(); + + expect( function() { sut.setRatingData( data ); } ) + .to.throw( Error ); + } ); + + it( `Bucket values setters/getters work correctly`, () => + { + const data = { foo: 'bar' }; + let bucket_data = null; + const sut = Sut(); + var called = false; + + const bucket = { + setValues( gdata ) + { + expect( gdata ).to.deep.equal( data ); + + bucket_data = gdata; + called = true; + }, + + getData() + { + return bucket_data; + }, + }; + + sut.setRateBucket( bucket ); + sut.setRatingData( data ); + + expect( called ).to.equal( true ); + } ); + } ); +} ); diff --git a/test/server/request/DataProcessorTest.ts b/test/server/request/DataProcessorTest.ts index faf69bf..361d99a 100644 --- a/test/server/request/DataProcessorTest.ts +++ b/test/server/request/DataProcessorTest.ts @@ -25,6 +25,7 @@ import { DocumentId } from "../../../src/document/Document"; import { PositiveInteger } from "../../../src/numeric"; import { UserRequest } from "../../../src/server/request/UserRequest"; import { ServerSideQuote } from "../../../src/server/quote/ServerSideQuote"; +import { QuoteDataBucket } from "../../../src/bucket/QuoteDataBucket"; chai_use( require( 'chai-as-promised' ) ); @@ -711,7 +712,7 @@ function createStubQuote() getBucket() { - return; + return new QuoteDataBucket(); } }; } diff --git a/test/server/service/RatingServiceTest.ts b/test/server/service/RatingServiceTest.ts index 1b7f7d7..3bc72c0 100644 --- a/test/server/service/RatingServiceTest.ts +++ b/test/server/service/RatingServiceTest.ts @@ -35,6 +35,7 @@ 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 { ServerDao, @@ -140,7 +141,7 @@ describe( 'RatingService', () => .then( () => expect( sent ).to.be.true ); } ); - it( "saves rate data to own field", () => + it( "saves rate data to it's own field", () => { const { logger, @@ -510,6 +511,10 @@ function getStubs() getLastPremiumDate: () => 0, getCurrentStepId: () => 0, setExplicitLock: () => quote, + setRateBucket: () => quote, + setRatingData: () => quote, + getRatingData: () => stub_rate_data, + getBucket: () => new QuoteDataBucket(), }; return {