/**
* @version 1.0
* @author Boris GBAHOUE
* @file Test template => create a DBObject
* @module amiwo/test/db
*/
// =============================================================
// BASE SETUP
// =============================================================
// call the packages we need
const debug = require('debug')('comingup:test');
const util = require('util');
const u = require('../../util');
const when = require('when');
// load our objects
const Template = require('../Template');
const DBObject = require('../../db/DBObject');
const ResponseJSON = require('../../rest/ResponseJSON');
const DBObjectFactory = require('../../db/DBObjectFactory');
const GenericError = require('../../error/GenericError');
const DuplicateObjectError = require('../../error/DuplicateObjectError');
// =============================================================
// CONSTRUCTOR
// =============================================================
/**
* @class
*
* @constructor
* @augments module:amiwo/test~Template
* @param {String} name: valid DBObject name
* @param {Object} data: data set
* @param {Object} [uri]: {route, method} object if not POST @ '/api/<name>'
* @param {Object} [options]
* @param {String} options.returnedProp: name of the property which contains the object / objectId
* @param {boolean} options.id: true => returns an ID only; false => return a full object
* @param {boolean} options.check: set to true to check if data from created object match data
* @param {function} options.compareFn: function that takes (key, createdValue, inputValue, isCreatedValueDBObject) and must return true if value1 is equal to value2. Used, if check is set to true, to check if the created object matches the input data (amiwo's smartEqual by default)
* @param {String} [options.exists=err]: define behavior if such a DBObject already exists => "reuse", "err"
*
*/
function DBObjectCreateTemplate(name, data, uri, options) {
Template.call(this);
this._name = name;
this._data = data;
this._uri = uri;
// Options processing
this._options = options || {};
// - exists
if (this._options.exists == null) this._options.exists = "err";
// - compareFn => amiwo's smartEqual by default
if (this._options.compareFn == null) {
this._options.compareFn = function $compare(key, createdValue, inputValue) {
return u.smartEqual(createdValue, inputValue, true); // ignore order if arrays
}
}
}
/**
* Inherit from `Template`.
*/
util.inherits(DBObjectCreateTemplate, Template);
// =============================================================
// PUBLIC METHODS
// =============================================================
/**
* Create a new DBObject from class 'this._name' by calling the associated route
*
* @return {Promise} promise wrapping the created DBObject
*/
DBObjectCreateTemplate.prototype.execute = function() {
var self = this;
var object;
return when.resolve()
.then(function() {
return self.$createObject();
}).then(function(object) {
if (self._options.check) {
for (var key in self._data) {
// compare values: compareFn returns true is the values match
var tmp = self.$compare(key, object[key], self._data[key]); // for breakpoints
if (self.$compare(key, object[key], self._data[key])) continue;
// else => values don't match => error
return when.reject(new Error("::DBOBJECTCREATETEMPLATE::"+self._name.toUpperCase()+"::ERROR "+key+" don't match: created object = "+JSON.stringify(object[key])+" vs input = "+JSON.stringify(self._data[key])))
}
}
debug("::AMIWO::TESTS::%s::CREATETEMPLATE::LOG\t\t* %s successfully created", self._name.toUpperCase(), self._name);
return when.resolve(object);
});
}
// =============================================================
// PRIVATE FUNCTIONS
// =============================================================
/**
* Create the object dealing with duplicate as per _option.exists
*
* @return {Promise} wrapping the created object
*/
DBObjectCreateTemplate.prototype.$createObject = function() {
var self = this;
// Send a request to the back server to create the DBObject
var options = {
method: self._uri.method,
uri: self._uri.route,
json: true // Automatically stringifies the body to JSON
};
if (self._uri.method.toUpperCase() == "GET") {
options.qs = self._data;
} else {
options.body = self._data;
}
return u.request(options).then(function(json) {
// Expects a proper ResponseJSON
if (!ResponseJSON.isValid(json)) return when.reject(new Error("::DBOBJECTCREATETEMPLATE::"+self._name.toUpperCase()+"::ERROR Invalid JSON returned from "+self._uri.method+"@"+self._uri.route+" => json = "+JSON.stringify(json)));
if (json.connection == "ko") return when.reject(new Error("::DBOBJECTCREATETEMPLATE::"+self._name.toUpperCase()+"::ERROR Error JSON returned from "+self._uri.method+"@"+self._uri.route));
// Get the full object (if needed)
var object = json.data[self._options.returnedProp];
var promise;
if (self._options.id) {
promise = DBObjectFactory.createObject(self._name).findById(object);
} else {
promise = when.resolve(object);
}
return promise;
}).catch(function(err) {
var error = err;
if (ResponseJSON.isValid(err)) {
if (err.data && err.data.error) {
error = err.data.error;
} else {
error = new GenericError(err.message);
}
}
if ((error instanceof DuplicateObjectError) || (error.name == "DuplicateObjectError")) {
switch (self._options.exists) {
case "reuse":
var duplicates = error.object;
// If there is only one duplicate use it, else err
if (u.isNotEmpty(duplicates) && (duplicates.length == 1)) {
return when.resolve(duplicates[0]);
} else if (u.isEmpty(duplicates)) {
return when.reject("Weird case: DuplicateObjectError received yet no duplicates");
} else {
return when.reject(new Error(util.format("Too many duplicate objects to handle (%d objects received)", duplicates.length)));
}
case "err":
return when.reject(error);
}
}
return when.reject(error);
})
}
/**
* Compare 'createdValue' with 'inputValue' handling special cases
*
* @returns {boolean}
*/
DBObjectCreateTemplate.prototype.$compare = function(key, createdValue, inputValue) {
// - handle Objects (i.e. Array or Object)
if (Array.isArray(createdValue)) {
if ((createdValue == null) || (inputValue == null)) return false;
if (!Array.isArray(inputValue) || (inputValue.length != createdValue.length)) return false;
for (var i=0; i < createdValue.length; i++) {
if (this.$compare(key, createdValue[i], inputValue[i])) continue;
// we reach here if values don't match
return false;
}
return true;
} else if (createdValue instanceof Object) {
return this._options.compareFn(key, createdValue, inputValue, $isDBObject(createdValue));
} else {
return this._options.compareFn(key, createdValue, inputValue);
}
}
/**
* Checks if 'obj' is an instanceof of DBObject or a leanified DBObject
* @memberOf DBObjectCreateTemplate
* @private
*/
function $isDBObject(obj) {
if (obj == null) return false;
if (obj instanceof DBObject) return true;
var propToTest = ["_version"];
if ($DB_IMPLEM.toLowerCase() == "mongo") {
propToTest.concat(["_v", "_id"]);
}
return u.hasOwnProperties(obj, propToTest);
}
module.exports = DBObjectCreateTemplate;