diff --git a/src/quote/BaseQuote.d.ts b/src/quote/BaseQuote.d.ts
index f2271f4..8273ad1 100644
--- a/src/quote/BaseQuote.d.ts
+++ b/src/quote/BaseQuote.d.ts
@@ -24,6 +24,7 @@
import { Program } from "../program/Program";
import { Quote, QuoteId } from "./Quote";
import { QuoteDataBucket } from "../bucket/QuoteDataBucket";
+import { PositiveInteger } from "../numeric";
export declare class BaseQuote implements Quote
@@ -33,7 +34,7 @@ export declare class BaseQuote implements Quote
*
* @return quote program
*/
- getProgram(): Program;
+ getProgram(): Program
/**
@@ -41,7 +42,7 @@ export declare class BaseQuote implements Quote
*
* @return program id
*/
- getProgramId(): string;
+ getProgramId(): string
/**
@@ -53,7 +54,7 @@ export declare class BaseQuote implements Quote
*
* @return quote id
*/
- getId(): QuoteId;
+ getId(): QuoteId
/**
@@ -61,7 +62,7 @@ export declare class BaseQuote implements Quote
*
* @return id of current step
*/
- getCurrentStepId(): number;
+ getCurrentStepId(): number
/**
@@ -72,7 +73,7 @@ export declare class BaseQuote implements Quote
*
* @return self
*/
- setExplicitLock( reason: string, step: number ): this;
+ setExplicitLock( reason: string, step: number ): this
/**
@@ -82,7 +83,7 @@ export declare class BaseQuote implements Quote
*
* @return self
*/
- setLastPremiumDate( timestamp: UnixTimestamp ): this;
+ setLastPremiumDate( timestamp: UnixTimestamp ): this
/**
@@ -90,7 +91,7 @@ export declare class BaseQuote implements Quote
*
* @return last calculated time or 0
*/
- getLastPremiumDate(): UnixTimestamp;
+ getLastPremiumDate(): UnixTimestamp
/**
@@ -99,4 +100,54 @@ export declare class BaseQuote implements Quote
* @return the data bucket
*/
getBucket(): QuoteDataBucket
+
+
+ /**
+ * Retrieves the reason for an explicit lock
+ *
+ * @return lock reason
+ */
+ getExplicitLockReason(): string
+
+
+ /**
+ * Returns the maximum step to which the explicit lock applies
+ *
+ * If no step restriction is set, then 0 will be returned.
+ *
+ * @return {number} locked max step or 0 if not applicable
+ */
+ getExplicitLockStep(): PositiveInteger
+
+
+ /**
+ * Returns whether the quote has been imported
+ *
+ * @return true if imported, otherwise false
+ */
+ isImported(): boolean
+
+
+ /**
+ * Returns whether the quote has been bound
+ *
+ * @return true if bound, otherwise false
+ */
+ isBound(): boolean
+
+
+ /**
+ * Returns the id of the highest step the quote has reached
+ *
+ * @return top visited step id
+ */
+ getTopVisitedStepId(): PositiveInteger
+
+
+ /**
+ * Returns the id of the highest step the quote has saved
+ *
+ * @return top saved step id
+ */
+ getTopSavedStepId(): PositiveInteger
}
diff --git a/src/server/daemon/controller.js b/src/server/daemon/controller.js
index 06b7a5e..116ed39 100644
--- a/src/server/daemon/controller.js
+++ b/src/server/daemon/controller.js
@@ -69,7 +69,7 @@ const {
DocumentServer,
db: {
- MongoServerDao,
+ MongoServerDao: { MongoServerDao },
},
lock: {
@@ -126,8 +126,8 @@ exports.post_rate_publish = {};
exports.init = function( logger, enc_service, conf )
{
- var db = _createDB( logger );
- const dao = MongoServerDao( db );
+ var db = _createDB( logger );
+ const dao = new MongoServerDao( db );
db.collection( 'quotes', function( err, collection )
{
diff --git a/src/server/db/MongoServerDao.js b/src/server/db/MongoServerDao.js
index db6140e..34878c4 100644
--- a/src/server/db/MongoServerDao.js
+++ b/src/server/db/MongoServerDao.js
@@ -1,3 +1,4 @@
+"use strict";
/**
* Mongo DB DAO for program server
*
@@ -18,86 +19,46 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
-
-var Class = require( 'easejs' ).Class,
- EventEmitter = require( '../../events' ).EventEmitter,
- ServerDao = require( './ServerDao' ).ServerDao;
-
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
+var EventEmitter = require('events').EventEmitter;
/**
* Uses MongoDB as a data store
*/
-module.exports = Class( 'MongoServerDao' )
- .implement( ServerDao )
- .extend( EventEmitter,
-{
- /**
- * Collection used to store quotes
- * @type String
- */
- 'const COLLECTION': 'quotes',
-
- /**
- * Sequence (auto-increment) collection
- * @type {string}
- */
- 'const COLLECTION_SEQ': 'seq',
-
- /**
- * Sequence key for quote ids
- *
- * @type {string}
- * @const
- */
- 'const SEQ_QUOTE_ID': 'quoteId',
-
- /**
- * Sequence quoteId default
- *
- * @type {number}
- * @const
- */
- 'const SEQ_QUOTE_ID_DEFAULT': 200000,
-
-
- /**
- * Database instance
- * @type Mongo.Db
- */
- 'private _db': null,
-
- /**
- * Whether the DAO is initialized and ready to be used
- * @type Boolean
- */
- 'private _ready': false,
-
- /**
- * Collection to save data to
- * @type null|Collection
- */
- 'private _collection': null,
-
- /**
- * Collection to read sequences (auto-increments) from
- * @type {null|Collection}
- */
- 'private _seqCollection': null,
-
-
+var MongoServerDao = /** @class */ (function (_super) {
+ __extends(MongoServerDao, _super);
/**
* Initializes DAO
*
* @param {Mongo.Db} db mongo database connection
- *
- * @return undefined
*/
- 'public __construct': function( db )
- {
- this._db = db;
- },
-
-
+ function MongoServerDao(_db) {
+ var _this = _super.call(this) || this;
+ _this._db = _db;
+ /** Collection used to store quotes */
+ _this.COLLECTION = 'quotes';
+ /** Sequence (auto-increment) collection */
+ _this.COLLECTION_SEQ = 'seq';
+ /** Sequence key for quote ids */
+ _this.SEQ_QUOTE_ID = 'quoteId';
+ /** Sequence quoteId default */
+ _this.SEQ_QUOTE_ID_DEFAULT = 200000;
+ /** Whether the DAO is initialized and ready to be used */
+ _this._ready = false;
+ return _this;
+ }
/**
* Initializes error events and attempts to connect to the database
*
@@ -108,24 +69,17 @@ module.exports = Class( 'MongoServerDao' )
*
* @return MongoServerDao self to allow for method chaining
*/
- 'public init': function( callback )
- {
+ MongoServerDao.prototype.init = function (callback) {
var dao = this;
-
// map db error event (on connection error) to our connectError event
- this._db.on( 'error', function( err )
- {
- dao._ready = false;
+ this._db.on('error', function (err) {
+ dao._ready = false;
dao._collection = null;
-
- dao.emit( 'connectError', err );
+ dao.emit('connectError', err);
});
-
- this.connect( callback );
+ this.connect(callback);
return this;
- },
-
-
+ };
/**
* Attempts to connect to the database
*
@@ -136,138 +90,88 @@ module.exports = Class( 'MongoServerDao' )
*
* @return MongoServerDao self to allow for method chaining
*/
- 'public connect': function( callback )
- {
+ MongoServerDao.prototype.connect = function (callback) {
var dao = this;
-
// attempt to connect to the database
- this._db.open( function( err, db )
- {
+ this._db.open(function (err, db) {
// if there was an error, don't bother with anything else
- if ( err )
- {
+ if (err) {
// in some circumstances, it may just be telling us that we're
// already connected (even though the connection may have been
// broken)
- if ( err.errno !== undefined )
- {
- dao.emit( 'connectError', err );
+ if (err.errno !== undefined) {
+ dao.emit('connectError', err);
return;
}
}
-
var ready_count = 0;
- var check_ready = function()
- {
- if ( ++ready_count < 2 )
- {
+ var check_ready = function () {
+ if (++ready_count < 2) {
return;
}
-
// we're ready to roll!
dao._ready = true;
- dao.emit( 'ready' );
-
+ dao.emit('ready');
// connection was successful; call the callback
- if ( callback instanceof Function )
- {
- callback.call( dao );
+ if (callback instanceof Function) {
+ callback.call(dao);
}
- }
-
+ };
// quotes collection
- db.collection( dao.__self.$('COLLECTION'), function( err, collection )
- {
+ db.collection(dao.COLLECTION, function (_err, collection) {
// for some reason this gets called more than once
- if ( collection == null )
- {
+ if (collection == null) {
return;
}
-
// initialize indexes
- collection.createIndex(
- [ ['id', 1] ],
- true,
- function( err, index )
- {
- // mark the DAO as ready to be used
- dao._collection = collection;
- check_ready();
- }
- );
+ collection.createIndex([['id', 1]], true, function (_err, _index) {
+ // mark the DAO as ready to be used
+ dao._collection = collection;
+ check_ready();
+ });
});
-
// seq collection
- db.collection( dao.__self.$('COLLECTION_SEQ'), function( err, collection )
- {
- if ( err )
- {
- dao.emit( 'seqError', err );
+ db.collection(dao.COLLECTION_SEQ, function (err, collection) {
+ if (err) {
+ dao.emit('seqError', err);
return;
}
-
- if ( collection == null )
- {
+ if (collection == null) {
return;
}
-
dao._seqCollection = collection;
-
// has the sequence we'll be referencing been initialized?
- collection.find(
- { _id: dao.__self.$('SEQ_QUOTE_ID') },
- { limit: 1 },
- function( err, cursor )
- {
- if ( err )
- {
- dao.initQuoteIdSeq( check_ready )
+ collection.find({ _id: dao.SEQ_QUOTE_ID }, { limit: 1 }, function (err, cursor) {
+ if (err) {
+ dao._initQuoteIdSeq(check_ready);
+ return;
+ }
+ cursor.toArray(function (_err, data) {
+ if (data.length == 0) {
+ dao._initQuoteIdSeq(check_ready);
return;
}
-
- cursor.toArray( function( err, data )
- {
- if ( data.length == 0 )
- {
- dao.initQuoteIdSeq( check_ready );
- return;
- }
-
- check_ready();
- });
- }
- );
+ check_ready();
+ });
+ });
});
});
-
return this;
- },
-
-
- 'public initQuoteIdSeq': function( callback )
- {
+ };
+ MongoServerDao.prototype._initQuoteIdSeq = function (callback) {
var dao = this;
-
- this._seqCollection.insert(
- {
- _id: this.__self.$('SEQ_QUOTE_ID'),
- val: this.__self.$('SEQ_QUOTE_ID_DEFAULT'),
- },
- function( err, docs )
- {
- if ( err )
- {
- dao.emit( 'seqError', err );
- return;
- }
-
- dao.emit( 'seqInit', this.__self.$('SEQ_QUOTE_ID') );
- callback.call( this );
+ this._seqCollection.insert({
+ _id: this.SEQ_QUOTE_ID,
+ val: this.SEQ_QUOTE_ID_DEFAULT,
+ }, function (err, _docs) {
+ if (err) {
+ dao.emit('seqError', err);
+ return;
}
- );
- },
-
-
+ dao.emit('seqInit', dao.SEQ_QUOTE_ID);
+ callback.call(dao);
+ });
+ };
/**
* Saves a quote to the database
*
@@ -281,109 +185,69 @@ module.exports = Class( 'MongoServerDao' )
* @param Function failure_callback function to call if save fails
* @param Object save_data quote data to save (optional)
* @param Object push_data quote data to push (optional)
- *
- * @return MongoServerDao self to allow for method chaining
*/
- 'public saveQuote': function(
- quote, success_callback, failure_callback, save_data, push_data
- )
- {
- var dao = this;
+ MongoServerDao.prototype.saveQuote = function (quote, success_callback, failure_callback, save_data, push_data) {
+ var dao = this;
var meta = {};
-
// if we're not ready, then we can't save the quote!
- if ( this._ready === false )
- {
- this.emit( 'saveQuoteError',
- { message: 'Database server not ready' },
- Error( 'Database not ready' ),
- quote
- );
-
- failure_callback.call( this, quote );
- return;
+ if (this._ready === false) {
+ this.emit('saveQuoteError', { message: 'Database server not ready' }, Error('Database not ready'), quote);
+ failure_callback.call(this, quote);
+ return dao;
}
-
- if ( save_data === undefined )
- {
+ if (save_data === undefined) {
save_data = {
data: quote.getBucket().getData(),
};
-
// full save will include all metadata
meta = quote.getMetabucket().getData();
}
-
var id = quote.getId();
-
// some data should always be saved because the quote will be created if
// it does not yet exist
- save_data.id = id;
- save_data.pver = quote.getProgramVersion();
- save_data.importDirty = 1;
- save_data.lastPremDate = quote.getLastPremiumDate();
- save_data.initialRatedDate = quote.getRatedDate();
- save_data.explicitLock = quote.getExplicitLockReason();
+ save_data.id = id;
+ save_data.pver = quote.getProgramVersion();
+ save_data.importDirty = 1;
+ save_data.lastPremDate = quote.getLastPremiumDate();
+ save_data.initialRatedDate = quote.getRatedDate();
+ save_data.explicitLock = quote.getExplicitLockReason();
save_data.explicitLockStepId = quote.getExplicitLockStep();
- save_data.importedInd = +quote.isImported();
- save_data.boundInd = +quote.isBound();
- save_data.lastUpdate = Math.round(
- ( new Date() ).getTime() / 1000
- );
-
+ save_data.importedInd = +quote.isImported();
+ save_data.boundInd = +quote.isBound();
+ save_data.lastUpdate = Math.round((new Date()).getTime() / 1000);
// meta will eventually take over for much of the above data
- meta.liza_timestamp_initial_rated = [ quote.getRatedDate() ];
-
+ meta.liza_timestamp_initial_rated = [quote.getRatedDate()];
// save the stack so we can track this call via the oplog
- save_data._stack = ( new Error() ).stack;
-
+ save_data._stack = (new Error()).stack;
// avoid wiping out other metadata (since this may not be a full set)
- Object.keys( meta ).forEach(
- key => save_data[ 'meta.' + key ] = meta[ key ]
- );
-
+ Object.keys(meta).forEach(function (key) { return save_data['meta.' + key] = meta[key]; });
// do not push empty objects
- const document = ( !push_data || !Object.keys( push_data ).length )
+ var document = (!push_data || !Object.keys(push_data).length)
? { '$set': save_data }
: { '$set': save_data, '$push': push_data };
-
// update the quote data if it already exists (same id), otherwise
// insert it
- this._collection.update( { id: id },
- document,
-
- // create record if it does not yet exist
- { upsert: true },
-
- // on complete
- function( err, docs )
- {
- // if an error occurred, then we cannot continue
- if ( err )
- {
- dao.emit( 'saveQuoteError', err, quote );
-
- // let the caller handle the error
- if ( failure_callback instanceof Function )
- {
- failure_callback.call( dao, quote );
- }
-
- return;
- }
-
- // successful
- if ( success_callback instanceof Function )
- {
- success_callback.call( dao, quote );
+ this._collection.update({ id: id }, document,
+ // create record if it does not yet exist
+ { upsert: true },
+ // on complete
+ function (err, _docs) {
+ // if an error occurred, then we cannot continue
+ if (err) {
+ dao.emit('saveQuoteError', err, quote);
+ // let the caller handle the error
+ if (failure_callback instanceof Function) {
+ failure_callback.call(dao, quote);
}
+ return;
}
- );
-
+ // successful
+ if (success_callback instanceof Function) {
+ success_callback.call(dao, quote);
+ }
+ });
return this;
- },
-
-
+ };
/**
* Merges quote data with the existing (rather than overwriting)
*
@@ -391,45 +255,26 @@ module.exports = Class( 'MongoServerDao' )
* @param {Object} data quote data
* @param {Function} scallback successful callback
* @param {Function} fcallback failure callback
- *
- * @return {MongoServerDao} self
*/
- 'public mergeData': function( quote, data, scallback, fcallback )
- {
+ MongoServerDao.prototype.mergeData = function (quote, data, scallback, fcallback) {
// we do not want to alter the original data; use it as a prototype
var update = data;
-
// save the stack so we can track this call via the oplog
var _self = this;
- this._collection.update( { id: quote.getId() },
- { '$set': update },
- {},
-
- function( err, docs )
- {
- if ( err )
- {
- _self.emit( 'saveQuoteError', err, quote );
-
- if ( typeof fcallback === 'function' )
- {
- fcallback( quote );
- }
-
- return;
- }
-
- if ( typeof scallback === 'function' )
- {
- scallback( quote );
+ this._collection.update({ id: quote.getId() }, { '$set': update }, {}, function (err, _docs) {
+ if (err) {
+ _self.emit('saveQuoteError', err, quote);
+ if (typeof fcallback === 'function') {
+ fcallback(quote);
}
+ return;
}
- );
-
+ if (typeof scallback === 'function') {
+ scallback(quote);
+ }
+ });
return this;
- },
-
-
+ };
/**
* Merges bucket data with the existing bucket (rather than overwriting the
* entire bucket)
@@ -441,24 +286,16 @@ module.exports = Class( 'MongoServerDao' )
*
* @return {MongoServerDao} self
*/
- 'public mergeBucket': function( quote, data, scallback, fcallback )
- {
+ MongoServerDao.prototype.mergeBucket = function (quote, data, success, failure) {
var update = {};
-
- for ( var field in data )
- {
- if ( !field )
- {
+ for (var field in data) {
+ if (!field) {
continue;
}
-
- update[ 'data.' + field ] = data[ field ];
+ update['data.' + field] = data[field];
}
-
- return this.mergeData( quote, update, scallback, fcallback );
- },
-
-
+ return this.mergeData(quote, update, success, failure);
+ };
/**
* Saves the quote state to the database
*
@@ -471,33 +308,17 @@ module.exports = Class( 'MongoServerDao' )
*
* @return MongoServerDao self
*/
- 'public saveQuoteState': function(
- quote, success_callback, failure_callback
- )
- {
+ MongoServerDao.prototype.saveQuoteState = function (quote, success_callback, failure_callback) {
var update = {
- currentStepId: quote.getCurrentStepId(),
+ currentStepId: quote.getCurrentStepId(),
topVisitedStepId: quote.getTopVisitedStepId(),
- topSavedStepId: quote.getTopSavedStepId(),
+ topSavedStepId: quote.getTopSavedStepId(),
};
-
- return this.mergeData(
- quote, update, success_callback, failure_callback
- );
- },
-
-
- 'public saveQuoteClasses': function( quote, classes, success, failure )
- {
- return this.mergeData(
- quote,
- { classData: classes },
- success,
- failure
- );
- },
-
-
+ return this.mergeData(quote, update, success_callback, failure_callback);
+ };
+ MongoServerDao.prototype.saveQuoteClasses = function (quote, classes, success, failure) {
+ return this.mergeData(quote, { classData: classes }, success, failure);
+ };
/**
* Save document metadata (meta field on document)
*
@@ -511,25 +332,16 @@ module.exports = Class( 'MongoServerDao' )
*
* @return {undefined}
*/
- 'public saveQuoteMeta'( quote, new_meta, success, failure )
- {
- const update = {};
-
- for ( var key in new_meta )
- {
- var meta = new_meta[ key ];
-
- for ( var i in meta )
- {
- update[ 'meta.' + key + '.' + i ] =
- new_meta[ key ][ i ];
+ MongoServerDao.prototype.saveQuoteMeta = function (quote, new_meta, success, failure) {
+ var update = {};
+ for (var key in new_meta) {
+ var meta = new_meta[key];
+ for (var i in meta) {
+ update['meta.' + key + '.' + i] = new_meta[key][i];
}
}
-
- this.mergeData( quote, update, success, failure );
- },
-
-
+ this.mergeData(quote, update, success, failure);
+ };
/**
* Saves the quote lock state to the database
*
@@ -539,15 +351,10 @@ module.exports = Class( 'MongoServerDao' )
*
* @return MongoServerDao self
*/
- 'public saveQuoteLockState': function(
- quote, success_callback, failure_callback
- )
- {
+ MongoServerDao.prototype.saveQuoteLockState = function (quote, success_callback, failure_callback) {
// lock state is saved by default
- return this.saveQuote( quote, success_callback, failure_callback, {} );
- },
-
-
+ return this.saveQuote(quote, success_callback, failure_callback, {});
+ };
/**
* Pulls quote data from the database
*
@@ -556,96 +363,54 @@ module.exports = Class( 'MongoServerDao' )
*
* @return MongoServerDao self to allow for method chaining
*/
- 'public pullQuote': function( quote_id, callback )
- {
+ MongoServerDao.prototype.pullQuote = function (quote_id, callback) {
var dao = this;
-
// XXX: TODO: Do not read whole of record into memory; filter out
// revisions!
- this._collection.find( { id: quote_id }, { limit: 1 },
- function( err, cursor )
- {
- cursor.toArray( function( err, data )
- {
- // was the quote found?
- if ( data.length == 0 )
- {
- callback.call( dao, null );
- return;
- }
-
- // return the quote data
- callback.call( dao, data[ 0 ] );
- });
- }
- );
-
- return this;
- },
-
-
- 'public getMinQuoteId': function( callback )
- {
- // just in case it's asynchronous later on
- callback.call( this, this.__self.$('SEQ_QUOTE_ID_DEFAULT') );
- return this;
- },
-
-
- 'public getMaxQuoteId': function( callback )
- {
- var dao = this;
-
- this._seqCollection.find(
- { _id: this.__self.$('SEQ_QUOTE_ID') },
- { limit: 1 },
- function( err, cursor )
- {
- cursor.toArray( function( err, data )
- {
- if ( data.length == 0 )
- {
- callback.call( dao, 0 );
- return;
- }
-
- // return the max quote id
- callback.call( dao, data[ 0 ].val );
- });
- }
- );
- },
-
-
- 'public getNextQuoteId': function( callback )
- {
- var dao = this;
-
- this._seqCollection.findAndModify(
- { _id: this.__self.$('SEQ_QUOTE_ID') },
- [ [ 'val', 'descending' ] ],
- { $inc: { val: 1 } },
- { 'new': true },
-
- function( err, doc )
- {
- if ( err )
- {
- dao.emit( 'seqError', err );
-
- callback.call( dao, 0 );
+ this._collection.find({ id: quote_id }, { limit: 1 }, function (_err, cursor) {
+ cursor.toArray(function (_err, data) {
+ // was the quote found?
+ if (data.length == 0) {
+ callback.call(dao, null);
return;
}
-
- // return the new id
- callback.call( dao, doc.val );
- }
- );
-
+ // return the quote data
+ callback.call(dao, data[0]);
+ });
+ });
return this;
- },
-
-
+ };
+ MongoServerDao.prototype.getMinQuoteId = function (callback) {
+ // just in case it's asynchronous later on
+ callback.call(this, this.SEQ_QUOTE_ID_DEFAULT);
+ return this;
+ };
+ MongoServerDao.prototype.getMaxQuoteId = function (callback) {
+ var dao = this;
+ this._seqCollection.find({ _id: this.SEQ_QUOTE_ID }, { limit: 1 }, function (_err, cursor) {
+ cursor.toArray(function (_err, data) {
+ if (data.length == 0) {
+ callback.call(dao, 0);
+ return;
+ }
+ // return the max quote id
+ callback.call(dao, data[0].val);
+ });
+ });
+ };
+ MongoServerDao.prototype.getNextQuoteId = function (callback) {
+ var dao = this;
+ this._seqCollection.findAndModify({ _id: this.SEQ_QUOTE_ID }, [['val', 'descending']], { $inc: { val: 1 } }, { 'new': true }, function (err, doc) {
+ if (err) {
+ dao.emit('seqError', err);
+ callback.call(dao, 0);
+ return;
+ }
+ // return the new id
+ callback.call(dao, doc.val);
+ });
+ return this;
+ };
/**
* Create a new revision with the provided quote data
*
@@ -654,170 +419,64 @@ module.exports = Class( 'MongoServerDao' )
* model of storing the deltas in previous revisions and the whole of the
* bucket in the most recently created revision).
*/
- 'public createRevision': function( quote, callback )
- {
- var _self = this,
- qid = quote.getId(),
- data = quote.getBucket().getData();
-
- this._collection.update( { id: qid },
- { '$push': { revisions: { data: data } } },
-
- // create record if it does not yet exist
- { upsert: true },
-
- // on complete
- function( err )
- {
- if ( err )
- {
- _self.emit( 'mkrevError', err );
- }
-
- callback( err );
- return;
+ MongoServerDao.prototype.createRevision = function (quote, callback) {
+ var _self = this, qid = quote.getId(), data = quote.getBucket().getData();
+ this._collection.update({ id: qid }, { '$push': { revisions: { data: data } } },
+ // create record if it does not yet exist
+ { upsert: true },
+ // on complete
+ function (err) {
+ if (err) {
+ _self.emit('mkrevError', err);
}
- );
- },
-
-
- 'public getRevision': function( quote, revid, callback )
- {
+ callback(err);
+ return;
+ });
+ };
+ MongoServerDao.prototype.getRevision = function (quote, revid, callback) {
revid = +revid;
-
// XXX: TODO: Filter out all but the revision we want
- this._collection.find(
- { id: quote.getId() },
- { limit: 1 },
- function( err, cursor )
- {
- cursor.toArray( function( err, data )
- {
- // was the quote found?
- if ( ( data.length === 0 )
- || ( data[ 0 ].revisions.length < ( revid + 1 ) )
- )
- {
- callback( null );
- return;
- }
-
- // return the quote data
- callback( data[ 0 ].revisions[ revid ] );
- });
- }
- );
- },
-
-
- 'public setWorksheets': function( qid, data, callback )
- {
- this._collection.update( { id: qid },
- { '$set': { worksheets: { data: data } } },
-
- // create record if it does not yet exist
- { upsert: true },
-
- // on complete
- function( err )
- {
- callback( err );
- return;
- }
- );
- },
-
-
- 'public getWorksheet': function( qid, supplier, index, callback )
- {
- this._collection.find(
- { id: qid },
- { limit: 1 },
- function( err, cursor )
- {
- cursor.toArray( function( err, data )
- {
- // was the quote found?
- if ( ( data.length === 0 )
- || ( !data[ 0 ].worksheets )
- || ( !data[ 0 ].worksheets.data )
- || ( !data[ 0 ].worksheets.data[ supplier ] )
- )
- {
- callback( null );
- return;
- }
-
- // return the quote data
- callback( data[ 0 ].worksheets.data[ supplier ][ index ] );
- });
- }
- );
- },
-
-
- /**
- * Set arbitrary data on a document
- *
- * @param {number} qid quote/document id
- * @param {string} key field key
- * @param {*} value field value
- * @param {function(?Error)} callback completion callback
- *
- * @return {undefined}
- */
- 'public setDocumentField'( qid, key, value, callback )
- {
- this._collection.update(
- { id: qid },
- { '$set': { [key]: value } },
-
- // create record if it does not yet exist
- { upsert: true },
-
- // on complete
- function( err )
- {
- callback && callback( err );
- return;
- }
- );
- },
-
-
- /**
- * Retrieve arbitrary data on a document
- *
- * @param {number} qid quote/document id
- * @param {string} key field key
- * @param {function(?Error)} callback completion callback
- *
- * @return {undefined}
- */
- 'public getDocumentField'( qid, key, callback )
- {
- this._collection.find(
- { id: qid },
- { limit: 1 },
- function( err, cursor )
- {
- if ( err !== null )
- {
- callback( err, null );
+ this._collection.find({ id: quote.getId() }, { limit: 1 }, function (_err, cursor) {
+ cursor.toArray(function (_err, data) {
+ // was the quote found?
+ if ((data.length === 0)
+ || (data[0].revisions.length < (revid + 1))) {
+ callback(null);
return;
}
-
- cursor.toArray( function( err, data )
- {
- if ( err !== null )
- {
- callback( err, null );
- return;
- }
-
- callback( null, ( data[ 0 ] || {} )[ key ] );
- } );
- }
- );
- },
-} );
+ // return the quote data
+ callback(data[0].revisions[revid]);
+ });
+ });
+ };
+ MongoServerDao.prototype.setWorksheets = function (qid, data, callback) {
+ this._collection.update({ id: qid }, { '$set': { worksheets: { data: data } } },
+ // create record if it does not yet exist
+ { upsert: true },
+ // on complete
+ function (err) {
+ callback(err);
+ return;
+ });
+ };
+ MongoServerDao.prototype.getWorksheet = function (qid, supplier, index, callback) {
+ this._collection.find({ id: qid }, { limit: 1 }, function (_err, cursor) {
+ cursor.toArray(function (_err, data) {
+ // was the quote found?
+ if ((data.length === 0)
+ || (!data[0].worksheets)
+ || (!data[0].worksheets.data)
+ || (!data[0].worksheets.data[supplier])) {
+ callback(null);
+ return;
+ }
+ // return the quote data
+ callback(data[0].worksheets.data[supplier][index]);
+ });
+ });
+ };
+ return MongoServerDao;
+}(EventEmitter));
+exports.MongoServerDao = MongoServerDao;
+;
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9uZ29TZXJ2ZXJEYW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJNb25nb1NlcnZlckRhby50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7Ozs7Ozs7Ozs7Ozs7OztBQVNILElBQU0sWUFBWSxHQUFHLE9BQU8sQ0FBRSxRQUFRLENBQUUsQ0FBQyxZQUFZLENBQUM7QUFJdEQ7O0dBRUc7QUFDSDtJQUFvQyxrQ0FBWTtJQXlCNUM7Ozs7T0FJRztJQUNILHdCQUNxQixHQUFRO1FBRDdCLFlBSUksaUJBQU8sU0FDVjtRQUpvQixTQUFHLEdBQUgsR0FBRyxDQUFLO1FBN0I3QixzQ0FBc0M7UUFDN0IsZ0JBQVUsR0FBVyxRQUFRLENBQUM7UUFFdkMsMkNBQTJDO1FBQ2xDLG9CQUFjLEdBQVcsS0FBSyxDQUFDO1FBRXhDLGlDQUFpQztRQUN4QixrQkFBWSxHQUFXLFNBQVMsQ0FBQztRQUUxQywrQkFBK0I7UUFDdEIsMEJBQW9CLEdBQVcsTUFBTSxDQUFDO1FBRy9DLDBEQUEwRDtRQUNsRCxZQUFNLEdBQVksS0FBSyxDQUFDOztJQW1CaEMsQ0FBQztJQUdEOzs7Ozs7Ozs7T0FTRztJQUNILDZCQUFJLEdBQUosVUFBTSxRQUFrQjtRQUVwQixJQUFJLEdBQUcsR0FBRyxJQUFJLENBQUM7UUFFZixxRUFBcUU7UUFDckUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUUsT0FBTyxFQUFFLFVBQVUsR0FBUTtZQUVwQyxHQUFHLENBQUMsTUFBTSxHQUFRLEtBQUssQ0FBQztZQUN4QixHQUFHLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQztZQUV2QixHQUFHLENBQUMsSUFBSSxDQUFFLGNBQWMsRUFBRSxHQUFHLENBQUUsQ0FBQztRQUNwQyxDQUFDLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxPQUFPLENBQUUsUUFBUSxDQUFFLENBQUM7UUFDekIsT0FBTyxJQUFJLENBQUM7SUFDaEIsQ0FBQztJQUdEOzs7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
\ No newline at end of file
diff --git a/src/server/db/MongoServerDao.ts b/src/server/db/MongoServerDao.ts
new file mode 100644
index 0000000..2cbce84
--- /dev/null
+++ b/src/server/db/MongoServerDao.ts
@@ -0,0 +1,788 @@
+/**
+ * Mongo DB DAO for program server
+ *
+ * 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 { ServerDao, Callback } from "./ServerDao";
+import { MongoCollection, MongoUpdate } from "mongodb";
+import { PositiveInteger } from "../../numeric";
+import { ServerSideQuote } from "../quote/ServerSideQuote";
+import { QuoteId } from "../../document/Document";
+import { WorksheetData } from "../rater/Rater";
+
+const EventEmitter = require( 'events' ).EventEmitter;
+
+type ErrorCallback = ( err: NullableError ) => void;
+
+/**
+ * Uses MongoDB as a data store
+ */
+export class MongoServerDao extends EventEmitter implements ServerDao
+{
+ /** Collection used to store quotes */
+ readonly COLLECTION: string = 'quotes';
+
+ /** Sequence (auto-increment) collection */
+ readonly COLLECTION_SEQ: string = 'seq';
+
+ /** Sequence key for quote ids */
+ readonly SEQ_QUOTE_ID: string = 'quoteId';
+
+ /** Sequence quoteId default */
+ readonly SEQ_QUOTE_ID_DEFAULT: number = 200000;
+
+
+ /** Whether the DAO is initialized and ready to be used */
+ private _ready: boolean = false;
+
+ /** Collection to save data to */
+ private _collection?: MongoCollection | null;
+
+ /** Collection to read sequences (auto-increments) from */
+ private _seqCollection?: MongoCollection | null;
+
+
+ /**
+ * Initializes DAO
+ *
+ * @param {Mongo.Db} db mongo database connection
+ */
+ constructor(
+ private readonly _db: any
+ )
+ {
+ super();
+ }
+
+
+ /**
+ * Initializes error events and attempts to connect to the database
+ *
+ * connectError event will be emitted on failure.
+ *
+ * @param Function callback function to call when connection is complete
+ * (will not be called if connection fails)
+ *
+ * @return MongoServerDao self to allow for method chaining
+ */
+ init( callback: () => {} ): this
+ {
+ var dao = this;
+
+ // map db error event (on connection error) to our connectError event
+ this._db.on( 'error', function( err: any )
+ {
+ dao._ready = false;
+ dao._collection = null;
+
+ dao.emit( 'connectError', err );
+ });
+
+ this.connect( callback );
+ return this;
+ }
+
+
+ /**
+ * Attempts to connect to the database
+ *
+ * connectError event will be emitted on failure.
+ *
+ * @param Function callback function to call when connection is complete
+ * (will not be called if connection fails)
+ *
+ * @return MongoServerDao self to allow for method chaining
+ */
+ connect( callback: () => {} ): this
+ {
+ var dao = this;
+
+ // attempt to connect to the database
+ this._db.open( function( err: any, db: any )
+ {
+ // if there was an error, don't bother with anything else
+ if ( err )
+ {
+ // in some circumstances, it may just be telling us that we're
+ // already connected (even though the connection may have been
+ // broken)
+ if ( err.errno !== undefined )
+ {
+ dao.emit( 'connectError', err );
+ return;
+ }
+ }
+
+ var ready_count = 0;
+ var check_ready = function()
+ {
+ if ( ++ready_count < 2 )
+ {
+ return;
+ }
+
+ // we're ready to roll!
+ dao._ready = true;
+ dao.emit( 'ready' );
+
+ // connection was successful; call the callback
+ if ( callback instanceof Function )
+ {
+ callback.call( dao );
+ }
+ }
+
+ // quotes collection
+ db.collection(
+ dao.COLLECTION,
+ function(
+ _err: any,
+ collection: MongoCollection,
+ ) {
+ // for some reason this gets called more than once
+ if ( collection == null )
+ {
+ return;
+ }
+
+ // initialize indexes
+ collection.createIndex(
+ [ ['id', 1] ],
+ true,
+ function( _err: any, _index: { [P: string]: any } )
+ {
+ // mark the DAO as ready to be used
+ dao._collection = collection;
+ check_ready();
+ }
+ );
+ }
+ );
+
+ // seq collection
+ db.collection(
+ dao.COLLECTION_SEQ,
+ function(
+ err: any,
+ collection: MongoCollection,
+ ) {
+ if ( err )
+ {
+ dao.emit( 'seqError', err );
+ return;
+ }
+
+ if ( collection == null )
+ {
+ return;
+ }
+
+ dao._seqCollection = collection;
+
+ // has the sequence we'll be referencing been initialized?
+ collection.find(
+ { _id: dao.SEQ_QUOTE_ID },
+ { limit: 1 },
+ function( err: any, cursor )
+ {
+ if ( err )
+ {
+ dao._initQuoteIdSeq( check_ready )
+ return;
+ }
+
+ cursor.toArray( function( _err: any, data: any[] )
+ {
+ if ( data.length == 0 )
+ {
+ dao._initQuoteIdSeq( check_ready );
+ return;
+ }
+
+ check_ready();
+ });
+ }
+ );
+ }
+ );
+ });
+
+ return this;
+ }
+
+
+ private _initQuoteIdSeq( callback: () => void )
+ {
+ var dao = this;
+
+ this._seqCollection!.insert(
+ {
+ _id: this.SEQ_QUOTE_ID,
+ val: this.SEQ_QUOTE_ID_DEFAULT,
+ },
+ function( err: any, _docs: any )
+ {
+ if ( err )
+ {
+ dao.emit( 'seqError', err );
+ return;
+ }
+
+ dao.emit( 'seqInit', dao.SEQ_QUOTE_ID );
+ callback.call( dao );
+ }
+ );
+ }
+
+
+ /**
+ * Saves a quote to the database
+ *
+ * A full save will include all metadata. This should not cause any
+ * problems with race conditions for pending Data API calls on meta
+ * fields because those results write to individual indexes and do not
+ * rely on existing data.
+ *
+ * @param Quote quote the quote to save
+ * @param Function success_callback function to call on success
+ * @param Function failure_callback function to call if save fails
+ * @param Object save_data quote data to save (optional)
+ * @param Object push_data quote data to push (optional)
+ */
+ saveQuote(
+ quote: ServerSideQuote,
+ success_callback: Callback,
+ failure_callback: Callback,
+ save_data?: any,
+ push_data?: any,
+ ): this
+ {
+ var dao = this;
+ var meta: Record = {};
+
+ // if we're not ready, then we can't save the quote!
+ if ( this._ready === false )
+ {
+ this.emit( 'saveQuoteError',
+ { message: 'Database server not ready' },
+ Error( 'Database not ready' ),
+ quote
+ );
+
+ failure_callback.call( this, quote );
+ return dao;
+ }
+
+ if ( save_data === undefined )
+ {
+ save_data = {
+ data: quote.getBucket().getData(),
+ };
+
+ // full save will include all metadata
+ meta = quote.getMetabucket().getData();
+ }
+
+ var id = quote.getId();
+
+ // some data should always be saved because the quote will be created if
+ // it does not yet exist
+ save_data.id = id;
+ save_data.pver = quote.getProgramVersion();
+ save_data.importDirty = 1;
+ save_data.lastPremDate = quote.getLastPremiumDate();
+ save_data.initialRatedDate = quote.getRatedDate();
+ save_data.explicitLock = quote.getExplicitLockReason();
+ save_data.explicitLockStepId = quote.getExplicitLockStep();
+ save_data.importedInd = +quote.isImported();
+ save_data.boundInd = +quote.isBound();
+ save_data.lastUpdate = Math.round(
+ ( new Date() ).getTime() / 1000
+ );
+
+ // meta will eventually take over for much of the above data
+ meta.liza_timestamp_initial_rated = [ quote.getRatedDate() ];
+
+ // save the stack so we can track this call via the oplog
+ save_data._stack = ( new Error() ).stack;
+
+ // avoid wiping out other metadata (since this may not be a full set)
+ Object.keys( meta ).forEach(
+ key => save_data[ 'meta.' + key ] = meta[ key ]
+ );
+
+ // do not push empty objects
+ const document = ( !push_data || !Object.keys( push_data ).length )
+ ? { '$set': save_data }
+ : { '$set': save_data, '$push': push_data };
+
+ // update the quote data if it already exists (same id), otherwise
+ // insert it
+ this._collection!.update( { id: id },
+ document,
+
+ // create record if it does not yet exist
+ { upsert: true },
+
+ // on complete
+ function( err, _docs )
+ {
+ // if an error occurred, then we cannot continue
+ if ( err )
+ {
+ dao.emit( 'saveQuoteError', err, quote );
+
+ // let the caller handle the error
+ if ( failure_callback instanceof Function )
+ {
+ failure_callback.call( dao, quote );
+ }
+
+ return;
+ }
+
+ // successful
+ if ( success_callback instanceof Function )
+ {
+ success_callback.call( dao, quote );
+ }
+ }
+ );
+
+ return this;
+ }
+
+
+ /**
+ * Merges quote data with the existing (rather than overwriting)
+ *
+ * @param {Quote} quote quote to save
+ * @param {Object} data quote data
+ * @param {Function} scallback successful callback
+ * @param {Function} fcallback failure callback
+ */
+ mergeData(
+ quote: ServerSideQuote,
+ data: MongoUpdate,
+ scallback: Callback,
+ fcallback: Callback,
+ ): this
+ {
+ // we do not want to alter the original data; use it as a prototype
+ var update = data;
+
+ // save the stack so we can track this call via the oplog
+ var _self = this;
+ this._collection!.update( { id: quote.getId() },
+ { '$set': update },
+ {},
+
+ function( err, _docs )
+ {
+ if ( err )
+ {
+ _self.emit( 'saveQuoteError', err, quote );
+
+ if ( typeof fcallback === 'function' )
+ {
+ fcallback( quote );
+ }
+
+ return;
+ }
+
+ if ( typeof scallback === 'function' )
+ {
+ scallback( quote );
+ }
+ }
+ );
+
+ return this;
+ }
+
+
+ /**
+ * Merges bucket data with the existing bucket (rather than overwriting the
+ * entire bucket)
+ *
+ * @param {Quote} quote quote to save
+ * @param {Object} data bucket data
+ * @param {Function} scallback successful callback
+ * @param {Function} fcallback failure callback
+ *
+ * @return {MongoServerDao} self
+ */
+ mergeBucket(
+ quote: ServerSideQuote,
+ data: MongoUpdate,
+ success: Callback,
+ failure: Callback,
+ ): this
+ {
+ var update: MongoUpdate = {};
+
+ for ( var field in data )
+ {
+ if ( !field )
+ {
+ continue;
+ }
+
+ update[ 'data.' + field ] = data[ field ];
+ }
+
+ return this.mergeData( quote, update, success, failure );
+ }
+
+
+ /**
+ * Saves the quote state to the database
+ *
+ * The quote state includes the current step, the top visited step and the
+ * explicit lock message.
+ *
+ * @param Quote quote the quote to save
+ * @param Function success_callback function to call on success
+ * @param Function failure_callback function to call if save fails
+ *
+ * @return MongoServerDao self
+ */
+ saveQuoteState(
+ quote: ServerSideQuote,
+ success_callback: any,
+ failure_callback: any,
+ )
+ {
+ var update = {
+ currentStepId: quote.getCurrentStepId(),
+ topVisitedStepId: quote.getTopVisitedStepId(),
+ topSavedStepId: quote.getTopSavedStepId(),
+ };
+
+ return this.mergeData(
+ quote, update, success_callback, failure_callback
+ );
+ }
+
+
+ saveQuoteClasses(
+ quote: ServerSideQuote,
+ classes: any,
+ success: any,
+ failure: any,
+ )
+ {
+ return this.mergeData(
+ quote,
+ { classData: classes },
+ success,
+ failure
+ );
+ }
+
+
+ /**
+ * Save document metadata (meta field on document)
+ *
+ * Only the provided indexes will be modified (that is---data will be
+ * merged with what is already in the database).
+ *
+ * @param {Quote} quote destination quote
+ * @param {Object} new_meta bucket-formatted data to write
+ * @param {Function} success callback on success
+ * @param {Function} failure callback on error
+ *
+ * @return {undefined}
+ */
+ saveQuoteMeta(
+ quote: ServerSideQuote,
+ new_meta: any,
+ success: Callback,
+ failure: Callback,
+ ): void
+ {
+ const update: MongoUpdate = {};
+
+ for ( var key in new_meta )
+ {
+ var meta = new_meta[ key ];
+
+ for ( var i in meta )
+ {
+ update[ 'meta.' + key + '.' + i ] = new_meta[ key ][ i ];
+ }
+ }
+
+ this.mergeData( quote, update, success, failure );
+ }
+
+
+ /**
+ * Saves the quote lock state to the database
+ *
+ * @param Quote quote the quote to save
+ * @param Function success_callback function to call on success
+ * @param Function failure_callback function to call if save fails
+ *
+ * @return MongoServerDao self
+ */
+ saveQuoteLockState(
+ quote: ServerSideQuote,
+ success_callback: Callback,
+ failure_callback: Callback,
+ ): this
+ {
+ // lock state is saved by default
+ return this.saveQuote(
+ quote,
+ success_callback,
+ failure_callback,
+ {}
+ );
+ }
+
+
+ /**
+ * Pulls quote data from the database
+ *
+ * @param Integer quote_id id of quote
+ * @param Function( data ) callback function to call when data is available
+ *
+ * @return MongoServerDao self to allow for method chaining
+ */
+ pullQuote(
+ quote_id: PositiveInteger,
+ callback: ( data: Record | null ) => void
+ ): this
+ {
+ var dao = this;
+
+ // XXX: TODO: Do not read whole of record into memory; filter out
+ // revisions!
+ this._collection!.find( { id: quote_id }, { limit: 1 },
+ function( _err, cursor )
+ {
+ cursor.toArray( function( _err: NullableError, data: any[] )
+ {
+ // was the quote found?
+ if ( data.length == 0 )
+ {
+ callback.call( dao, null );
+ return;
+ }
+
+ // return the quote data
+ callback.call( dao, data[ 0 ] );
+ });
+ }
+ );
+
+ return this;
+ }
+
+
+ getMinQuoteId( callback: ( min_id: number ) => void ): this
+ {
+ // just in case it's asynchronous later on
+ callback.call( this, this.SEQ_QUOTE_ID_DEFAULT );
+
+ return this;
+ }
+
+
+ getMaxQuoteId( callback: ( min_id: number ) => void ): void
+ {
+ var dao = this;
+
+ this._seqCollection!.find(
+ { _id: this.SEQ_QUOTE_ID },
+ { limit: 1 },
+ function( _err, cursor )
+ {
+ cursor.toArray( function( _err: NullableError, data: any[] )
+ {
+ if ( data.length == 0 )
+ {
+ callback.call( dao, 0 );
+ return;
+ }
+
+ // return the max quote id
+ callback.call( dao, data[ 0 ].val );
+ });
+ }
+ );
+ }
+
+
+ getNextQuoteId( callback: ( quote_id: number ) => void ): this
+ {
+ var dao = this;
+
+ this._seqCollection!.findAndModify(
+ { _id: this.SEQ_QUOTE_ID },
+ [ [ 'val', 'descending' ] ],
+ { $inc: { val: 1 } },
+ { 'new': true },
+
+ function( err, doc )
+ {
+ if ( err )
+ {
+ dao.emit( 'seqError', err );
+
+ callback.call( dao, 0 );
+ return;
+ }
+
+ // return the new id
+ callback.call( dao, doc.val );
+ }
+ );
+
+ return this;
+ }
+
+
+ /**
+ * Create a new revision with the provided quote data
+ *
+ * The revision will contain the whole the quote. If space is a concern, we
+ * can (in the future) calculate a delta instead (Mike recommends the Git
+ * model of storing the deltas in previous revisions and the whole of the
+ * bucket in the most recently created revision).
+ */
+ createRevision(
+ quote: ServerSideQuote,
+ callback: ErrorCallback,
+ ): void
+ {
+ var _self = this,
+ qid = quote.getId(),
+ data = quote.getBucket().getData();
+
+ this._collection!.update( { id: qid },
+ { '$push': { revisions: { data: data } } },
+
+ // create record if it does not yet exist
+ { upsert: true },
+
+ // on complete
+ function( err )
+ {
+ if ( err )
+ {
+ _self.emit( 'mkrevError', err );
+ }
+
+ callback( err );
+ return;
+ }
+ );
+ }
+
+
+ getRevision(
+ quote: ServerSideQuote,
+ revid: PositiveInteger,
+ callback: ErrorCallback,
+ ): void
+ {
+ revid = +revid;
+
+ // XXX: TODO: Filter out all but the revision we want
+ this._collection!.find(
+ { id: quote.getId() },
+ { limit: 1 },
+ function( _err, cursor )
+ {
+ cursor.toArray( function( _err: NullableError, data: any[] )
+ {
+ // was the quote found?
+ if ( ( data.length === 0 )
+ || ( data[ 0 ].revisions.length < ( revid + 1 ) )
+ )
+ {
+ callback( null );
+ return;
+ }
+
+ // return the quote data
+ callback( data[ 0 ].revisions[ revid ] );
+ });
+ }
+ );
+ }
+
+
+ setWorksheets(
+ qid: QuoteId,
+ data: MongoUpdate,
+ callback: NodeCallback,
+ ): void
+ {
+ this._collection!.update( { id: qid },
+ { '$set': { worksheets: { data: data } } },
+
+ // create record if it does not yet exist
+ { upsert: true },
+
+ // on complete
+ function( err )
+ {
+ callback( err );
+ return;
+ }
+ );
+ }
+
+
+ getWorksheet(
+ qid: QuoteId,
+ supplier: string,
+ index: PositiveInteger,
+ callback: ( data: WorksheetData | null ) => void,
+ ): void
+ {
+ this._collection!.find(
+ { id: qid },
+ { limit: 1 },
+ function( _err, cursor )
+ {
+ cursor.toArray( function( _err: NullableError, data: any[] )
+ {
+ // was the quote found?
+ if ( ( data.length === 0 )
+ || ( !data[ 0 ].worksheets )
+ || ( !data[ 0 ].worksheets.data )
+ || ( !data[ 0 ].worksheets.data[ supplier ] )
+ )
+ {
+ callback( null );
+ return;
+ }
+
+ // return the quote data
+ callback( data[ 0 ].worksheets.data[ supplier ][ index ] );
+ } );
+ }
+ );
+ }
+};
diff --git a/src/server/db/ServerDao.d.ts b/src/server/db/ServerDao.d.ts
index 6cc8025..59228c3 100644
--- a/src/server/db/ServerDao.d.ts
+++ b/src/server/db/ServerDao.d.ts
@@ -131,7 +131,7 @@ export interface ServerDao
qid: QuoteId,
data: WorksheetData,
callback: NodeCallback,
- ): this;
+ ): void;
/**
@@ -147,5 +147,5 @@ export interface ServerDao
supplier: string,
index: PositiveInteger,
callback: ( data: WorksheetData | null ) => void,
- ): this;
+ ): void;
}
diff --git a/src/server/quote/ServerSideQuote.d.ts b/src/server/quote/ServerSideQuote.d.ts
index c8bdf2f..1e00d3f 100644
--- a/src/server/quote/ServerSideQuote.d.ts
+++ b/src/server/quote/ServerSideQuote.d.ts
@@ -68,4 +68,20 @@ export declare class ServerSideQuote extends BaseQuote
* @return rating data
*/
getRatingData(): Record;
+
+
+ /**
+ * Metadata bucket
+ *
+ * @return the metadata bucket
+ */
+ getMetabucket(): QuoteDataBucket;
+
+
+ /**
+ * Get the program version
+ *
+ * @return program version
+ */
+ getProgramVersion(): string;
}
diff --git a/src/server/service/RatingService.ts b/src/server/service/RatingService.ts
index ea4667e..83cbd10 100644
--- a/src/server/service/RatingService.ts
+++ b/src/server/service/RatingService.ts
@@ -433,7 +433,7 @@ export class RatingService
}
}
- this._dao.setWorksheets( qid, worksheets, ( err: Error | null ) =>
+ this._dao.setWorksheets( qid, worksheets, ( err: NullableError ) =>
{
if ( err )
{
diff --git a/src/server/service/RatingServicePublish.js b/src/server/service/RatingServicePublish.js
index 2ce2d98..ce843f0 100644
--- a/src/server/service/RatingServicePublish.js
+++ b/src/server/service/RatingServicePublish.js
@@ -1,4 +1,3 @@
-"use strict";
/**
* Publishes message to queue after rating
*
@@ -19,22 +18,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-var __extends = (this && this.__extends) || (function () {
- var extendStatics = function (d, b) {
- extendStatics = Object.setPrototypeOf ||
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
- function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
- return extendStatics(d, b);
- };
- return function (d, b) {
- extendStatics(d, b);
- function __() { this.constructor = d; }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
- };
-})();
-Object.defineProperty(exports, "__esModule", { value: true });
-var RatingService_1 = require("./RatingService");
-var amqplib_1 = require("amqplib");
+
+'use strict';
+
+const { Interface, Trait } = require( 'easejs' );
+const { RatingService } = require( './RatingService' );
+
+
/**
* Publish message to a queue after rating
*
@@ -60,11 +50,35 @@ var amqplib_1 = require("amqplib");
*
* See the body of `#_sendMessage' for their values.
*/
-var RatingServicePublish = /** @class */ (function (_super) {
- __extends(RatingServicePublish, _super);
- function RatingServicePublish() {
- return _super !== null && _super.apply(this, arguments) || this;
- }
+module.exports = Trait( 'RatingServicePublish' )
+ .implement( Interface( { 'postProcessRaterData': [] } ) )
+ .extend(
+{
+ /**
+ * AMQP library (amqplib API)
+ *
+ * @type {amqplib}
+ */
+ 'private _amqp': null,
+
+ /**
+ * AMQP configuration
+ *
+ * This should be the configuration expected by amqplib's #connect. It
+ * should additionally contain a `queueName' field.
+ *
+ * @type {Object}
+ */
+ 'private _conf': {},
+
+ /**
+ * Logger
+ *
+ * @type {DebugLog}
+ */
+ 'private _log': null,
+
+
/**
* Initialize trait
*
@@ -72,13 +86,14 @@ var RatingServicePublish = /** @class */ (function (_super) {
* @param {Object} conf AMQP configuration
* @param {DebugLog} logger logger instance
*/
- RatingServicePublish.prototype.__mixin = function (
- // constructor(
- // private readonly _amqp: Connection,
- _conf) {
- // super();
- this._conf = _conf;
- };
+ __mixin( amqp, conf, logger )
+ {
+ this._amqp = amqp;
+ this._conf = conf;
+ this._log = logger;
+ },
+
+
/**
* Publish quote message to exchange post-rating
*
@@ -90,25 +105,44 @@ var RatingServicePublish = /** @class */ (function (_super) {
*
* @return {undefined}
*/
- RatingServicePublish.prototype.postProcessRaterData = function (request, data, actions, program, quote) {
- var _this = this;
+ 'abstract override postProcessRaterData'(
+ request, data, actions, program, quote
+ )
+ {
// check both as we transition from one to the other
- var exchange = this._conf.exchange;
- amqplib_1.connect(this._conf)
- .then(function (conn) {
- setTimeout(function () { return conn.close(); }, 10000);
- return conn.createChannel();
- })
- .then(function (ch) {
- ch.assertExchange(exchange, 'fanout', { durable: true });
- return _this._sendMessage(ch, exchange, request.getSession(), quote);
- })
- .then(function () { return _this._logger.log(_this._logger.PRIORITY_INFO, "Published quote " + quote.getId() +
- " to post-rate exchange '" + exchange + "'"); })
- .catch(function (e) { return _this._logger.log(_this._logger.PRIORITY_ERROR, "Post-rate exchange publish failure for quote " +
- quote.getId() + ": " + e.message); });
- _super.prototype.postProcessRaterData.call(this, request, data, actions, program, quote);
- };
+ const exchange = this._conf.exchange || this._conf.queueName;
+
+ this._amqp.connect( this._conf )
+ .then( conn =>
+ {
+ setTimeout( () => conn.close(), 10000 );
+ return conn.createChannel();
+ } )
+ .then( ch => {
+ ch.assertExchange( exchange, 'fanout', { durable: true } );
+
+ return this._sendMessage(
+ ch,
+ exchange,
+ request.getSession(),
+ quote
+ );
+ } )
+ .then( () => this._log.log(
+ this._log.PRIORITY_INFO,
+ "Published quote " + quote.getId() +
+ " to post-rate exchange '" + exchange + "'"
+ ) )
+ .catch( e => this._log.log(
+ this._log.PRIORITY_ERROR,
+ "Post-rate exchange publish failure for quote " +
+ quote.getId() + ": " + e.message
+ ) );
+
+ this.__super( request, data, actions, program, quote );
+ },
+
+
/**
* Send message to exchange
*
@@ -117,25 +151,31 @@ var RatingServicePublish = /** @class */ (function (_super) {
* @param {UserSession} session user session
* @param {Quote} quote rated quote
*
- * @return whether publish was successful
+ * @return {Promise} whether publish was successful
*/
- RatingServicePublish.prototype._sendMessage = function (channel, exchange, session, quote) {
- var headers = {
+ 'private _sendMessage'( channel, exchange, session, quote )
+ {
+ const headers = {
version: 1,
created: Date.now(),
};
- var data = new Buffer(JSON.stringify({
- quote_id: quote.getId(),
- program_id: quote.getProgramId(),
- agent_id: session.agentId(),
- entity_id: session.agentEntityId(),
+
+ const data = new Buffer( JSON.stringify( {
+ quote_id: quote.getId(),
+ program_id: quote.getProgramId(),
+ agent_id: session.agentId(),
+ entity_id: session.agentEntityId(),
entity_name: session.agentName(),
- }));
+ } ) );
+
// we don't use a routing key; fanout exchange
- var routing_key = '';
- return channel.publish(exchange, routing_key, data, { headers: headers });
- };
- return RatingServicePublish;
-}(RatingService_1.RatingService));
-exports.RatingServicePublish = RatingServicePublish;
-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmF0aW5nU2VydmljZVB1Ymxpc2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJSYXRpbmdTZXJ2aWNlUHVibGlzaC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7Ozs7Ozs7Ozs7Ozs7OztBQUdILGlEQUFnRDtBQVFoRCxtQ0FJaUI7QUFRakI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCRztBQUNIO0lBQTBDLHdDQUFhO0lBQXZEOztJQTJIQSxDQUFDO0lBOUdHOzs7Ozs7T0FNRztJQUNILHNDQUFPLEdBQVA7SUFDQSxlQUFlO0lBQ1gsd0NBQXdDO0lBQ3hDLEtBQW1CO1FBRW5CLFdBQVc7UUFDWCxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztJQUN2QixDQUFDO0lBR0Q7Ozs7Ozs7Ozs7TUFVRTtJQUNPLG1EQUFvQixHQUE5QixVQUNLLE9BQW9CLEVBQ3BCLElBQW1CLEVBQ25CLE9BQXNCLEVBQ3RCLE9BQWdCLEVBQ2hCLEtBQXdCO1FBTDdCLGlCQXVDRTtRQS9CRyxvREFBb0Q7UUFDcEQsSUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUM7UUFFckMsaUJBQVcsQ0FBRSxJQUFJLENBQUMsS0FBSyxDQUFFO2FBQ3BCLElBQUksQ0FBRSxVQUFBLElBQUk7WUFFUCxVQUFVLENBQUUsY0FBTSxPQUFBLElBQUksQ0FBQyxLQUFLLEVBQUUsRUFBWixDQUFZLEVBQUUsS0FBSyxDQUFFLENBQUM7WUFDeEMsT0FBTyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDaEMsQ0FBQyxDQUFFO2FBQ0YsSUFBSSxDQUFFLFVBQUEsRUFBRTtZQUNMLEVBQUUsQ0FBQyxjQUFjLENBQUUsUUFBUSxFQUFFLFFBQVEsRUFBRSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBRSxDQUFDO1lBRTNELE9BQU8sS0FBSSxDQUFDLFlBQVksQ0FDcEIsRUFBRSxFQUNGLFFBQVEsRUFDUixPQUFPLENBQUMsVUFBVSxFQUFFLEVBQ3BCLEtBQUssQ0FDUixDQUFDO1FBQ04sQ0FBQyxDQUFFO2FBQ0YsSUFBSSxDQUFFLGNBQU0sT0FBQSxLQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FDekIsS0FBSSxDQUFDLE9BQU8sQ0FBQyxhQUFhLEVBQzFCLGtCQUFrQixHQUFHLEtBQUssQ0FBQyxLQUFLLEVBQUU7WUFDOUIsMEJBQTBCLEdBQUcsUUFBUSxHQUFHLEdBQUcsQ0FDbEQsRUFKWSxDQUlaLENBQUU7YUFDRixLQUFLLENBQUUsVUFBQSxDQUFDLElBQUksT0FBQSxLQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FDekIsS0FBSSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEVBQzNCLCtDQUErQztZQUMzQyxLQUFLLENBQUMsS0FBSyxFQUFFLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQ3ZDLEVBSlksQ0FJWixDQUFFLENBQUM7UUFFUixpQkFBTSxvQkFBb0IsWUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxDQUFFLENBQUM7SUFDekUsQ0FBQztJQUdEOzs7Ozs7Ozs7T0FTRztJQUNILDJDQUFZLEdBQVosVUFDSSxPQUFpQixFQUNqQixRQUFnQixFQUNoQixPQUFxQixFQUNyQixLQUF5QjtRQUd6QixJQUFNLE9BQU8sR0FBRztZQUNaLE9BQU8sRUFBRSxDQUFDO1lBQ1YsT0FBTyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUU7U0FDdEIsQ0FBQztRQUVGLElBQU0sSUFBSSxHQUFHLElBQUksTUFBTSxDQUFFLElBQUksQ0FBQyxTQUFTLENBQUU7WUFDckMsUUFBUSxFQUFLLEtBQUssQ0FBQyxLQUFLLEVBQUU7WUFDMUIsVUFBVSxFQUFHLEtBQUssQ0FBQyxZQUFZLEVBQUU7WUFDakMsUUFBUSxFQUFLLE9BQU8sQ0FBQyxPQUFPLEVBQUU7WUFDOUIsU0FBUyxFQUFJLE9BQU8sQ0FBQyxhQUFhLEVBQUU7WUFDcEMsV0FBVyxFQUFFLE9BQU8sQ0FBQyxTQUFTLEVBQUU7U0FDbkMsQ0FBRSxDQUFFLENBQUM7UUFFTiw4Q0FBOEM7UUFDOUMsSUFBTSxXQUFXLEdBQUcsRUFBRSxDQUFDO1FBRXZCLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FDbEIsUUFBUSxFQUNSLFdBQVcsRUFDWCxJQUFJLEVBQ0osRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLENBQ3ZCLENBQUM7SUFDTixDQUFDO0lBQ0wsMkJBQUM7QUFBRCxDQUFDLEFBM0hELENBQTBDLDZCQUFhLEdBMkh0RDtBQTNIWSxvREFBb0IifQ==
\ No newline at end of file
+ const routing_key = '';
+
+ return channel.publish(
+ exchange,
+ routing_key,
+ data,
+ { headers: headers }
+ );
+ },
+} );
diff --git a/src/server/service/RatingServicePublish.ts b/src/server/service/RatingServicePublish.ts
deleted file mode 100644
index 1966759..0000000
--- a/src/server/service/RatingServicePublish.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * Publishes message to queue after rating
- *
- * 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 { RatingService } from "./RatingService";
-import { UserRequest } from "../request/UserRequest";
-import { RateResult } from "../rater/Rater";
-import { ClientActions } from "../../client/action/ClientAction";
-import { Program } from "../../program/Program";
-import { ServerSideQuote } from "../quote/ServerSideQuote";
-import { UserSession } from "../request/UserSession";
-
-import {
- connect as amqpConnect,
- Options,
- Channel,
-} from 'amqplib';
-
-
-export interface AmqpConfig extends Options.Connect {
- /** The name of a queue or exchange to publish to */
- exchange: string;
-}
-
-/**
- * Publish message to a queue after rating
- *
- * This is an initial proof-of-concept implementation. In particular, we
- * have the following considerations:
- *
- * - A fresh connection is made for each message until we can ensure that
- * we can auto-reconnect on failure;
- * - This trait is not yet tested;
- * - It does not use an exchange;
- * - It does a poor job checking for and reporting errors.
- *
- * The message consists of a `version' header that is set to 1. Future
- * changes to the message format will bump this version. There is also a
- * `created' header holding a Unix timestamp of the moment that the message
- * was created.
- *
- * Version 1 of the body consists of four fields:
- * - quote_id
- * - agent_id
- * - entity_id
- * - entity_name
- *
- * See the body of `#_sendMessage' for their values.
- */
-export class RatingServicePublish extends RatingService
-{
- /**
- * AMQP configuration
- *
- * This should be the configuration expected by amqplib's #connect. It
- * should additionally contain a `queueName' field.
- *
- * @type {Object}
- */
- private _conf: AmqpConfig;
-
-
- /**
- * Initialize trait
- *
- * @param {amqplib} AMQP library
- * @param {Object} conf AMQP configuration
- * @param {DebugLog} logger logger instance
- */
- __mixin(
- // constructor(
- // private readonly _amqp: Connection,
- _conf: AmqpConfig,
- ) {
- // super();
- this._conf = _conf;
- }
-
-
- /**
- * Publish quote message to exchange post-rating
- *
- * @param {UserRequest} request user request
- * @param {Object} data rating data returned
- * @param {Array} actions actions to send to client
- * @param {Program} program program used to perform rating
- * @param {Quote} quote quote used for rating
- *
- * @return {undefined}
- */
- protected postProcessRaterData(
- request: UserRequest,
- data: RateResult,
- actions: ClientActions,
- program: Program,
- quote: ServerSideQuote,
- ): void
- {
- // check both as we transition from one to the other
- const exchange = this._conf.exchange;
-
- amqpConnect( this._conf )
- .then( conn =>
- {
- setTimeout( () => conn.close(), 10000 );
- return conn.createChannel();
- } )
- .then( ch => {
- ch.assertExchange( exchange, 'fanout', { durable: true } );
-
- return this._sendMessage(
- ch,
- exchange,
- request.getSession(),
- quote
- );
- } )
- .then( () => this._logger.log(
- this._logger.PRIORITY_INFO,
- "Published quote " + quote.getId() +
- " to post-rate exchange '" + exchange + "'"
- ) )
- .catch( e => this._logger.log(
- this._logger.PRIORITY_ERROR,
- "Post-rate exchange publish failure for quote " +
- quote.getId() + ": " + e.message
- ) );
-
- super.postProcessRaterData( request, data, actions, program, quote );
- }
-
-
- /**
- * Send message to exchange
- *
- * @param {Channel} channel AMQP channel
- * @param {string} exchange exchange name
- * @param {UserSession} session user session
- * @param {Quote} quote rated quote
- *
- * @return whether publish was successful
- */
- _sendMessage(
- channel: Channel,
- exchange: string,
- session: UserSession,
- quote: ServerSideQuote
- ): boolean
- {
- const headers = {
- version: 1,
- created: Date.now(),
- };
-
- const data = new Buffer( JSON.stringify( {
- quote_id: quote.getId(),
- program_id: quote.getProgramId(),
- agent_id: session.agentId(),
- entity_id: session.agentEntityId(),
- entity_name: session.agentName(),
- } ) );
-
- // we don't use a routing key; fanout exchange
- const routing_key = '';
-
- return channel.publish(
- exchange,
- routing_key,
- data,
- { headers: headers }
- );
- }
-}
diff --git a/src/server/token/MongoTokenDao.ts b/src/server/token/MongoTokenDao.ts
index ee90d7a..5d3dede 100644
--- a/src/server/token/MongoTokenDao.ts
+++ b/src/server/token/MongoTokenDao.ts
@@ -34,6 +34,8 @@ import { DocumentId } from "../../document/Document";
import { TokenId, TokenNamespace, TokenState } from "./Token";
import { UnknownTokenError } from "./UnknownTokenError";
import { context } from "../../error/ContextError";
+import { MongoCollection } from "mongodb";
+
/**
@@ -118,7 +120,7 @@ export class MongoTokenDao implements TokenDao
},
},
- ( err: Error|null, prev_data ) =>
+ ( err: NullableError, prev_data ) =>
{
if ( err )
{
@@ -250,7 +252,7 @@ export class MongoTokenDao implements TokenDao
this._collection.findOne(
{ id: +doc_id },
{ fields: fields },
- ( err: Error|null, data: TokenQueryResult ) =>
+ ( err: NullableError, data: TokenQueryResult ) =>
{
if ( err || !data )
{
diff --git a/src/types/misc.d.ts b/src/types/misc.d.ts
index 5572739..b26d827 100644
--- a/src/types/misc.d.ts
+++ b/src/types/misc.d.ts
@@ -65,4 +65,7 @@ type UnixTimestampMillis = NominalType;
* reduce the boilerplate of these function definitions, and to clearly
* document that this pattern is something that used to be done frequently.
*/
-type NodeCallback = ( e: Error | null, result: T | null ) => R;
+type NodeCallback = ( e: NullableError, result: T | null ) => R;
+
+/** Nullable error */
+type NullableError = Error | null;
diff --git a/src/types/mongodb.d.ts b/src/types/mongodb.d.ts
index 808b458..4b0e21f 100644
--- a/src/types/mongodb.d.ts
+++ b/src/types/mongodb.d.ts
@@ -23,13 +23,15 @@
* front.
*/
+import { PositiveInteger } from "../numeric";
+
declare module "mongodb";
/**
* Node-style callback for queries
*/
-type MongoCallback = ( err: Error|null, data: { [P: string]: any } ) => void;
+type MongoCallback = ( err: NullableError, data: { [P: string]: any } ) => void;
/**
@@ -52,10 +54,24 @@ interface MongoQueryUpdateOptions
*/
interface MongoFindOneOptions
{
+ /** Fields to select */
fields?: MongoFieldSelector,
}
+/**
+ * Options for `find` queries
+ *
+ * This is not at all comprehensive; it covers only the fields we actually
+ * make use of.
+ */
+interface MongoFindOptions
+{
+ /** Limit results returned */
+ limit?: PositiveInteger,
+}
+
+
/**
* Options for `findAndModify` queries
*
@@ -76,21 +92,26 @@ interface MongoFindAndModifyOptions
/** Mongo query selector */
-type MongoSelector = { [P: string]: any };
-
+export type MongoSelector = { [P: string]: any };
/** Field selector */
type MongoFieldSelector = { [P: string]: number };
+/** Mongo index specification */
+type MongoIndexSpecification = Array< Array < string | number >>;
/** Mongo update clause */
-type MongoUpdate = MongoSelector;
+export type MongoUpdate = MongoSelector;
+/** Mongo object */
+type MongoObject = { [P: string]: any };
+
+/** Mongo update clause */
+type MongoInsertSpecification = MongoObject | MongoObject[];
/** Sorting clause **/
type MongoSortClause = Array;
-
/** Sort direction */
type MongoSortDirection = -1 | 1 | 'ascending' | 'descending' | 'asc' | 'desc';
@@ -126,6 +147,23 @@ declare interface MongoCollection
): void;
+ /**
+ * Execute a query and return the first result
+ *
+ * Unlike `update`, the callback return value is not propagated, and so
+ * the callback ought not return anything.
+ *
+ * @param selector document query
+ * @param fields fields to return
+ * @param callback continuation on completion
+ */
+ find(
+ selector: MongoSelector,
+ fields: MongoFindOptions,
+ callback: MongoCallback
+ ): void;
+
+
/**
* Execute a query and return the first result
*
@@ -158,4 +196,23 @@ declare interface MongoCollection
options: MongoFindAndModifyOptions,
callback: MongoCallback,
): void;
+
+
+ /**
+ * Creates an index on the collection
+ */
+ createIndex(
+ fieldOrSpec: MongoIndexSpecification,
+ options: boolean,
+ callback: MongoCallback,
+ ): void;
+
+
+ /**
+ * Creates an index on the collection
+ */
+ insert(
+ docs: MongoInsertSpecification,
+ callback: MongoCallback,
+ ): void;
}
diff --git a/test/bucket/delta.ts b/test/bucket/delta.ts
index a729f29..ba1d192 100644
--- a/test/bucket/delta.ts
+++ b/test/bucket/delta.ts
@@ -89,96 +89,6 @@ describe( 'Delta', () =>
dest_data: { foo: [ '' ] },
expected: {},
},
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'change', 'baz' ] },
- data: { foo: [ 'change', 'baz' ] },
- diff: { foo: [ 'change' ] },
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', 'change' ] },
- data: { foo: [ 'bar', 'change' ] },
- diff: { foo: [ , 'change' ] },
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ undefined, 'change' ] },
- data: { foo: [ 'bar', 'change' ] },
- diff: { foo: [ , 'change' ] },
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ undefined, 'baz' ] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: {},
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', undefined ] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: {},
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', null ] },
- data: { foo: [ 'bar' ] },
- diff: { foo: [ , null ] },
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', 'baz', null ] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: {},
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [ 'bar', 'baz', 'quux' ] },
- data: { foo: [ 'bar', 'baz', 'quux' ] },
- diff: { foo: [ , , 'quux' ]},
- },
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz' ] },
- dest_data: { foo: [] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: {},
- },
-
- // null not at end of set means unchanged
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz', 'quux' ] },
- dest_data: { foo: [ null, null, 'quux' ] },
- data: { foo: [ 'bar', 'baz', 'quux' ] },
- diff: {},
- },
- // but the last one is
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz', 'quux' ] },
- dest_data: { foo: [ null, 'baz', null ] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: { foo: [ , , null ] },
- },
- // given a string of nulls, only the last one is terminating; the
- // rest are interpreted as undefined (because JSON serializes
- // undefined values to `null' -_-)
- {
- label: "",
- src_data: { foo: [ 'bar', 'baz', 'quux' ] },
- dest_data: { foo: [ null, null, null ] },
- data: { foo: [ 'bar', 'baz' ] },
- diff: { foo: [ , , null ] },
- },
] ).forEach( ( { label, src_data, dest_data, expected } ) =>
{
it( label, () =>
diff --git a/test/server/dapi/TokenedDataApiTest.ts b/test/server/dapi/TokenedDataApiTest.ts
index 27375fa..0d0a3ad 100644
--- a/test/server/dapi/TokenedDataApiTest.ts
+++ b/test/server/dapi/TokenedDataApiTest.ts
@@ -40,7 +40,7 @@ describe( 'TokenedDataApi', () =>
const expected_ns = 'foo_ns';
- ( <[string, boolean, ( e: Error|null ) => void][]>[
+ ( <[string, boolean, ( e: NullableError ) => void][]>[
[
"creates token and returns data if last_created",
true,
diff --git a/test/server/db/MongoServerDaoTest.js b/test/server/db/MongoServerDaoTest.js
index 0fa5d30..d6c8bf1 100644
--- a/test/server/db/MongoServerDaoTest.js
+++ b/test/server/db/MongoServerDaoTest.js
@@ -18,173 +18,140 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
'use strict';
-
-const chai = require( 'chai' );
-const expect = chai.expect;
-const { MongoServerDao: Sut } = require( '../../../' ).server.db;
-
-
-describe( 'MongoServerDao', () =>
-{
- describe( '#saveQuote', () =>
- {
- describe( "with no save data or push data", () =>
- {
- it( "saves entire metabucket record individually", done =>
- {
- const metadata = {
- foo: [ 'bar', 'baz' ],
- bar: [ { quux: 'quuux' } ],
+Object.defineProperty(exports, "__esModule", { value: true });
+var MongoServerDao_1 = require("../../../src/server/db/MongoServerDao");
+var chai_1 = require("chai");
+chai_1.use(require('chai-as-promised'));
+describe('MongoServerDao', function () {
+ describe('#saveQuote', function () {
+ describe("with no save data or push data", function () {
+ it("saves entire metabucket record individually", function (done) {
+ var metadata = {
+ foo: ['bar', 'baz'],
+ bar: [{ quux: 'quuux' }],
};
-
- const quote = createStubQuote( metadata );
-
- const sut = Sut( createMockDb(
- // update
- ( selector, data ) =>
- {
- expect( data.$set[ 'meta.foo' ] )
- .to.deep.equal( metadata.foo );
-
- expect( data.$set[ 'meta.bar' ] )
- .to.deep.equal( metadata.bar );
-
-
- expect( data.$push ).to.equal( undefined );
-
- done();
- }
- ) );
-
- sut.init( () =>
- sut.saveQuote( quote, () => {}, () => {} )
- );
- } );
- } );
-
- describe( "with push data", () =>
- {
- it( "adds push data to the collection", done =>
- {
- const push_data = {
- foo: [ 'bar', 'baz' ],
- bar: [ { quux: 'quuux' } ],
+ var quote = createStubQuote(metadata);
+ var sut = new MongoServerDao_1.MongoServerDao(createMockDb(
+ // update
+ function (_selector, data) {
+ chai_1.expect(data.$set['meta.foo'])
+ .to.deep.equal(metadata.foo);
+ chai_1.expect(data.$set['meta.bar'])
+ .to.deep.equal(metadata.bar);
+ chai_1.expect(data.$push).to.equal(undefined);
+ done();
+ }));
+ sut.init(function () {
+ return sut.saveQuote(quote, function () { }, function () { });
+ });
+ });
+ });
+ describe("with push data", function () {
+ it("adds push data to the collection", function (done) {
+ var push_data = {
+ foo: ['bar', 'baz'],
+ bar: [{ quux: 'quuux' }],
};
-
- const quote = createStubQuote( {} );
-
- const sut = Sut( createMockDb(
- // update
- ( selector, data ) =>
- {
- expect( data.$push[ 'foo' ] )
- .to.deep.equal( push_data.foo );
-
- expect( data.$push[ 'bar' ] )
- .to.deep.equal( push_data.bar );
-
- done();
- }
- ) );
-
- sut.init( () =>
- sut.saveQuote(
- quote,
- () => {},
- () => {},
- undefined,
- push_data
- )
- );
- } );
-
- it( "skips push data when it is an empty object", done =>
- {
- const push_data = {};
-
- const quote = createStubQuote( {} );
-
- const sut = Sut( createMockDb(
- // update
- ( selector, data ) =>
- {
- expect( data.$push ).to.equal( undefined );
-
- done();
- }
- ) );
-
- sut.init( () =>
- sut.saveQuote(
- quote,
- () => {},
- () => {},
- undefined,
- push_data
- )
- );
- } );
- } );
- } );
-} );
-
-
-function createMockDb( on_update )
-{
- const collection_quotes = {
+ var quote = createStubQuote({});
+ var sut = new MongoServerDao_1.MongoServerDao(createMockDb(
+ // update
+ function (_selector, data) {
+ chai_1.expect(data.$push['foo'])
+ .to.deep.equal(push_data.foo);
+ chai_1.expect(data.$push['bar'])
+ .to.deep.equal(push_data.bar);
+ done();
+ }));
+ sut.init(function () {
+ return sut.saveQuote(quote, function () { }, function () { }, undefined, push_data);
+ });
+ });
+ it("skips push data when it is an empty object", function (done) {
+ var push_data = {};
+ var quote = createStubQuote({});
+ var sut = new MongoServerDao_1.MongoServerDao(createMockDb(
+ // update
+ function (_selector, data) {
+ chai_1.expect(data.$push).to.equal(undefined);
+ done();
+ }));
+ sut.init(function () {
+ return sut.saveQuote(quote, function () { }, function () { }, undefined, push_data);
+ });
+ });
+ });
+ });
+});
+function createMockDb(on_update) {
+ var collection_quotes = {
update: on_update,
- createIndex: ( _, __, c ) => c(),
+ createIndex: function (_, __, c) { return c(); },
};
-
- const collection_seq = {
- find( _, __, c )
- {
- c( null, {
- toArray: c => c( null, { length: 5 } ),
- } );
+ var collection_seq = {
+ find: function (_, __, c) {
+ c(null, {
+ toArray: function (c) { return c(null, { length: 5 }); },
+ });
},
};
-
- const db = {
- collection( id, c )
- {
- const coll = ( id === 'quotes' )
+ var db = {
+ collection: function (id, c) {
+ var coll = (id === 'quotes')
? collection_quotes
: collection_seq;
-
- c( null, coll );
+ c(null, coll);
},
};
-
- const driver = {
- open: c => c( null, db ),
- on: () => {},
+ var driver = {
+ open: function (c) { return c(null, db); },
+ on: function () { },
};
-
return driver;
}
-
-
-function createStubQuote( metadata )
-{
- return {
- getBucket: () => ( {
- getData: () => {},
- } ),
-
- getMetabucket: () => ( {
- getData: () => metadata,
- } ),
-
- getId: () => 1,
- getProgramVersion: () => 0,
- getLastPremiumDate: () => 0,
- getRatedDate: () => 0,
- getExplicitLockReason: () => "",
- getExplicitLockStep: () => 0,
- isImported: () => false,
- isBound: () => false,
+function createStubQuote(metadata) {
+ var program = {
+ getId: function () { return '1'; },
+ ineligibleLockCount: 0,
+ apis: {},
+ internal: {},
+ meta: {
+ arefs: {},
+ fields: {},
+ groups: {},
+ qdata: {},
+ qtypes: {},
+ },
+ mapis: {},
+ initQuote: function () { },
};
+ var quote = {
+ getBucket: function () { return ({
+ getData: function () { },
+ }); },
+ getMetabucket: function () { return ({
+ getData: function () { return metadata; },
+ }); },
+ getId: function () { return 123; },
+ getProgramVersion: function () { return 'Foo'; },
+ getLastPremiumDate: function () { return 0; },
+ getRatedDate: function () { return 0; },
+ getExplicitLockReason: function () { return ""; },
+ getExplicitLockStep: function () { return 1; },
+ isImported: function () { return false; },
+ isBound: function () { return false; },
+ getTopVisitedStepId: function () { return 1; },
+ getTopSavedStepId: function () { return 1; },
+ setRatedDate: function () { return quote; },
+ setRateBucket: function () { return quote; },
+ setRatingData: function () { return quote; },
+ getRatingData: function () { return ({ _unavailable_all: '0' }); },
+ getProgram: function () { return program; },
+ setExplicitLock: function () { return quote; },
+ getProgramId: function () { return 'Foo'; },
+ getCurrentStepId: function () { return 0; },
+ setLastPremiumDate: function () { return quote; },
+ };
+ return quote;
}
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9uZ29TZXJ2ZXJEYW9UZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiTW9uZ29TZXJ2ZXJEYW9UZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBRUgsWUFBWSxDQUFDOztBQUViLHdFQUE4RTtBQUU5RSw2QkFBK0M7QUFRL0MsVUFBUSxDQUFFLE9BQU8sQ0FBRSxrQkFBa0IsQ0FBRSxDQUFFLENBQUM7QUFHMUMsUUFBUSxDQUFFLGdCQUFnQixFQUFFO0lBRXhCLFFBQVEsQ0FBRSxZQUFZLEVBQUU7UUFFcEIsUUFBUSxDQUFFLGdDQUFnQyxFQUFFO1lBRXhDLEVBQUUsQ0FBRSw2Q0FBNkMsRUFBRSxVQUFBLElBQUk7Z0JBRW5ELElBQU0sUUFBUSxHQUFHO29CQUNiLEdBQUcsRUFBRSxDQUFFLEtBQUssRUFBRSxLQUFLLENBQUU7b0JBQ3JCLEdBQUcsRUFBRSxDQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxDQUFFO2lCQUM3QixDQUFDO2dCQUVGLElBQU0sS0FBSyxHQUFHLGVBQWUsQ0FBRSxRQUFRLENBQUUsQ0FBQztnQkFFMUMsSUFBTSxHQUFHLEdBQUcsSUFBSSwrQkFBRyxDQUFFLFlBQVk7Z0JBQzdCLFNBQVM7Z0JBQ1QsVUFBRSxTQUF3QixFQUFFLElBQWlCO29CQUV6QyxhQUFNLENBQUUsSUFBSSxDQUFDLElBQUksQ0FBRSxVQUFVLENBQUUsQ0FBRTt5QkFDNUIsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUUsUUFBUSxDQUFDLEdBQUcsQ0FBRSxDQUFDO29CQUVuQyxhQUFNLENBQUUsSUFBSSxDQUFDLElBQUksQ0FBRSxVQUFVLENBQUUsQ0FBRTt5QkFDNUIsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUUsUUFBUSxDQUFDLEdBQUcsQ0FBRSxDQUFDO29CQUduQyxhQUFNLENBQUUsSUFBSSxDQUFDLEtBQUssQ0FBRSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENB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
\ No newline at end of file
diff --git a/test/server/db/MongoServerDaoTest.ts b/test/server/db/MongoServerDaoTest.ts
new file mode 100644
index 0000000..b0a83bb
--- /dev/null
+++ b/test/server/db/MongoServerDaoTest.ts
@@ -0,0 +1,227 @@
+/**
+ * Tests MongoServerDao
+ *
+ * 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 .
+ */
+
+'use strict';
+
+import { MongoServerDao as Sut } from "../../../src/server/db/MongoServerDao";
+import { MongoSelector, MongoUpdate } from "mongodb";
+import { expect, use as chai_use } from 'chai';
+import { ServerSideQuote } from "../../../src/server/quote/ServerSideQuote";
+import { PositiveInteger } from "../../../src/numeric";
+import { Program } from "../../../src/program/Program";
+import { RateResult } from "../../../src/server/rater/Rater";
+import { QuoteDataBucket } from "../../../src/bucket/QuoteDataBucket";
+import { QuoteId } from "../../../src/quote/Quote";
+
+chai_use( require( 'chai-as-promised' ) );
+
+
+describe( 'MongoServerDao', () =>
+{
+ describe( '#saveQuote', () =>
+ {
+ describe( "with no save data or push data", () =>
+ {
+ it( "saves entire metabucket record individually", done =>
+ {
+ const metadata = {
+ foo: [ 'bar', 'baz' ],
+ bar: [ { quux: 'quuux' } ],
+ };
+
+ const quote = createStubQuote( metadata );
+
+ const sut = new Sut( createMockDb(
+ // update
+ ( _selector: MongoSelector, data: MongoUpdate ) =>
+ {
+ expect( data.$set[ 'meta.foo' ] )
+ .to.deep.equal( metadata.foo );
+
+ expect( data.$set[ 'meta.bar' ] )
+ .to.deep.equal( metadata.bar );
+
+
+ expect( data.$push ).to.equal( undefined );
+
+ done();
+ }
+ ) );
+
+ sut.init( () =>
+ sut.saveQuote( quote, () => {}, () => {} )
+ );
+ } );
+ } );
+
+ describe( "with push data", () =>
+ {
+ it( "adds push data to the collection", done =>
+ {
+ const push_data = {
+ foo: [ 'bar', 'baz' ],
+ bar: [ { quux: 'quuux' } ],
+ };
+
+ const quote = createStubQuote( {} );
+
+ const sut = new Sut( createMockDb(
+ // update
+ (_selector: MongoSelector, data: MongoUpdate ) =>
+ {
+ expect( data.$push[ 'foo' ] )
+ .to.deep.equal( push_data.foo );
+
+ expect( data.$push[ 'bar' ] )
+ .to.deep.equal( push_data.bar );
+
+ done();
+ }
+ ) );
+
+ sut.init( () =>
+ sut.saveQuote(
+ quote,
+ () => {},
+ () => {},
+ undefined,
+ push_data
+ )
+ );
+ } );
+
+ it( "skips push data when it is an empty object", done =>
+ {
+ const push_data = {};
+
+ const quote = createStubQuote( {} );
+
+ const sut = new Sut( createMockDb(
+ // update
+ ( _selector: MongoSelector, data: MongoUpdate ) =>
+ {
+ expect( data.$push ).to.equal( undefined );
+
+ done();
+ }
+ ) );
+
+ sut.init( () =>
+ sut.saveQuote(
+ quote,
+ () => {},
+ () => {},
+ undefined,
+ push_data
+ )
+ );
+ } );
+ } );
+ } );
+} );
+
+
+function createMockDb( on_update: any )
+{
+ const collection_quotes = {
+ update: on_update,
+ createIndex: ( _: any, __: any, c: any ) => c(),
+ };
+
+ const collection_seq = {
+ find( _: any, __: any, c: any )
+ {
+ c( null, {
+ toArray: ( c: any ) => c( null, { length: 5 } ),
+ } );
+ },
+ };
+
+ const db = {
+ collection( id: any, c: any )
+ {
+ const coll = ( id === 'quotes' )
+ ? collection_quotes
+ : collection_seq;
+
+ c( null, coll );
+ },
+ };
+
+ const driver = {
+ open: ( c: any ) => c( null, db ),
+ on: () => {},
+ };
+
+ return driver;
+}
+
+
+function createStubQuote( metadata: Record )
+{
+ const program = {
+ getId: () => '1',
+ ineligibleLockCount: 0,
+ apis: {},
+ internal: {},
+ meta: {
+ arefs: {},
+ fields: {},
+ groups: {},
+ qdata: {},
+ qtypes: {},
+ },
+ mapis: {},
+ initQuote: () => {},
+ };
+
+ const quote = {
+ getBucket: () => ( {
+ getData: () => {},
+ } ),
+
+ getMetabucket: () => ( {
+ getData: () => metadata,
+ } ),
+
+ getId: () => 123,
+ getProgramVersion: () => 'Foo',
+ getLastPremiumDate: () => 0,
+ getRatedDate: () => 0,
+ getExplicitLockReason: () => "",
+ getExplicitLockStep: () => 1,
+ isImported: () => false,
+ isBound: () => false,
+ getTopVisitedStepId: () => 1,
+ getTopSavedStepId: () => 1,
+ setRatedDate: () => quote,
+ setRateBucket: () => quote,
+ setRatingData: () => quote,
+ getRatingData: () => { _unavailable_all: '0' },
+ getProgram: () => program,
+ setExplicitLock: () => quote,
+ getProgramId: () => 'Foo',
+ getCurrentStepId: () => 0,
+ setLastPremiumDate: () => quote,
+ };
+
+ return quote;
+}
diff --git a/test/server/request/DataProcessorTest.ts b/test/server/request/DataProcessorTest.ts
index a3a4d0e..2be9d61 100644
--- a/test/server/request/DataProcessorTest.ts
+++ b/test/server/request/DataProcessorTest.ts
@@ -713,7 +713,44 @@ function createStubQuote()
getBucket()
{
return new QuoteDataBucket();
- }
+ },
+
+ getMetabucket(){
+ return new QuoteDataBucket();
+ },
+
+ getProgramVersion(){
+ return 'Foo';
+ },
+
+ getExplicitLockReason(){
+ return 'Reason';
+ },
+
+ getExplicitLockStep()
+ {
+ return 1;
+ },
+
+ isImported()
+ {
+ return true;
+ },
+
+ isBound()
+ {
+ return true;
+ },
+
+ getTopVisitedStepId()
+ {
+ return 1;
+ },
+
+ getTopSavedStepId()
+ {
+ return 1;
+ },
};
}
diff --git a/test/server/service/RatingServiceTest.ts b/test/server/service/RatingServiceTest.ts
index 8896c49..546118c 100644
--- a/test/server/service/RatingServiceTest.ts
+++ b/test/server/service/RatingServiceTest.ts
@@ -36,6 +36,7 @@ import { UserRequest } from "../../../src/server/request/UserRequest";
import { UserResponse } from "../../../src/server/request/UserResponse";
import { UserSession } from "../../../src/server/request/UserSession";
import { QuoteDataBucket } from "../../../src/bucket/QuoteDataBucket";
+import { PositiveInteger } from "../../../src/numeric";
import { Kv } from "../../../src/bucket/delta";
import {
@@ -573,19 +574,27 @@ function getStubs()
const response = {};
const quote = {
- getProgramId: () => program_id,
- getProgram: () => program,
- getId: () => 0,
- setLastPremiumDate: () => quote,
- setRatedDate: () => quote,
- getRatedDate: () => 0,
- getLastPremiumDate: () => 0,
- getCurrentStepId: () => 0,
- setExplicitLock: () => quote,
- setRateBucket: () => quote,
- setRatingData: () => quote,
- getRatingData: () => stub_rate_data,
- getBucket: () => new QuoteDataBucket(),
+ getProgramId: () => program_id,
+ getProgram: () => program,
+ getId: () => 0,
+ setLastPremiumDate: () => quote,
+ setRatedDate: () => quote,
+ getRatedDate: () => 0,
+ getLastPremiumDate: () => 0,
+ getCurrentStepId: () => 0,
+ setExplicitLock: () => quote,
+ setRateBucket: () => quote,
+ setRatingData: () => quote,
+ getRatingData: () => stub_rate_data,
+ getBucket: () => new QuoteDataBucket(),
+ getMetabucket: () => new QuoteDataBucket(),
+ getProgramVersion: () => 'Foo',
+ getExplicitLockReason: () => 'Reason',
+ getExplicitLockStep: () => 1,
+ isImported: () => true,
+ isBound: () => true,
+ getTopVisitedStepId: () => 1,
+ getTopSavedStepId: () => 1,
};
return {
diff --git a/test/server/token/MongoTokenDaoTest.ts b/test/server/token/MongoTokenDaoTest.ts
index 167685a..23e403e 100644
--- a/test/server/token/MongoTokenDaoTest.ts
+++ b/test/server/token/MongoTokenDaoTest.ts
@@ -26,7 +26,7 @@ import {
} from "../../../src/server/token/TokenDao";
import { MongoTokenDao as Sut } from "../../../src/server/token/MongoTokenDao";
-
+import { MongoCollection } from "mongodb";
import {
TokenId,
TokenNamespace,
@@ -248,6 +248,9 @@ describe( 'server.token.TokenDao', () =>
update() {},
findOne() {},
+ find() {},
+ createIndex() {},
+ insert() {},
};
return expect(
@@ -269,6 +272,9 @@ describe( 'server.token.TokenDao', () =>
update() {},
findOne() {},
+ find() {},
+ createIndex() {},
+ insert() {},
};
return expect(
@@ -477,6 +483,9 @@ describe( 'server.token.TokenDao', () =>
update() {},
findAndModify() {},
+ find() {},
+ createIndex() {},
+ insert() {},
};
const result = new Sut( coll, field, () => 0 )
@@ -520,6 +529,9 @@ describe( 'server.token.TokenDao', () =>
update() {},
findAndModify() {},
+ find() {},
+ createIndex() {},
+ insert() {},
};
return expect(