Better server-side change detection, QuoteDataApi
commit
b7139bdc6a
|
@ -80,6 +80,12 @@ module.exports = Class( 'StagingBucket' )
|
||||||
*/
|
*/
|
||||||
'private _dirty': false,
|
'private _dirty': false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent setCommittedValues from bypassing staging
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
'private _noStagingBypass': false,
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes staging bucket with the provided data bucket
|
* Initializes staging bucket with the provided data bucket
|
||||||
|
@ -155,6 +161,11 @@ module.exports = Class( 'StagingBucket' )
|
||||||
*/
|
*/
|
||||||
'public setCommittedValues': function( data /*, ...*/ )
|
'public setCommittedValues': function( data /*, ...*/ )
|
||||||
{
|
{
|
||||||
|
if ( this._noStagingBypass )
|
||||||
|
{
|
||||||
|
return this.setValues.apply( this, arguments );
|
||||||
|
}
|
||||||
|
|
||||||
this._bucket.setValues.apply( this._bucket, arguments );
|
this._bucket.setValues.apply( this._bucket, arguments );
|
||||||
|
|
||||||
// no use in triggering a pre-update, since these values are
|
// no use in triggering a pre-update, since these values are
|
||||||
|
@ -165,6 +176,20 @@ module.exports = Class( 'StagingBucket' )
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent #setCommittedValues from bypassing staging
|
||||||
|
*
|
||||||
|
* When set, #setCommittedValues will act as an alias of #setValues.
|
||||||
|
*
|
||||||
|
* @return {StagingBucket} self
|
||||||
|
*/
|
||||||
|
'public forbidBypass'()
|
||||||
|
{
|
||||||
|
this._noStagingBypass = true;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether values have changed
|
* Determine whether values have changed
|
||||||
*
|
*
|
||||||
|
@ -179,31 +204,108 @@ module.exports = Class( 'StagingBucket' )
|
||||||
*/
|
*/
|
||||||
'private _hasChanged': function( data, merge_index )
|
'private _hasChanged': function( data, merge_index )
|
||||||
{
|
{
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
for ( let name in data )
|
for ( let name in data )
|
||||||
{
|
{
|
||||||
let values = data[ name ];
|
let values = data[ name ];
|
||||||
let cur = this._curdata[ name ] || [];
|
let cur = this._curdata[ name ] || [];
|
||||||
|
let len = this._length( values );
|
||||||
|
let has_null = ( len !== values.length );
|
||||||
|
|
||||||
if ( !merge_index && ( values.length !== cur.length ) )
|
let merge_len_change = (
|
||||||
|
merge_index && has_null && ( len < cur.length )
|
||||||
|
);
|
||||||
|
|
||||||
|
let replace_len_change = (
|
||||||
|
!merge_index && ( len !== cur.length )
|
||||||
|
);
|
||||||
|
|
||||||
|
// quick change check (index removal if merge_index, or index
|
||||||
|
// count change if not merge_index)
|
||||||
|
if ( merge_len_change || replace_len_change )
|
||||||
{
|
{
|
||||||
return true;
|
changed = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( let index in values )
|
for ( let index = 0; index < len; index++ )
|
||||||
{
|
{
|
||||||
if ( merge_index && ( values[ index ] === undefined ) )
|
if ( merge_index && ( values[ index ] === undefined ) )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( values[ index ] !== cur[ index ] )
|
if ( !this._deepEqual( values[ index ], cur[ index ] ) )
|
||||||
{
|
{
|
||||||
return true;
|
changed = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unchanged
|
||||||
|
values[ index ] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if nothing is left, remove entirely
|
||||||
|
if ( !values.some( x => x !== undefined ) )
|
||||||
|
{
|
||||||
|
delete data[ name ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get actual length of vector
|
||||||
|
*
|
||||||
|
* This considers when the last element of the vector is a null value,
|
||||||
|
* which is a truncation indicator.
|
||||||
|
*
|
||||||
|
* @param {Array} values value vector
|
||||||
|
*
|
||||||
|
* @return {number} length of vector considering truncation
|
||||||
|
*/
|
||||||
|
'private _length'( values )
|
||||||
|
{
|
||||||
|
if ( values[ values.length - 1 ] === null )
|
||||||
|
{
|
||||||
|
return values.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively check for equality of two vavlues
|
||||||
|
*
|
||||||
|
* This only recognizes nested arrays (vectors). They are not
|
||||||
|
* traditionally encountered in the bucket, but may exist.
|
||||||
|
*
|
||||||
|
* The final comparison is by string equality, since bucket values are
|
||||||
|
* traditionally strings.
|
||||||
|
*
|
||||||
|
* @param {*} a first vector or scalar
|
||||||
|
* @param {*} b second vector or scalar
|
||||||
|
*
|
||||||
|
* @return {boolean} whether `a` and `b` are equal
|
||||||
|
*/
|
||||||
|
'private _deepEqual'( a, b )
|
||||||
|
{
|
||||||
|
if ( Array.isArray( a ) )
|
||||||
|
{
|
||||||
|
if ( !Array.isArray( b ) || ( a.length !== b.length ) )
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.map( ( item, i ) => this._deepEqual( item, b[ i ] ) )
|
||||||
|
.every( res => res === true );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''+a === ''+b;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,14 @@ function _each( data, value, callback )
|
||||||
|
|
||||||
for ( var i = 0; i < data_len; i++ )
|
for ( var i = 0; i < data_len; i++ )
|
||||||
{
|
{
|
||||||
|
// index removals are null
|
||||||
|
if ( data[ i ] === null )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
cur_val = ( value[ i ] !== undefined ) ? value[ i ] : cur_val;
|
cur_val = ( value[ i ] !== undefined ) ? value[ i ] : cur_val;
|
||||||
|
|
||||||
result.push( callback( data[ i ], cur_val, i ) );
|
result.push( callback( data[ i ], cur_val, i ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +58,11 @@ exports.join = function( data, value )
|
||||||
{
|
{
|
||||||
return _each( data, value, function( arr, delimiter )
|
return _each( data, value, function( arr, delimiter )
|
||||||
{
|
{
|
||||||
|
if ( !Array.isArray( arr ) )
|
||||||
|
{
|
||||||
|
arr = [];
|
||||||
|
}
|
||||||
|
|
||||||
return arr.join( delimiter );
|
return arr.join( delimiter );
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -121,7 +133,11 @@ exports.length = function( data )
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push( item.length );
|
var len = ( item[ item.length - 1 ] === null )
|
||||||
|
? item.length - 1
|
||||||
|
: item.length;
|
||||||
|
|
||||||
|
result.push( len );
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -214,8 +230,8 @@ exports.date = function()
|
||||||
// formatted as
|
// formatted as
|
||||||
return [
|
return [
|
||||||
now.getFullYear() + '-'
|
now.getFullYear() + '-'
|
||||||
+ ( now.getMonth() + 1 ) + '-'
|
+ ( '0' + ( now.getMonth() + 1 ) ).substr( -2 ) + '-'
|
||||||
+ now.getDate()
|
+ ( '0' + now.getDate() ).substr( -2 )
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -277,8 +293,8 @@ exports.relativeDate = function( data, value )
|
||||||
// return in the YYYY-MM-DD format, since that's what our fields are
|
// return in the YYYY-MM-DD format, since that's what our fields are
|
||||||
// formatted as
|
// formatted as
|
||||||
return date_new.getFullYear() + '-'
|
return date_new.getFullYear() + '-'
|
||||||
+ ( date_new.getMonth() + 1 ) + '-'
|
+ ( '0' + ( date_new.getMonth() + 1 ) ).substr( -2 ) + '-'
|
||||||
+ date_new.getDate();
|
+ ( '0' + date_new.getDate() ).substr( -2 );
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -647,8 +663,43 @@ exports.value = function( data, indexes )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.repeat = function( data, value )
|
||||||
|
{
|
||||||
|
var times = value[ 0 ] || 0;
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
while ( times-- > 0 )
|
||||||
|
{
|
||||||
|
result.push( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.repeatConcat = function( data, value )
|
||||||
|
{
|
||||||
|
var times = value[ 0 ] || 0;
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
while ( times-- > 0 )
|
||||||
|
{
|
||||||
|
result = result.concat( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.index = function( data, value )
|
||||||
|
{
|
||||||
|
var index = value[ 0 ] || 0;
|
||||||
|
|
||||||
|
return data[ index ] || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
exports[ 'void' ] = function()
|
exports[ 'void' ] = function()
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Interface = require( 'easejs' ).Interface;
|
'use strict';
|
||||||
|
|
||||||
|
const { Interface } = require( 'easejs' );
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +44,8 @@ module.exports = Interface( 'DataApi',
|
||||||
* The first parameter of the callback shall contain an Error in the event
|
* The first parameter of the callback shall contain an Error in the event
|
||||||
* of a failure; otherwise, it shall be null.
|
* of a failure; otherwise, it shall be null.
|
||||||
*
|
*
|
||||||
* @param {Object=} data request params
|
* @param {?Object<string,string>|string} data params or post data
|
||||||
* @param {function(?Error,*)} callback continuation upon reply
|
* @param {function(?Error,*):string} callback continuation upon reply
|
||||||
*
|
*
|
||||||
* @return {DataApi} self
|
* @return {DataApi} self
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,13 +19,17 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
const Class = require( 'easejs' ).Class;
|
const Class = require( 'easejs' ).Class;
|
||||||
const HttpDataApi = require( './http/HttpDataApi' );
|
const HttpDataApi = require( './http/HttpDataApi' );
|
||||||
const XhrHttpImpl = require( './http/XhrHttpImpl' );
|
const XhrHttpImpl = require( './http/XhrHttpImpl' );
|
||||||
const JsonResponse = require( './format/JsonResponse' );
|
const JsonResponse = require( './format/JsonResponse' );
|
||||||
|
const ResponseApply = require( './format/ResponseApply' );
|
||||||
const RestrictedDataApi = require( './RestrictedDataApi' );
|
const RestrictedDataApi = require( './RestrictedDataApi' );
|
||||||
const StaticAdditionDataApi = require( './StaticAdditionDataApi' );
|
const StaticAdditionDataApi = require( './StaticAdditionDataApi' );
|
||||||
const BucketDataApi = require( './BucketDataApi' );
|
const BucketDataApi = require( './BucketDataApi' );
|
||||||
|
const QuoteDataApi = require( './QuoteDataApi' );
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,8 +40,8 @@ module.exports = Class( 'DataApiFactory',
|
||||||
/**
|
/**
|
||||||
* Return a DataApi instance for the requested service type
|
* Return a DataApi instance for the requested service type
|
||||||
*
|
*
|
||||||
* The source and method have type-specific meaning; that is, "source" may
|
* The source and method have type-specific meaning; that is, "source"
|
||||||
* be a URL and "method" may be get/post for a RESTful service.
|
* may be a URL and "method" may be get/post for a RESTful service.
|
||||||
*
|
*
|
||||||
* @param {string} type service type (e.g. "rest")
|
* @param {string} type service type (e.g. "rest")
|
||||||
* @param {Object} desc API description
|
* @param {Object} desc API description
|
||||||
|
@ -46,39 +50,11 @@ module.exports = Class( 'DataApiFactory',
|
||||||
*/
|
*/
|
||||||
'public fromType': function( type, desc, bucket )
|
'public fromType': function( type, desc, bucket )
|
||||||
{
|
{
|
||||||
var api = null,
|
const static_data = ( desc['static'] || [] );
|
||||||
source = ( desc.source || '' ),
|
const nonempty = !!desc.static_nonempty;
|
||||||
method = ( desc.method || '' ),
|
const multiple = !!desc.static_multiple;
|
||||||
|
|
||||||
static_data = ( desc['static'] || [] ),
|
const api = this._createDataApi( type, desc, bucket );
|
||||||
nonempty = !!desc.static_nonempty,
|
|
||||||
multiple = !!desc.static_multiple;
|
|
||||||
|
|
||||||
switch ( type )
|
|
||||||
{
|
|
||||||
case 'rest':
|
|
||||||
const impl = this.createHttpImpl();
|
|
||||||
|
|
||||||
api = HttpDataApi.use( JsonResponse )(
|
|
||||||
source,
|
|
||||||
method.toUpperCase(),
|
|
||||||
impl
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'local':
|
|
||||||
// currently, only local bucket data sources are supported
|
|
||||||
if ( source !== 'bucket' )
|
|
||||||
{
|
|
||||||
throw Error( "Unknown local data API source: " + source );
|
|
||||||
}
|
|
||||||
|
|
||||||
api = BucketDataApi( bucket, desc.retvals );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw Error( 'Unknown data API type: ' + type );
|
|
||||||
};
|
|
||||||
|
|
||||||
return RestrictedDataApi(
|
return RestrictedDataApi(
|
||||||
StaticAdditionDataApi( api, nonempty, multiple, static_data ),
|
StaticAdditionDataApi( api, nonempty, multiple, static_data ),
|
||||||
|
@ -87,6 +63,90 @@ module.exports = Class( 'DataApiFactory',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create DataApi instance
|
||||||
|
*
|
||||||
|
* @param {string} type API type
|
||||||
|
* @param {Object} desc API descriptor
|
||||||
|
* @param {Bucket} bucket data bucket
|
||||||
|
*
|
||||||
|
* @return {DataApi}
|
||||||
|
*/
|
||||||
|
'private _createDataApi'( type, desc, bucket )
|
||||||
|
{
|
||||||
|
const source = ( desc.source || '' );
|
||||||
|
const method = ( desc.method || '' );
|
||||||
|
const enctype = ( desc.enctype || '' );
|
||||||
|
|
||||||
|
switch ( type )
|
||||||
|
{
|
||||||
|
case 'rest':
|
||||||
|
return this._createHttp(
|
||||||
|
HttpDataApi.use( JsonResponse ),
|
||||||
|
source,
|
||||||
|
method,
|
||||||
|
enctype
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'local':
|
||||||
|
// currently, only local bucket data sources are supported
|
||||||
|
if ( source !== 'bucket' )
|
||||||
|
{
|
||||||
|
throw Error( "Unknown local data API source: " + source );
|
||||||
|
}
|
||||||
|
|
||||||
|
return BucketDataApi( bucket, desc.retvals );
|
||||||
|
|
||||||
|
case 'quote':
|
||||||
|
return QuoteDataApi(
|
||||||
|
this._createHttp(
|
||||||
|
HttpDataApi
|
||||||
|
.use( JsonResponse )
|
||||||
|
.use( ResponseApply( data => [ data ] ) ),
|
||||||
|
source,
|
||||||
|
method,
|
||||||
|
enctype
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Error( 'Unknown data API type: ' + type );
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HttpDataApi instance
|
||||||
|
*
|
||||||
|
* The `Base` is intended to allow for the caller to mix traits in.
|
||||||
|
*
|
||||||
|
* @param {HttpDataApi} Base HttpDataApi type
|
||||||
|
* @param {string} source URL
|
||||||
|
* @param {string} method HTTP method
|
||||||
|
* @param {string} enctype MIME media type (for POST)
|
||||||
|
*
|
||||||
|
* @return {HttpDataApi}
|
||||||
|
*/
|
||||||
|
'private _createHttp'( Base, source, method, enctype )
|
||||||
|
{
|
||||||
|
const impl = this.createHttpImpl();
|
||||||
|
|
||||||
|
return Base(
|
||||||
|
source,
|
||||||
|
method.toUpperCase(),
|
||||||
|
impl,
|
||||||
|
enctype
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HttpImpl
|
||||||
|
*
|
||||||
|
* This is simply intended to allow subtypes to override the type.
|
||||||
|
*
|
||||||
|
* @return {XhrHttpImpl}
|
||||||
|
*/
|
||||||
'virtual protected createHttpImpl'()
|
'virtual protected createHttpImpl'()
|
||||||
{
|
{
|
||||||
return XhrHttpImpl( XMLHttpRequest );
|
return XhrHttpImpl( XMLHttpRequest );
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
/**
|
||||||
|
* Transform key/value data into standard quote request
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This is insurance-specific using a standardized request format for
|
||||||
|
* producing insurance quote data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
const DataApi = require( './DataApi' );
|
||||||
|
const EventEmitter = require( 'events' ).EventEmitter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure flat key/value data for quote request
|
||||||
|
*
|
||||||
|
* The request structure can be seen in #mapData. No fields are required to
|
||||||
|
* be present---they all have defaults; the philosophy is to allow the
|
||||||
|
* server to fail if necessary. Basic validations (e.g. ensuring correct
|
||||||
|
* data type and format) may be added in the future.
|
||||||
|
*
|
||||||
|
* This DataApi is responsible only for data transformation---it is expected
|
||||||
|
* to decorate a DataApi capable of performing an actual data transfer.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'QuoteDataApi' )
|
||||||
|
.implement( DataApi )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decorated DataApi
|
||||||
|
*
|
||||||
|
* @type {DataApi}
|
||||||
|
*/
|
||||||
|
'private _dapi': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize with DataApi to decorate
|
||||||
|
*
|
||||||
|
* @param {DataApi} dapi subject to decorate
|
||||||
|
*/
|
||||||
|
constructor( dapi )
|
||||||
|
{
|
||||||
|
if ( !( Class.isA( DataApi, dapi ) ) )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
'Expected object of type DataApi; given: ' + data_api
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dapi = dapi;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request data from the service
|
||||||
|
*
|
||||||
|
* @param {Object=} data request params
|
||||||
|
* @param {function(?Error,Object)=} callback server response callback
|
||||||
|
*
|
||||||
|
* @return {DataApi} self
|
||||||
|
*/
|
||||||
|
'public request'( data, callback )
|
||||||
|
{
|
||||||
|
this._dapi.request( this.mapData( data ), callback );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map key/value data into quote request
|
||||||
|
*
|
||||||
|
* @param {Object} data key/value data
|
||||||
|
*
|
||||||
|
* @return {Object} mapped request data
|
||||||
|
*/
|
||||||
|
'protected mapData'( data )
|
||||||
|
{
|
||||||
|
const rate_date = data.rate_date || data.effective_date || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
"effective_date": this._formatDate( data.effective_date || "" ),
|
||||||
|
"rate_date": this._formatDate( rate_date ),
|
||||||
|
"insured": {
|
||||||
|
"location": {
|
||||||
|
"city": data.insured_city || "",
|
||||||
|
"state": data.insured_state || "",
|
||||||
|
"zip": data.insured_zip || "",
|
||||||
|
"county": data.insured_county || "",
|
||||||
|
},
|
||||||
|
"business_year_count": +data.business_year_count || 0,
|
||||||
|
},
|
||||||
|
"coverages": ( data.classes || [] ).map(
|
||||||
|
( class_code, i ) => ( {
|
||||||
|
"class": class_code,
|
||||||
|
"limit": {
|
||||||
|
"occurrence": +( data.limit_occurrence || 0 ),
|
||||||
|
"aggregate": +( data.limit_aggregate || 0 ),
|
||||||
|
},
|
||||||
|
"exposure": +( data.exposure || [] )[ i ] || 0,
|
||||||
|
} )
|
||||||
|
),
|
||||||
|
"losses": ( data.loss_type || [] ).map(
|
||||||
|
loss_type => ( {
|
||||||
|
type: loss_type,
|
||||||
|
} )
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append time to ISO 8601 date+time format
|
||||||
|
*
|
||||||
|
* This is required by some services.
|
||||||
|
*
|
||||||
|
* @type {string} date ISO 8601 date (without time)
|
||||||
|
*
|
||||||
|
* @return {string} ISO 8601 combined date and time
|
||||||
|
*/
|
||||||
|
'private _formatDate'( date )
|
||||||
|
{
|
||||||
|
return ( date === "" )
|
||||||
|
? ""
|
||||||
|
: ( date + "T00:00:00" );
|
||||||
|
},
|
||||||
|
} );
|
|
@ -85,7 +85,7 @@ module.exports = Class( 'RestrictedDataApi' )
|
||||||
*
|
*
|
||||||
* @return {DataApi} self
|
* @return {DataApi} self
|
||||||
*/
|
*/
|
||||||
'public request': function( data, callback )
|
'virtual public request': function( data, callback )
|
||||||
{
|
{
|
||||||
data = data || {};
|
data = data || {};
|
||||||
callback = callback || function() {};
|
callback = callback || function() {};
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Applies arbitrary function to response data
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Trait = require( 'easejs' ).Trait,
|
||||||
|
DataApi = require( '../DataApi' );
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Trait( 'ResponseApply' )
|
||||||
|
.implement( DataApi )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Function to apply to data
|
||||||
|
*
|
||||||
|
* @type {function(*)}
|
||||||
|
*/
|
||||||
|
'private _dataf': () => {},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize with function to apply to return data
|
||||||
|
*
|
||||||
|
* @param {function(*)} req_callback return data function
|
||||||
|
*/
|
||||||
|
__mixin( dataf )
|
||||||
|
{
|
||||||
|
if ( typeof dataf !== 'function' )
|
||||||
|
{
|
||||||
|
throw TypeError( 'expected function for #request callback' );
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dataf = dataf;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply function to response
|
||||||
|
*
|
||||||
|
* The function provided during mixin will be applied to the response
|
||||||
|
* data to produce a new response.
|
||||||
|
*
|
||||||
|
* It is not recommended to use this trait for complex transformations;
|
||||||
|
* a new trait should be created instead.
|
||||||
|
*
|
||||||
|
* @param {string} data binary data to transmit
|
||||||
|
* @param {function(?Error,*)} callback continuation upon reply
|
||||||
|
*
|
||||||
|
* @return {DataApi} self
|
||||||
|
*/
|
||||||
|
'virtual abstract override public request'( data, callback )
|
||||||
|
{
|
||||||
|
this.__super( data, ( e, retdata ) =>
|
||||||
|
{
|
||||||
|
callback( e, this._dataf( retdata ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
} );
|
|
@ -19,12 +19,14 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Class = require( 'easejs' ).Class,
|
'use strict';
|
||||||
DataApi = require( '../DataApi' ),
|
|
||||||
HttpImpl = require( './HttpImpl' ),
|
const { Class } = require( 'easejs' );
|
||||||
|
const DataApi = require( '../DataApi' );
|
||||||
|
const HttpImpl = require( './HttpImpl' );
|
||||||
|
|
||||||
// RFC 2616 methods
|
// RFC 2616 methods
|
||||||
rfcmethods = {
|
const rfcmethods = {
|
||||||
DELETE: true,
|
DELETE: true,
|
||||||
GET: true,
|
GET: true,
|
||||||
HEAD: true,
|
HEAD: true,
|
||||||
|
@ -61,6 +63,12 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
*/
|
*/
|
||||||
'private _impl': null,
|
'private _impl': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIME media type
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
'private _enctype': '',
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Data API with destination and HTTP implementation
|
* Initialize Data API with destination and HTTP implementation
|
||||||
|
@ -69,15 +77,18 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
* requests, which permits the user to use whatever implementation works
|
* requests, which permits the user to use whatever implementation works
|
||||||
* well with their existing system.
|
* well with their existing system.
|
||||||
*
|
*
|
||||||
|
* Default `enctype` is `application/x-www-form-urlencoded`.
|
||||||
|
*
|
||||||
* TODO: Accept URI encoder.
|
* TODO: Accept URI encoder.
|
||||||
*
|
*
|
||||||
* @param {string} url destination URL
|
* @param {string} url destination URL
|
||||||
* @param {string} method RFC-2616-compliant HTTP method
|
* @param {string} method RFC-2616-compliant HTTP method
|
||||||
* @param {HttpImpl} impl HTTP implementation
|
* @param {HttpImpl} impl HTTP implementation
|
||||||
|
* @param {string=} enctype MIME media type
|
||||||
*
|
*
|
||||||
* @throws {TypeError} when non-HttpImpl is provided
|
* @throws {TypeError} when non-HttpImpl is provided
|
||||||
*/
|
*/
|
||||||
__construct: function( url, method, impl )
|
__construct: function( url, method, impl, enctype )
|
||||||
{
|
{
|
||||||
if ( !( Class.isA( HttpImpl, impl ) ) )
|
if ( !( Class.isA( HttpImpl, impl ) ) )
|
||||||
{
|
{
|
||||||
|
@ -87,6 +98,10 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
this._url = ''+url;
|
this._url = ''+url;
|
||||||
this._method = this._validateMethod( method );
|
this._method = this._validateMethod( method );
|
||||||
this._impl = impl;
|
this._impl = impl;
|
||||||
|
|
||||||
|
this._enctype = ( enctype )
|
||||||
|
? ''+enctype
|
||||||
|
: 'application/x-www-form-urlencoded';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,7 +142,7 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
this._impl.requestData(
|
this._impl.requestData(
|
||||||
this._url,
|
this._url,
|
||||||
this._method,
|
this._method,
|
||||||
this._encodeData( data ),
|
this.encodeData( data ),
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -164,7 +179,7 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
*/
|
*/
|
||||||
'private _validateDataType': function( data )
|
'private _validateDataType': function( data )
|
||||||
{
|
{
|
||||||
var type = typeof data;
|
const type = typeof data;
|
||||||
|
|
||||||
if( !( ( type === 'string' ) || ( type === 'object' ) ) )
|
if( !( ( type === 'string' ) || ( type === 'object' ) ) )
|
||||||
{
|
{
|
||||||
|
@ -177,51 +192,57 @@ module.exports = Class( 'HttpDataApi' )
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the data are an object, it's converted to an encoded key-value
|
* Generate params for URI from key-value `data`
|
||||||
* URI; otherwise, the original string datum is returned.
|
|
||||||
*
|
*
|
||||||
* @param {?Object<string,string>|string=} data raw data or key-value
|
* Conversion depends on the MIME type (enctype) with which this instance
|
||||||
|
* was initialized. For example, `application/x-www-form-urlencoded`
|
||||||
|
* will result in a urlencoded string, whereas `application/json` will
|
||||||
|
* simply be serialized.
|
||||||
*
|
*
|
||||||
* @return {string} encoded data
|
* If `data` is not an object, it will be returned as a string datum.
|
||||||
|
*
|
||||||
|
* @param {Object<string,string>|string} data key-value request params
|
||||||
|
*
|
||||||
|
* @return {string} generated URI, or empty if no keys
|
||||||
*/
|
*/
|
||||||
'private _encodeData': function( data )
|
'protected encodeData': function( data )
|
||||||
{
|
{
|
||||||
if ( typeof data !== 'object' )
|
if ( typeof data !== 'object' )
|
||||||
{
|
{
|
||||||
return ''+data;
|
return ''+data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._encodeKeys( data );
|
if ( this._method !== 'POST' )
|
||||||
|
{
|
||||||
|
return this._urlEncode( data );
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( this._enctype )
|
||||||
|
{
|
||||||
|
case 'application/x-www-form-urlencoded':
|
||||||
|
return this._urlEncode( data );
|
||||||
|
|
||||||
|
case 'application/json':
|
||||||
|
return JSON.stringify( data );
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw Error( 'Unknown enctype for POST: ' + this._enctype );
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate params for URI from key-value DATA
|
* urlencode each key of provided object
|
||||||
*
|
*
|
||||||
* @param {Object<string,string>} data key-value request params
|
* @param {Object} obj key/value
|
||||||
*
|
*
|
||||||
* @return {string} generated URI, or empty if no keys
|
* @return {string} urlencoded string, joined with '&'
|
||||||
*/
|
*/
|
||||||
'private _encodeKeys': function( obj )
|
'private _urlEncode'( obj )
|
||||||
{
|
{
|
||||||
var uri = '';
|
return Object.keys( obj ).map( key =>
|
||||||
|
encodeURIComponent( key ) + '=' +
|
||||||
// ES3 support
|
encodeURIComponent( obj[ key ] )
|
||||||
for ( var key in obj )
|
).join( '&' );
|
||||||
{
|
},
|
||||||
if ( !Object.prototype.hasOwnProperty.call( obj, key ) )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uri += ( uri )
|
|
||||||
? '&'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
uri += encodeURIComponent( key ) + '=' +
|
|
||||||
encodeURIComponent( obj[ key ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -25,6 +25,7 @@ const {
|
||||||
bucket: {
|
bucket: {
|
||||||
bucket_filter,
|
bucket_filter,
|
||||||
QuoteDataBucket,
|
QuoteDataBucket,
|
||||||
|
StagingBucket,
|
||||||
},
|
},
|
||||||
|
|
||||||
dapi: {
|
dapi: {
|
||||||
|
@ -67,7 +68,8 @@ module.exports = Class( 'DocumentServer',
|
||||||
),
|
),
|
||||||
apis
|
apis
|
||||||
),
|
),
|
||||||
DapiMetaSource( QuoteDataBucket )
|
DapiMetaSource( QuoteDataBucket ),
|
||||||
|
StagingBucket
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -1140,12 +1140,7 @@ module.exports = Class( 'Server' )
|
||||||
parsed_data, request, program, bucket
|
parsed_data, request, program, bucket
|
||||||
);
|
);
|
||||||
|
|
||||||
quote.setData( filtered );
|
|
||||||
|
|
||||||
server._monitorMetadataPromise( quote, dapis );
|
server._monitorMetadataPromise( quote, dapis );
|
||||||
|
|
||||||
// calculated values (store only)
|
|
||||||
program.initQuote( bucket, true );
|
|
||||||
}
|
}
|
||||||
catch ( err )
|
catch ( err )
|
||||||
{
|
{
|
||||||
|
@ -1185,11 +1180,10 @@ module.exports = Class( 'Server' )
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.catch( e =>
|
.catch( e =>
|
||||||
server.logger.log(
|
this.logger.log(
|
||||||
server.logger.PRIORITY_ERROR,
|
this.logger.PRIORITY_ERROR,
|
||||||
"Failed to save field %s[%s] metadata: %s",
|
"Failed to save metadata (quote id %d): %s",
|
||||||
field,
|
quote.getId(),
|
||||||
index,
|
|
||||||
e.message
|
e.message
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
const { Class } = require( 'easejs' );
|
const { Class } = require( 'easejs' );
|
||||||
|
|
||||||
const { QuoteDataBucket } = require( '../../' ).bucket;
|
const { QuoteDataBucket, StagingBucket } = require( '../../' ).bucket;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,15 +56,20 @@ module.exports = Class( 'DataProcessor',
|
||||||
/**
|
/**
|
||||||
* Initialize processor
|
* Initialize processor
|
||||||
*
|
*
|
||||||
|
* The staging bucket constructor will be used to wrap the bucket for
|
||||||
|
* diff-related operations.
|
||||||
|
*
|
||||||
* @param {Object} filter bucket filter
|
* @param {Object} filter bucket filter
|
||||||
* @param {function()} dapif data API constructor
|
* @param {function()} dapif data API constructor
|
||||||
* @param {DapiMetaSource} meta_source metadata source
|
* @param {DapiMetaSource} meta_source metadata source
|
||||||
|
* @param {function(Bucket)} staging_ctor staging bucket constructor
|
||||||
*/
|
*/
|
||||||
constructor( filter, dapif, meta_source )
|
constructor( filter, dapif, meta_source, staging_ctor )
|
||||||
{
|
{
|
||||||
this._filter = filter;
|
this._filter = filter;
|
||||||
this._dapif = dapif;
|
this._dapif = dapif;
|
||||||
this._metaSource = meta_source;
|
this._metaSource = meta_source;
|
||||||
|
this._stagingCtor = staging_ctor;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,12 +91,21 @@ module.exports = Class( 'DataProcessor',
|
||||||
{
|
{
|
||||||
const filtered = this.sanitizeDiff( data, request, program, false );
|
const filtered = this.sanitizeDiff( data, request, program, false );
|
||||||
const dapi_manager = this._dapif( program.apis, request );
|
const dapi_manager = this._dapif( program.apis, request );
|
||||||
|
const staging = this._stagingCtor( bucket );
|
||||||
|
|
||||||
|
// forbidBypass will force diff generation on initQuote
|
||||||
|
staging.setValues( filtered, true );
|
||||||
|
staging.forbidBypass();
|
||||||
|
|
||||||
|
program.initQuote( staging, true );
|
||||||
|
|
||||||
// array of promises for any dapi requests
|
// array of promises for any dapi requests
|
||||||
const dapis = this._triggerDapis(
|
const dapis = this._triggerDapis(
|
||||||
dapi_manager, program, data, bucket
|
dapi_manager, program, staging.getDiff(), staging
|
||||||
);
|
);
|
||||||
|
|
||||||
|
staging.commit();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filtered: filtered,
|
filtered: filtered,
|
||||||
dapis: dapis,
|
dapis: dapis,
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
const { Class } = require( 'easejs' );
|
const { Class } = require( 'easejs' );
|
||||||
const root = require( '../../' );
|
const root = require( '../../' );
|
||||||
const expect = require( 'chai' ).expect;
|
const expect = require( 'chai' ).expect;
|
||||||
|
const sinon = require( 'sinon' );
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Bucket,
|
Bucket,
|
||||||
|
@ -111,6 +112,30 @@ describe( 'StagingBucket', () =>
|
||||||
merge_index: true,
|
merge_index: true,
|
||||||
is_change: true,
|
is_change: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
initial: { foo: [ 'bar', 'baz' ] },
|
||||||
|
update: { foo: [ 'bar', 'baz', null ] },
|
||||||
|
merge_index: true,
|
||||||
|
is_change: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initial: { foo: [ 'bar', 'baz' ] },
|
||||||
|
update: { foo: [ 'bar', 'baz', null ] },
|
||||||
|
merge_index: false,
|
||||||
|
is_change: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initial: { foo: [ 'bar', 'baz' ] },
|
||||||
|
update: { foo: [ 'bar', 'baz', 'quux' ] },
|
||||||
|
merge_index: true,
|
||||||
|
is_change: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
initial: { foo: [ 'bar', 'baz' ] },
|
||||||
|
update: { foo: [ 'bar', 'baz', 'quux' ] },
|
||||||
|
merge_index: false,
|
||||||
|
is_change: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
initial: { foo: [ 'bar', 'baz' ] },
|
initial: { foo: [ 'bar', 'baz' ] },
|
||||||
update: { foo: [] },
|
update: { foo: [] },
|
||||||
|
@ -161,6 +186,48 @@ describe( 'StagingBucket', () =>
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
describe( "#setCommittedValues", () =>
|
||||||
|
{
|
||||||
|
it( "bypasses staging bucket without no bypass flag", () =>
|
||||||
|
{
|
||||||
|
const b = createStubBucket();
|
||||||
|
const bmock = sinon.mock( b );
|
||||||
|
const data = { foo: [ "bar" ] };
|
||||||
|
const sut = Sut( b );
|
||||||
|
|
||||||
|
bmock.expects( 'setValues' )
|
||||||
|
.once()
|
||||||
|
.withExactArgs( data );
|
||||||
|
|
||||||
|
sut.setCommittedValues( data );
|
||||||
|
|
||||||
|
// no diff if bypassed
|
||||||
|
expect( sut.getDiff() ).to.deep.equal( {} );
|
||||||
|
|
||||||
|
bmock.verify();
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( "does not bypasses staging bucket with no bypass flag", () =>
|
||||||
|
{
|
||||||
|
const b = createStubBucket();
|
||||||
|
const bmock = sinon.mock( b );
|
||||||
|
const data = { foo: [ "bar" ] };
|
||||||
|
const sut = Sut( b );
|
||||||
|
|
||||||
|
bmock.expects( 'setValues' ).never();
|
||||||
|
|
||||||
|
sut.forbidBypass();
|
||||||
|
sut.setCommittedValues( data );
|
||||||
|
|
||||||
|
// should have been staged
|
||||||
|
expect( sut.getDiff() ).to.deep.equal( data );
|
||||||
|
|
||||||
|
bmock.verify();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* Applies arbitrary function to response data
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
const { DataApi } = require( '../../' ).dapi;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy DataApi implementation for testing
|
||||||
|
*
|
||||||
|
* This should not be used in production.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'DummyDataApi' )
|
||||||
|
.implement( DataApi )
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* #request callback
|
||||||
|
*
|
||||||
|
* @type {Function} #request method callback
|
||||||
|
*/
|
||||||
|
'private _reqCallback': () => {},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize with `#request` method callback
|
||||||
|
*
|
||||||
|
* @param {Function} req_callback #request method callback
|
||||||
|
*/
|
||||||
|
constructor( req_callback )
|
||||||
|
{
|
||||||
|
this._reqCallback = req_callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy method that invokes the callback provided via constructor
|
||||||
|
*
|
||||||
|
* @param {?Object<string,string>|string} data request params or post data
|
||||||
|
* @param {function(?Error,*):string} callback continuation upon reply
|
||||||
|
*
|
||||||
|
* @return {DataApi} self
|
||||||
|
*/
|
||||||
|
'virtual public request'( data, callback )
|
||||||
|
{
|
||||||
|
this._reqCallback( data, callback );
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
} );
|
|
@ -0,0 +1,146 @@
|
||||||
|
/**
|
||||||
|
* Tests QuoteDataApi
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { expect } = require( 'chai' );
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
const DummyDataApi = require( './DummyDataApi' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
DataApi,
|
||||||
|
QuoteDataApi: Sut
|
||||||
|
} = require( '../../' ).dapi;
|
||||||
|
|
||||||
|
|
||||||
|
describe( 'QuoteDataApi', () =>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
// empty request; use defaults
|
||||||
|
{
|
||||||
|
given: {},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
"effective_date": "",
|
||||||
|
"rate_date": "",
|
||||||
|
"insured": {
|
||||||
|
"location": {
|
||||||
|
"city": "",
|
||||||
|
"state": "",
|
||||||
|
"zip": "",
|
||||||
|
"county": ""
|
||||||
|
},
|
||||||
|
"business_year_count": 0,
|
||||||
|
},
|
||||||
|
"coverages": [],
|
||||||
|
"losses": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// empty coverage
|
||||||
|
{
|
||||||
|
given: {
|
||||||
|
classes: [ "11111" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
"effective_date": "",
|
||||||
|
"rate_date": "",
|
||||||
|
"insured": {
|
||||||
|
"location": {
|
||||||
|
"city": "",
|
||||||
|
"state": "",
|
||||||
|
"zip": "",
|
||||||
|
"county": ""
|
||||||
|
},
|
||||||
|
"business_year_count": 0,
|
||||||
|
},
|
||||||
|
"coverages": [
|
||||||
|
{
|
||||||
|
"class": "11111",
|
||||||
|
"limit": {
|
||||||
|
"occurrence": 0,
|
||||||
|
"aggregate": 0,
|
||||||
|
},
|
||||||
|
"exposure": 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"losses": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// full request
|
||||||
|
{
|
||||||
|
given: {
|
||||||
|
effective_date: "12345",
|
||||||
|
rate_date: "2345",
|
||||||
|
insured_city: "Buffalo",
|
||||||
|
insured_state: "NY",
|
||||||
|
insured_zip: "14043",
|
||||||
|
insured_county: "Erie",
|
||||||
|
business_year_count: "1",
|
||||||
|
classes: [ "11111", "11112" ],
|
||||||
|
limit_occurrence: "100",
|
||||||
|
limit_aggregate: "200",
|
||||||
|
exposure: [ "200", "300" ],
|
||||||
|
loss_type: [ "gl", "property" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
"effective_date": "12345T00:00:00",
|
||||||
|
"rate_date": "2345T00:00:00",
|
||||||
|
"insured": {
|
||||||
|
"location": {
|
||||||
|
"city": "Buffalo",
|
||||||
|
"state": "NY",
|
||||||
|
"zip": "14043",
|
||||||
|
"county": "Erie"
|
||||||
|
},
|
||||||
|
"business_year_count": 1,
|
||||||
|
},
|
||||||
|
"coverages": [
|
||||||
|
{
|
||||||
|
"class": "11111",
|
||||||
|
"limit": {
|
||||||
|
"occurrence": 100,
|
||||||
|
"aggregate": 200,
|
||||||
|
},
|
||||||
|
"exposure": 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class": "11112",
|
||||||
|
"limit": {
|
||||||
|
"occurrence": 100,
|
||||||
|
"aggregate": 200,
|
||||||
|
},
|
||||||
|
"exposure": 300,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"losses": [
|
||||||
|
{ type: "gl" },
|
||||||
|
{ type: "property" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
].forEach( ( { given, expected }, i ) => {
|
||||||
|
it( `maps input data to structured object (#${i})`, done =>
|
||||||
|
{
|
||||||
|
const dummyc = () => {};
|
||||||
|
|
||||||
|
const mock_dapi = DummyDataApi( ( data, callback ) =>
|
||||||
|
{
|
||||||
|
expect( data ).to.deep.equal( expected );
|
||||||
|
expect( callback ).to.equal( dummyc );
|
||||||
|
|
||||||
|
done();
|
||||||
|
} );
|
||||||
|
|
||||||
|
const sut = Sut( mock_dapi );
|
||||||
|
|
||||||
|
sut.request( given, dummyc );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Tests ResponseApply
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { expect } = require( 'chai' );
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
const DummyDataApi = require( '../DummyDataApi' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
DataApi,
|
||||||
|
format: {
|
||||||
|
ResponseApply: Sut
|
||||||
|
},
|
||||||
|
} = require( '../../../' ).dapi;
|
||||||
|
|
||||||
|
|
||||||
|
describe( 'ResponseApply', () =>
|
||||||
|
{
|
||||||
|
it( 'applies function to response', done =>
|
||||||
|
{
|
||||||
|
const expected = {};
|
||||||
|
const given = { given: 'data' };
|
||||||
|
|
||||||
|
const f = src =>
|
||||||
|
{
|
||||||
|
expect( src ).to.equal( given );
|
||||||
|
return expected;
|
||||||
|
};
|
||||||
|
|
||||||
|
DummyDataApi.use( Sut( f ) )( ( _, c ) => c( null, given ) )
|
||||||
|
.request( given, ( e, data ) =>
|
||||||
|
{
|
||||||
|
expect( data ).to.equal( expected );
|
||||||
|
done();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( 'returns self', () =>
|
||||||
|
{
|
||||||
|
const sut = DummyDataApi.use( Sut( _ => {} ) )( _ => {} );
|
||||||
|
|
||||||
|
expect( sut.request( {}, () => {} ) )
|
||||||
|
.to.equal( sut );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -105,7 +105,7 @@ describe( 'HttpDataApi', function()
|
||||||
*/
|
*/
|
||||||
it( 'delegates to provided HTTP implementation', function()
|
it( 'delegates to provided HTTP implementation', function()
|
||||||
{
|
{
|
||||||
var method = 'POST',
|
var method = 'GET',
|
||||||
data = "ribbit",
|
data = "ribbit",
|
||||||
c = function() {};
|
c = function() {};
|
||||||
|
|
||||||
|
@ -119,37 +119,58 @@ describe( 'HttpDataApi', function()
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
||||||
/**
|
[
|
||||||
* It's nice to do this for the HttpImpl so that they don't have to
|
// default is urlencoded
|
||||||
* worry about the proper way to handle it, or duplicate the logic.
|
|
||||||
*/
|
|
||||||
describe( 'given key-value data', function()
|
|
||||||
{
|
{
|
||||||
it( 'converts data into encoded string', function()
|
enctype: '',
|
||||||
{
|
method: 'POST',
|
||||||
var method = 'POST',
|
data: { foo: "bar=baz", '&bar': "moo%cow" },
|
||||||
data = { foo: "bar=baz", '&bar': "moo%cow" },
|
expected: 'foo=' + encodeURIComponent( 'bar=baz' ) +
|
||||||
c = function() {};
|
|
||||||
|
|
||||||
Sut( dummy_url, method, impl ).request( data, c );
|
|
||||||
|
|
||||||
expect( impl.provided[ 2 ] ).to.equal(
|
|
||||||
'foo=' + encodeURIComponent( data.foo ) +
|
|
||||||
'&' + encodeURIComponent( '&bar' ) + '=' +
|
'&' + encodeURIComponent( '&bar' ) + '=' +
|
||||||
encodeURIComponent( data[ '&bar' ] )
|
encodeURIComponent( 'moo%cow' )
|
||||||
);
|
},
|
||||||
} );
|
|
||||||
|
|
||||||
|
// same as above
|
||||||
it( 'with no keys, results in empty string', function()
|
|
||||||
{
|
{
|
||||||
var method = 'POST',
|
enctype: 'application/x-www-form-urlencoded',
|
||||||
data = {},
|
method: 'POST',
|
||||||
c = function() {};
|
data: { foo: "bar=baz", '&bar': "moo%cow" },
|
||||||
|
expected: 'foo=' + encodeURIComponent( 'bar=baz' ) +
|
||||||
|
'&' + encodeURIComponent( '&bar' ) + '=' +
|
||||||
|
encodeURIComponent( 'moo%cow' )
|
||||||
|
},
|
||||||
|
|
||||||
Sut( dummy_url, method, impl ).request( data, c );
|
// empty string
|
||||||
|
{
|
||||||
|
enctype: 'application/x-www-form-urlencoded',
|
||||||
|
method: 'POST',
|
||||||
|
data: {},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
|
||||||
expect( impl.provided[ 2 ] ).to.equal( "" );
|
// json
|
||||||
|
{
|
||||||
|
enctype: 'application/json',
|
||||||
|
method: 'POST',
|
||||||
|
data: { foo: 'bar' },
|
||||||
|
expected: '{"foo":"bar"}',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ignored if GET
|
||||||
|
{
|
||||||
|
enctype: 'application/json',
|
||||||
|
method: 'GET',
|
||||||
|
data: { foo: "bar" },
|
||||||
|
expected: "foo=bar",
|
||||||
|
},
|
||||||
|
].forEach( ( { enctype, method, data, expected }, i ) =>
|
||||||
|
{
|
||||||
|
it( `${method} encodes (${i})`, () =>
|
||||||
|
{
|
||||||
|
Sut( dummy_url, method, impl, enctype )
|
||||||
|
.request( data, _ => {} );
|
||||||
|
|
||||||
|
expect( impl.provided[ 2 ] ).to.equal( expected );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ describe( 'DataProcessor', () =>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Sut( filter, () => {}, meta_source )
|
Sut( filter, () => {}, meta_source, createStubStagingBucket )
|
||||||
.processDiff( data, request, program );
|
.processDiff( data, request, program );
|
||||||
|
|
||||||
expect( data.filtered ).to.equal( true );
|
expect( data.filtered ).to.equal( true );
|
||||||
|
@ -109,7 +109,7 @@ describe( 'DataProcessor', () =>
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
|
||||||
Sut( filter, dapi_factory )
|
Sut( filter, dapi_factory, null, createStubStagingBucket )
|
||||||
.processDiff( {}, request, program );
|
.processDiff( {}, request, program );
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -136,7 +136,12 @@ describe( 'DataProcessor', () =>
|
||||||
meta_source,
|
meta_source,
|
||||||
} = createStubs( false, {}, getFieldData );
|
} = createStubs( false, {}, getFieldData );
|
||||||
|
|
||||||
const sut = Sut( filter, () => dapi_manager, meta_source );
|
const sut = Sut(
|
||||||
|
filter,
|
||||||
|
() => dapi_manager,
|
||||||
|
meta_source,
|
||||||
|
createStubStagingBucket
|
||||||
|
);
|
||||||
|
|
||||||
program.meta.fields = {
|
program.meta.fields = {
|
||||||
foo: {
|
foo: {
|
||||||
|
@ -249,7 +254,13 @@ function createSutFromStubs( /* see createStubs */ )
|
||||||
program: program,
|
program: program,
|
||||||
filter: filter,
|
filter: filter,
|
||||||
meta_source: meta_source,
|
meta_source: meta_source,
|
||||||
sut: Sut( filter, () => {}, meta_source ),
|
|
||||||
|
sut: Sut(
|
||||||
|
filter,
|
||||||
|
() => {},
|
||||||
|
meta_source,
|
||||||
|
createStubStagingBucket
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,6 +292,8 @@ function createStubProgram( internals )
|
||||||
internal: internals,
|
internal: internals,
|
||||||
meta: { qtypes: {}, fields: {} },
|
meta: { qtypes: {}, fields: {} },
|
||||||
apis: {},
|
apis: {},
|
||||||
|
|
||||||
|
initQuote() {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,3 +318,28 @@ function createStubBucket( data )
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createStubStagingBucket( bucket )
|
||||||
|
{
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getDataByName( name )
|
||||||
|
{
|
||||||
|
return bucket.getDataByName( name );
|
||||||
|
},
|
||||||
|
|
||||||
|
setValues( values )
|
||||||
|
{
|
||||||
|
data = values;
|
||||||
|
},
|
||||||
|
|
||||||
|
forbidBypass() {},
|
||||||
|
getDiff()
|
||||||
|
{
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
commit() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue