cramit
Version:
<a href="https://nodei.co/npm/cramit/" target="_blank"><img src="https://nodei.co/npm/cramit.png?downloads=true&downloadRank=true"></a>
451 lines (394 loc) • 14.4 kB
JavaScript
module.exports = function(cramit, options) {
var _ = require('lodash');
/* ************************************************** *
* ******************** Constructor
* ************************************************** */
/**
* Constructor to setup and initialize a new Fixture.
*
* @param {string} id is a unique identifier for the
* Fixture.
* @param {object|undefined} config is a fixture
* configuration object.
* @param {object|undefined} log is a bunyan instance.
*/
var Fixture = function(id, config, log) {
this.db = cramit.databaseAdapter;
this.id = id || 'UnknownFixtureId';
this.config = config || cramit.config.fixture;
this.log = log || cramit.log;
};
/* ************************************************** *
* ******************** Public API
* ************************************************** */
/**
* Insert all of the fixture's data into the database.
*
* @param {cudCallback} cb is a callback method.
*/
Fixture.prototype.insertAll = function(cb) {
this.db.add(this.getAll(), { fixtureId: this.id }, cb);
};
/**
* Upsert all of the fixture's data in the database.
*
* Upsert refers to inserting the new data or
* updating the existing data.
*
* @param {cudCallback} cb is a callback method.
*/
Fixture.prototype.upsertAll = function(cb) {
this.db.upsert(this.getAll(), { fixtureId: this.id }, cb);
};
/**
* Delete all of the fixture's data from the database.
*
* @param {cudCallback} cb is a callback method.
*/
Fixture.prototype.deleteAll = function(cb) {
this.db.remove(this.getAllAndNew(), { fixtureId: this.id }, cb);
};
/**
* Find an item in the schema's database with the
* specified ID.
*
* @param {crudOneItemCallback} cb is a callback
* method.
*/
Fixture.prototype.findById = function(id, cb) {
this.db.findItemById(this.id, id, cb);
};
/**
* Find an item in the fixture's dataset with
* the specified ID.
*
* @param {crudOneItemCallback} cb is a callback
* method.
*/
Fixture.prototype.findInDatasetById = function(id, cb) {
var items = this.getAllAndNew();
for(var i = 0; i < items.length; i++) {
if(items[i][this.idAttributeName] == id) {
return cb(undefined, items[i]);
}
}
cb();
};
/**
* Populates an item by ID if possible. If an
* object is already populated it will just be
* returned. If the value is not a valid, then
* undefined will be returned and a warning logged.
*
* @param {object|string|undefined} value is the id to populate.
* @param {crudOneItemCallback} is a callback method.
*/
Fixture.prototype.populateId = function(value, cb) {
if(_.isObject(value)) {
cb(undefined, value);
} else if(_.isString(value)){
this.findById(value, cb);
} else {
cramit.log.w("Fixture.populateId(): Invalid value %s", value);
cb(undefined, undefined);
}
};
/**
* Populates a list of items by ID if possible.
* If an object in the list is already populated
* it will be left alone. However if a value is,
* not valid then undefined will be returned in
* it's place and a warning logged.
*
* @param {array} values is the list of ids to populate.
* @param {crudMultipleItemsCallback} is a callback method.
*/
Fixture.prototype.populateIds = function(values, cb) {
var fixture = this;
var tasks = [];
for(var i = 0; i < values.length; i++) {
tasks.push(fixture.createPopulateIdMethod(values[i]))
}
async.parallel(tasks, cb);
};
/**
* Populates an item by ID from the dataset if possible.
* If an object is already populated it will just be
* returned. If the value is not a valid, then
* undefined will be returned and a warning logged.
*
* @param {object|string|undefined} value is the id to populate.
* @param {crudOneItemCallback} is a callback method.
*/
Fixture.prototype.populateIdFromDataset = function(value, cb) {
if(_.isObject(value)) {
cb(undefined, value);
} else if(_.isString(value)){
this.findInDatasetById(value, cb);
} else {
cb(undefined, value);
}
};
/**
* Populates a list of items by ID from the dataset
* if possible. If an object in the list is already
* populated it will be left alone. However if a
* value is, not valid then undefined will be returned
* in it's place and a warning logged.
*
* @param {array} values is the list of ids to populate.
* @param {crudMultipleItemsCallback} is a callback method.
*/
Fixture.prototype.populateIdsFromDataset = function(values, cb) {
var fixture = this;
var tasks = [];
for(var i = 0; i < values.length; i++) {
tasks.push(fixture.createPopulateIdsFromDatasetMethod(values[i]))
}
async.parallel(tasks, cb);
};
/**
* Fixtures that inherit this class should override
* this method.
*
* @return {object} a new item that is not already
* in the main dataset.
*/
Fixture.prototype.getNew = function() {
return {};
};
/**
* Fixtures that inherit this class should override
* this method.
*
* @return {array} the main list of items
*/
Fixture.prototype.getAll = function() {
return [];
};
/**
* @return {array} the entire list of items.
*/
Fixture.prototype.getAllAndNew = function() {
var items = this.getAll();
items.push(this.getNew());
return items;
};
/**
* Compare two objects of the fixture's model
* type. When strict checking is enabled,
* the two objects must be exactly the same.
* When in non-strict mode, only certain attributes
* will be checked for in the actual object.
*
* Fixtures that inherit this class may want to
* override this method.
*
* @param {Object} actual is the object to compare too.
* @param {Object} expected is the object used to compare.
* @param {compareCallback} cb is a callback method.
* @param {Boolean} strict enables or disables strict mode.
*/
Fixture.prototype.compare = function(actual, expected, cb, strict) {
this.compareSuper(actual, expected, cb, strict);
};
/**
* Compare two objects of the fixture's model
* type. When strict checking is enabled,
* the two objects must be exactly the same.
* When in non-strict mode, only attributes
* in expected will be checked for in the
* actual object.
*
* Fixtures that inherit this class may want to
* override the compare method. They can then
* choose to call this super method to perform
* further checking.
*
* @param {Object} actual is the object to compare too.
* @param {Object} expected is the object used to compare.
* @param {compareCallback} cb is a callback method.
* @param {Boolean} strict enables or disables strict mode.
*/
Fixture.prototype.compareSuper = function(actual, expected, cb, strict) {
var fixture = this;
// Make sure expected and actual are valid objects to compare to each other.
if( ! expected || ! _.isObject(expected)) {
if(actual === expected) {
return cb(undefined, true);
} else if(strict || ! actual || ! _.isObject(actual)) {
return cb(undefined, false);
} else {
return cb(undefined, true);
}
// If the expected object is valid, check if the actual object is valid too.
} else if( ! actual || ! _.isObject(actual)) {
return cb(cramit.error.build('compare expected an object, but recieved ' + actual, 400), false);
}
var actualNumberAttributes = Object.keys(actual).length,
expectedNumberAttributes = Object.keys(expected).length;
// Make sure the expected number of attributes exist in the actual object.
if(strict && actualNumberAttributes != expectedNumberAttributes) {
return cb(cramit.error.build('object contains '+actualNumberAttributes+' attributes when '+expectedNumberAttributes+' attributes were expected in strict mode.', 400), false);
}
// Check for each expected attribute.
for(var key in expected) {
if(expected.hasOwnProperty(key)) {
// In strict mode, every expected attribute must be defined.
if( ! expected[key]) {
if(strict === true) {
return cb(cramit.error.build(fixture.id+' Fixture: '+key+' attribute is required in the expected object when using strict mode.'), false);
}
} else {
switch(typeof expected[key]) {
default: // In strict mode, every attribute must be a model attribute.
console.log(fixture.id+' Fixture: Unknown type: ' + typeof expected[key])
//if(strict) {
// return cb(cramit.error.build('expecting an unknown attribute "'+key+'" is not allowed in strict mode.', 500), false);
// }
break;
// Object ID?
case 'object':
//if( ! expected[key].equals(actual[key])) {
// return cb(cramit.error.build(key+' is "'+actual[key]+'" when expected to be "'+expected[key]+'".', 500), false);
// }
break;
// Boolean
case 'boolean':
if(expected[key] !== actual[key]) {
return cb(cramit.error.build(fixture.id+' Fixture: Boolean '+key+' is "'+actual[key]+'" when expected to be "'+expected[key]+'".', 500), false);
}
break;
// Strings
case 'string':
if(expected[key] != actual[key]) {
return cb(cramit.error.build(fixture.id+' Fixture: String '+key+' is "'+actual[key]+'" when expected to be "'+expected[key]+'".', 500), false);
}
break;
// Date
case 'date':
break;
// Number
case 'number':
if(expected[key] !== actual[key]) {
if(_.isDate(actual[key]) || _.isDate(expected[key])) {
if(fixture.dateDiff(new Date(actual[key]), new Date(expected[key])) === 0) {
return cb(undefined, true);
}
}
return cb(cramit.error.build(fixture.id+' Fixture: Number '+key+' is "'+actual[key]+'" when expected to be "'+expected[key]+'".', 500), false);
}
break;
}
}
}
}
cb(undefined, true);
};
/* ************************************************** *
* ******************** Private API
* ************************************************** */
/**
* Returns the difference between two dates. A Positive
* number is a date in the future where negative is in the past.
* If either parameter is not a valid date, undefined will be returned.
* @param a is the minuend date value.
* @param b is the subtrahend date value.
* @param isDLS when true the remainder will include Daylight Savings Time.
*/
Fixture.prototype.dateDiff = function(a, b, isDLS) {
if(_.isDate(a) && _.isDate(b)) {
if(isDLS) {
return (a.getTime() - b.getTime());
} else {
// Discard the time and time-zone information.
var utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return (utc1 - utc2);
}
}
return undefined;
};
/**
* Create an asynchronous method to populate an ID.
* @param {object|string|undefined} value is the id to populate.
* @return {asyncFunction} is populateId method.
*/
Fixture.prototype.createPopulateIdMethod = function(value) {
var fixture = this;
return function(cb) {
fixture.populateId(value, cb);
}
};
/**
* Create an asynchronous method to populate an ID using
* the fixture's dataset.
* @param {object|string|undefined} value is the id to populate.
* @return {asyncFunction} is populateId method.
*/
Fixture.prototype.createPopulateIdFromDatasetMethod = function(value) {
var fixture = this;
return function(cb) {
fixture.populateIdFromDataset(value, cb);
}
};
/* ************************************************** *
* ******************** Expose Fixture
* ************************************************** */
return Fixture;
};
/* ************************************************** *
* ******************** Documentation Stubs
* ************************************************** */
/**
* A callback used when fixture data is inserted, updated,
* or deleted in the database. The result data will be an
* array of objects. Each object will contain a transaction
* and result key. The result key's value will relate to the
* data inserted, updated, or deleted. The transaction key's
* data will be related to the transaction used to modify the
* data in the database.
*
* @callback cudCallback
* @param {object|undefined} error describes the error that occurred.
* @param {array|undefined} result is a list of objects with
* information related to the database action.
*/
/**
* A callback used when fixture data is queried, inserted,
* updated, or deleted in the database or dataset. The
* result data will be the object or modified object.
*
* @callback crudOneItemCallback
* @param {object|undefined} error describes the error that
* occurred.
* @param {object|undefined} result is the object.
*/
/**
* A callback used when fixture data is queried, inserted,
* updated, or deleted in the database or dataset. The
* result data will be an array of objects or modified objects.
*
* @callback crudMultipleItemsCallback
* @param {object|undefined} error describes the error that
* occurred.
* @param {array|undefined} result is the array of objects.
*/
/**
* An asynchronous function that accepts only a callback as the parameter.
* All results will be passed to the callback with the first parameter
* being an error.
*
* @callback asyncFunction
* @param {function} cb is a callback method.
*/
/**
* A callback used to return the results of comparing two
* objects.
*
* @callback compareCallback
* @param {object|undefined} error describes the error that
* occurred.
* @param {boolean} result describes if the two objects are
* equal or not.
*/