From d3bef3b82f85dbf6628bb938b67052fae7e32c36 Mon Sep 17 00:00:00 2001 From: vamsee Date: Wed, 24 Oct 2018 15:57:36 +0530 Subject: [PATCH 01/25] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c85a167..7d3097a 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "gelf-stream": "^1.1.1", "helmet": "^2.1.1", "inflection": "^1.10.0", - "js-feel": "git+http://evgit/oecloud.io/feel.git#v1.4.2", + "js-feel": "git+http://evgit/oecloud.io/feel.git#v1.4.4", "locks": "^0.2.2", "lodash": "3.10.1", "loopback": "2.25.0", From 2c9d796e50084c8200d929e41abf5f279b794734 Mon Sep 17 00:00:00 2001 From: ajith Date: Thu, 25 Oct 2018 11:02:16 +0530 Subject: [PATCH 02/25] Added error for -m option --- lib/common/util.js | 28 - lib/db-migrate-helper.js | 487 ------------------ server/boot/001_db-ds-autoupdate.js | 14 +- server/server.js | 23 +- .../test1/app/app-list.json | 6 - .../db/0.8.10/default/ModelDefinition.json | 7 - .../test1/db/0.8.10/meta.json | 17 - .../db/0.9.0/default/ModelDefinition.json | 8 - .../test1/db/0.9.0/meta.json | 17 - test/database-migration/test1/package.json | 4 - .../test2/app/app-list.json | 14 - .../db/0.9.1/default/ModelDefinition.json | 15 - .../test2/db/0.9.1/default/migration.js | 11 - .../test2/db/0.9.1/meta.json | 21 - .../db/0.1.0/default/ModelDefinition.json | 7 - .../db/0.1.0/meta.json | 17 - .../oe-component-sample-test/package.json | 4 - test/database-migration/test2/package.json | 4 - test/db-migration-test.js | 209 -------- 19 files changed, 13 insertions(+), 900 deletions(-) delete mode 100644 lib/db-migrate-helper.js delete mode 100644 test/database-migration/test1/app/app-list.json delete mode 100644 test/database-migration/test1/db/0.8.10/default/ModelDefinition.json delete mode 100644 test/database-migration/test1/db/0.8.10/meta.json delete mode 100644 test/database-migration/test1/db/0.9.0/default/ModelDefinition.json delete mode 100644 test/database-migration/test1/db/0.9.0/meta.json delete mode 100644 test/database-migration/test1/package.json delete mode 100644 test/database-migration/test2/app/app-list.json delete mode 100644 test/database-migration/test2/db/0.9.1/default/ModelDefinition.json delete mode 100644 test/database-migration/test2/db/0.9.1/default/migration.js delete mode 100644 test/database-migration/test2/db/0.9.1/meta.json delete mode 100644 test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/default/ModelDefinition.json delete mode 100644 test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/meta.json delete mode 100644 test/database-migration/test2/node_modules/oe-component-sample-test/package.json delete mode 100644 test/database-migration/test2/package.json delete mode 100644 test/db-migration-test.js diff --git a/lib/common/util.js b/lib/common/util.js index 9a95c7d..d1f6bda 100644 --- a/lib/common/util.js +++ b/lib/common/util.js @@ -577,34 +577,6 @@ module.exports.createContextString = createContextString; module.exports.createDefaultContextString = createDefaultContextString; module.exports.createModelId = createModelId; -var getMode = (appinstance) => { - var mode = 'NONE'; - var enableMigration = appinstance.get('enableMigration'); - var migrationCheckMode; - if (enableMigration) { - migrationCheckMode = checkForMigrationSwitch('DOMIGRATE'); - mode = migrationCheckMode ? migrationCheckMode : 'WAIT'; - } else { - migrationCheckMode = checkForMigrationSwitch('AUTOUPDATE'); - mode = migrationCheckMode ? migrationCheckMode : mode; - } - return mode; -}; - -var checkForMigrationSwitch = (defaultMode) => { - var mode = null; - for (var i = 0; i < process.argv.length; i++) { - var val = process.argv[i]; - if (i > 1 && (val.toLowerCase() === '--migrate' || val.toLowerCase() === '-m')) { - mode = defaultMode; - break; - } - } - return mode; -}; - -module.exports.getMode = getMode; -module.exports.checkForMigrationSwitch = checkForMigrationSwitch; var getFileBasedModelSettings = (model) => { var modelDefinitionObject = JSON.parse(JSON.stringify(model.definition.settings)); diff --git a/lib/db-migrate-helper.js b/lib/db-migrate-helper.js deleted file mode 100644 index 9487cb6..0000000 --- a/lib/db-migrate-helper.js +++ /dev/null @@ -1,487 +0,0 @@ -/** - * - * �2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* eslint-disable no-console , no-process-exit*/ - -var fs = require('fs'); -var util = require('./common/util'); -var path = require('path'); -var async = require('async'); -var semver = require('semver'); -var process = require('process'); -var _ = require('lodash'); -var loopback = require('loopback'); -var log = require('oe-logger')('db-migrate-helper'); - -var appVersion; -var curAppName; -var curDbVer; -var curSysVerData; -var printMigrationCompleteMessage; -var runAsPartofTestCase; - -module.exports = function dbMigrationHelperFn(appinstance, option, isTestCases, cb) { - if (typeof isTestCases === 'function') { - // Checking 3rd parameter passed is callback function. - cb = isTestCases; - runAsPartofTestCase = isTestCases = false; - } else { - runAsPartofTestCase = isTestCases; - } - var mode = util.getMode(appinstance); - switch (mode) { - case 'NONE': - cb(); - break; - case 'AUTOUPDATE': - doAutoUpdate(appinstance, option, cb); - break; - case 'WAIT': - waitForMigrationToComplete(appinstance, option, cb); - break; - case 'DOMIGRATE': - doAutoUpdate(appinstance, option, function (updateErr) { - if (updateErr) { - log.error(log.defaultContext, 'doAutoUpdate Error : ', JSON.stringify(updateErr)); - cb(updateErr); - } else { - doMigrate(appinstance, option, function doMigrateFn(err, data) { - var status = 0; - resetGlobalVariables(); - if (err) { - console.log('Error in migration ', JSON.stringify(err)); - log.error(log.defaultContext, JSON.stringify(err)); - status = 1; - } else { - log.debug('Migration complete'); - console.log('Migration complete'); - } - if (!isTestCases) { - process.exit(status); - } else { - cb(err, data); - } - }); - } - }); - break; - default: - } -}; - -function resetGlobalVariables() { - appVersion = null; - curAppName = null; - curDbVer = null; - curSysVerData = null; - printMigrationCompleteMessage = null; - runAsPartofTestCase = null; -} - -// Autoupdate will be ran even if 'enableMigration' is not set in the application configuration. -// Autoupdate will do create new indexes for the models and sync file based model setting -// changes to ModelDefinition table. -// If we run multiple versions of app (Ex: 0.9.1, 0.9.2) which has different settings for -// a file based model (Ex: counter.json) which ever version of app that ran 'node . -m' last -// those changes will get reflected in the Database. -// It is not recommended to run 'node . -m' without 'enableMigration' set to true in application config -function doAutoUpdate(app, options, cb) { - log.debug(options, 'doAutoUpdate called.'); - var keys = Object.keys(app.models); - async.eachSeries(keys, function asyncForEachKey(key, callback) { - var model = app.models[key]; - if (key !== model.modelName) { - log.warn(options, 'key !== model.modelName, key="', key, '" modelName="', model.modelName, '"'); - return callback(); - } - if (util.isBaseChainPersistedModel(model, options)) { - util.checkAndDoAutoUpdate(model, options, callback); - } else { - log.warn(options, 'Base chain of model ', model.modelName, ' is not PersistedModel. No need to call ds.autoupdate'); - return callback(); - } - }, function asyncCb(err) { - if (err) { - log.error(options, 'async.eachSeries final cb, Error: ', err); - return cb(err); - } - updateFileBasedModels(cb); - }); -} - -function waitForMigrationToComplete(appinstance, option, cb) { - var appVer = buildAppVersion(appinstance, option); - var SystemConfig = loopback.findModel('SystemConfig'); - SystemConfig.find({ - where: { - key: 'version' - } - }, util.bootContext(), function systemConfigFindCbFn(err, data) { - var versionMisMatch = true; - if (err) { - cb(err); - } - if (data.length) { - var dbVer = data[0]; - versionMisMatch = appVerGreaterThanDBVer(appVer, dbVer.value); - } - if (versionMisMatch) { - printMigrationCompleteMessage = true; - log.debug(option, 'Waiting for db migration to complete, expecting "node . -m" to be running.'); - console.log('Waiting for db migration to complete, expecting "node . -m" to be running.'); - if (runAsPartofTestCase) { - cb(); - } else { - setTimeout(function setTimeOutFn() { - waitForMigrationToComplete(appinstance, option, cb); - }, 10000); - } - } else { - if (printMigrationCompleteMessage) { - log.debug(option, 'Migration complete, database is now migrated to latest version'); - } - return cb(); - } - }); -} - -function getListOfMigrations(app, option) { - var retList = []; - if (appVersion) { - Object.keys(appVersion).forEach(function appVersionKeysForEachFn(moduleName) { - var dbMigDir; - if (moduleName === curAppName) { - dbMigDir = path.join(app.locals.apphome, '..', 'db'); - } else { - dbMigDir = path.join(app.locals.apphome, '..', 'node_modules', moduleName, 'db'); - } - var dbVerOfModule = curDbVer && curDbVer[moduleName]; - var appVerOfModule = appVersion[moduleName]; - if (!fs.existsSync(dbMigDir)) { - return; - } - var allDirs = fs.readdirSync(dbMigDir).map(f => ({ - ver: f, - dir: path.join(dbMigDir, f) - })); - var migDirs = []; - allDirs.forEach(function allDirsForEachFn(f) { - try { - var ret = fs.statSync(f.dir).isDirectory(); - if (ret && dbVerOfModule && semver.lte(f.ver, dbVerOfModule)) { - ret = false; - } - ret = ret && semver.lte(f.ver, appVerOfModule); - if (ret) { - migDirs.push({ - version: f.ver, - dir: f.dir - }); - } - } catch (ex) { - // ignore error when directories with invalid semver version is there - } - }); - migDirs.sort(function migDirsSortFn(a, b) { - return semver.gt(a.version, b.version); - }); - if (migDirs.length) { - retList.push({ - name: moduleName, - migList: migDirs - }); - } - }); - } - return retList; -} - -function doMigrationForModuleVersion(migModule, appinstance, option, cb) { - log.debug(option, 'Running migrations for moduleVersion ', migModule.version); - var metaPath = path.join(migModule.dir, 'meta.json'); - if (fs.existsSync(metaPath)) { - var meta = require(metaPath); - async.forEachOfSeries(meta.files, function asyncForEachFn(value, key1, asyncCb) { - if (value.enabled === false) { - return asyncCb(); - } - var filePath = path.join(migModule.dir, value.file); - log.debug(option, 'Running migration for file ', filePath); - if (path.extname(filePath) === '.js') { - require(filePath)(appinstance, function requireFileCbFn(err) { - asyncCb(err); - }); - } else { - var options = { - ctx: {} - }; - options.ctx = meta.contexts[value.ctxId]; - if (!options.ctx) { - return asyncCb(Error('ctxId not found in contexts of ' + metaPath)); - } - var model = loopback.findModel(value.model); - var dataList = require(filePath); - if (Object.prototype.toString.call(dataList) === '[object Object]') { - dataList = [dataList]; - } - async.forEachOf(dataList, function dataListForEachFn(data, key2, asyncCb2) { - var localOptions = JSON.parse(JSON.stringify(options)); - var key = value.key || 'id'; - if (value.model === 'NodeRedFlow') { - key = 'name'; - } - if (value.model === 'Tenant') { - key = 'tenantId'; - } - - var filter = { - where: {} - }; - filter.where[key] = data[key]; - if (data[key]) { - model.find(filter, localOptions, function modelFindCbFn(err, dbData) { - if (err) { - return asyncCb2(err); - } - if (dbData.length) { - data.id = dbData[0].id; - if (dbData[0]._version) { - data._version = dbData[0]._version; - } - } - model.upsert(data, localOptions, function modelUpsertCbFn(err, data) { - asyncCb2(err); - }); - }); - } else { - model.upsert(data, localOptions, function modelUpsertCbFn(err, data) { - asyncCb2(err); - }); - } - }, function asyncForEachCallbackFn(err) { - asyncCb(err); - }); - } - }, function asyncForEachSeriesCallbackFn(err) { - cb(err); - }); - } else { - cb(Error('file not found ' + metaPath)); - } -} - -function doMigrationForModule(migModule, appinstance, option, cb) { - log.debug(option, 'Running migrations for ', migModule.name); - async.forEachOfSeries(migModule.migList, function asyncForEachOfSeriesFn(value, key, asyncCb) { - doMigrationForModuleVersion(value, appinstance, option, function doMigrationForModuleVersionFn(err) { - if (err) { - asyncCb(err); - } else { - var version = {}; - if (curSysVerData) { - version = curSysVerData.value; - } - version[migModule.name] = value.version; - updateVersion(version, asyncCb); - } - }); - }, function asyncForEachOfSeriesCbFn(err) { - cb(err); - }); -} - -function doMigrationForAllModules(appMigList, appinstance, option, cb) { - async.forEachOfSeries(appMigList, function asyncForEachOfSeriesFn(value, key, asyncCb) { - doMigrationForModule(value, appinstance, option, asyncCb); - }, function doMigrationForModuleCnFn(err) { - cb(err); - }); -} - -function updateVersion(newVersion, cb) { - var SystemConfig = loopback.findModel('SystemConfig'); - if (curSysVerData) { - curSysVerData.value = newVersion; - } else { - curSysVerData = { - key: 'version', - value: newVersion - }; - } - SystemConfig.upsert(curSysVerData, util.bootContext(), function systemConfigUpsertCbFn(err, data) { - curSysVerData = data; - cb(err, data); - }); -} - -function doMigrate(appinstance, option, cb) { - var appVer = buildAppVersion(appinstance, option); - var SystemConfig = loopback.findModel('SystemConfig'); - SystemConfig.find({ - where: { - key: 'version' - } - }, util.bootContext(), function systemConfigFindCbFn(err, data) { - var versionMisMatch = true; - if (err) { - cb(err); - } - if (data.length) { - curDbVer = data[0].value; - curSysVerData = data[0]; - versionMisMatch = appVerGreaterThanDBVer(appVer, curDbVer); - } - if (versionMisMatch) { - var dbMig = getListOfMigrations(appinstance, option); - if (dbMig.length) { - doMigrationForAllModules(dbMig, appinstance, option, function doMigrationForAllModulesFn(err, data) { - if (err) { - return cb(err); - } - updateVersion(appVer, cb); - }); - } else { - updateVersion(appVer, cb); - } - } else { - cb(); - } - }); -} - -function buildAppVersion(appinstance, option) { - if (appVersion) { - return appVersion; - } - var appListPath = path.join(appinstance.locals.apphome, 'app-list.json'); - var appList; - try { - appList = require(appListPath); - } catch (ex) { - log.debug(option, 'app-list.json not present, ignoring migration'); - return null; - } - appList.forEach(function appListForEachFn(appModule) { - if (!appModule.enabled) { - return; - } - appVersion = appVersion || {}; - var appVer = getVersion(appinstance, appModule.path, option); - if (!appVer) { - return null; - } - if (appModule.path === '.' || appModule.path === './') { - curAppName = appVer.name; - appVersion[appVer.name] = appVer.version; - } else { - appVersion[appModule.path] = appVer.version; - } - }); - return appVersion; -} - -// Reads package.json from the application and returns the version. -function getVersion(app, givenPath, option) { - var packageJsonPath; - if (givenPath === './' || givenPath === '.') { - packageJsonPath = path.join(app.locals.apphome, '..', 'package.json'); - } else { - packageJsonPath = path.join(app.locals.apphome, '..', 'node_modules', givenPath, 'package.json'); - } - try { - var myPackage = require(packageJsonPath); - return { - name: myPackage.name, - version: myPackage.version - }; - } catch (error) { - log.error(option, 'Given module "', givenPath, '" package.json doesn\'t exists.'); - log.error(option, 'Error: ', error); - return null; - } -} - -// Does semver comparison between application versions and versions in DB -function appVerGreaterThanDBVer(appVer, dbVer) { - var appVerGTDBVer = false; - appVerGTDBVer = Object.keys(appVer).some((key) => { - const appVerVal = appVer[key]; - const dbVerVal = dbVer[key]; - if (typeof dbVerVal === 'undefined') { - return true; - } else if (semver.gt(appVerVal, dbVerVal)) { - return true; - } - }); - return appVerGTDBVer; -} - -// Updates the ModelDefinition table data for file based models -// if there is any difference from file content to the table row data -function updateFileBasedModels(cb) { - var modelDefinitionModel = loopback.findModel('ModelDefinition'); - var filter = { - where: { - filebased: true - } - }; - var options = util.bootContext(); - options.noQueryCache = true; - modelDefinitionModel.find(filter, options, function (err, results) { - console.log('At updateFileBasedModels : modelDefinitionModel.find'); - if (err) return cb(err); - async.eachSeries(results, function asyncForEachModelDef(modelDef, callback) { - var modelName = modelDef.name; - var model = loopback.findModel(modelName); - if (model) { - var modelDefinitionObject = util.getFileBasedModelSettings(model); - modelDefinitionObject.name = modelDef.name; - // Check for if there is any difference between file model and DB data model instance. - var updatedData = {}; - // Currently the implementation takes care of only differences, if there is any setting removed - // It will not get updated in the DB. Current work around is set the corresponding setting - // value to corresponding empty value. Ex: Object -> {}, String -> '' - var fileModelKeys = Object.keys(modelDefinitionObject); - // Looping through all the settings keys from file based model - fileModelKeys.forEach(function fileModelKeysCb(field) { - // Checking existence of field value in DB record - if (typeof modelDef[field] !== 'undefined') { - // Comparing DB record with the file model setting - if (!_.isEqual(modelDef[field], modelDefinitionObject[field])) { - updatedData[field] = modelDefinitionObject[field]; - } - } else if (typeof modelDefinitionModel.definition.properties[field] !== 'undefined') { - // If a new setting introduced it should be one of ModelDefinition model properties - updatedData[field] = modelDefinitionObject[field]; - } - }); - // Checking if DB has to be updated if the updatedData is not empty - if (!_.isEmpty(updatedData)) { - modelDef.updateAttributes(updatedData, util.bootContext(), function modelDefUpdateSettings(err) { - if (err) { - log.error(util.bootContext(), 'Error while updating settings for model ', modelDef.name, '. Error: ', err); - return callback(err); - } - callback(); - }); - } else { - callback(); - } - } else { - modelDefinitionModel.deleteById(modelDef.id, modelDef._version, util.bootContext(), function (err, data) { - if (err) { - // ignore error in case of multiple migrations going in parallel, this is not critical step - return callback(); - } - callback(); - }); - } - }, function modelDefs(err) { - if (err) return cb(err); - cb(); - }); - }); -} diff --git a/server/boot/001_db-ds-autoupdate.js b/server/boot/001_db-ds-autoupdate.js index 4d5ae00..52361f0 100644 --- a/server/boot/001_db-ds-autoupdate.js +++ b/server/boot/001_db-ds-autoupdate.js @@ -14,17 +14,7 @@ var log = require('oe-logger')('boot-db-models'); var util = require('../../lib/common/util'); module.exports = function DbDsAutoupdate(app, cb) { - if (app.get('enableMigration')) { - if (util.checkForMigrationSwitch('ON') === 'ON') { - log.debug(util.bootContext(), 'DB Migration is enabled, but the migration switch also provided, setting the environment "ENABLE_DS_AUTOUPDATE" variable to true'); - process.env.ENABLE_DS_AUTOUPDATE = true; - } else { - log.debug(util.bootContext(), 'DB Migration is enabled, but the migration switch not provided, setting the environment "ENABLE_DS_AUTOUPDATE" variable to false'); - process.env.ENABLE_DS_AUTOUPDATE = false; - } - } else { - log.debug(util.bootContext(), 'DB Migration is disabled, setting the environment "ENABLE_DS_AUTOUPDATE" variable to true'); + log.debug(util.bootContext(), 'Setting the environment "ENABLE_DS_AUTOUPDATE" variable to true'); process.env.ENABLE_DS_AUTOUPDATE = true; - } - cb(); + cb(); }; diff --git a/server/server.js b/server/server.js index aa52a5b..494bdec 100644 --- a/server/server.js +++ b/server/server.js @@ -156,20 +156,19 @@ function finalBoot(appinstance, options, cb) { appinstance.server = server; module.exports.options = options; - function bootWithMigrate(appinstance, options, cb) { - boot(appinstance, options, function bootCbFn(err) { - if (err) { - return cb(err); - } - // db migrate and call back - var dbm = require('../lib/db-migrate-helper.js'); - dbm(appinstance, options, cb); - }); - } - appinstance.set('apiInfo', getApiInfo()); - bootWithMigrate(appinstance, options, function serverFinalBootCb(err) { + if(process.argv.indexOf('-m') > -1 || process.argv.indexOf('--migrate') > -1) { + console.log("\n\n===============================================================================\n"); + console.log(" Migration functionality using the \"-m\" switch is no longer part of oeCloud. "); + console.log(" Please use the app-list module \"oe-migration\" for the same."); + console.log(" Refer README.md of http://evgit/oecloud.io/oe-migration"); + console.log(" to know how to integrate oe-migration with your oeCloud-based application"); + console.log("\n===============================================================================\n\n"); + process.exit(1); + } + + boot(appinstance, options, function serverFinalBootCb(err) { if (err) { throw err; } diff --git a/test/database-migration/test1/app/app-list.json b/test/database-migration/test1/app/app-list.json deleted file mode 100644 index b368690..0000000 --- a/test/database-migration/test1/app/app-list.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "path": "./", - "enabled": true - } -] \ No newline at end of file diff --git a/test/database-migration/test1/db/0.8.10/default/ModelDefinition.json b/test/database-migration/test1/db/0.8.10/default/ModelDefinition.json deleted file mode 100644 index c4daa86..0000000 --- a/test/database-migration/test1/db/0.8.10/default/ModelDefinition.json +++ /dev/null @@ -1,7 +0,0 @@ -[{ - "name": "DBMigrationTestModel0", - "base": "BaseEntity", - "properties": { - "name": "string" - } -}] \ No newline at end of file diff --git a/test/database-migration/test1/db/0.8.10/meta.json b/test/database-migration/test1/db/0.8.10/meta.json deleted file mode 100644 index 50008f4..0000000 --- a/test/database-migration/test1/db/0.8.10/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "contexts": { - "default": { - "id": 0, - "tenantId": "default", - "remoteUser": "upload" - } - }, - "files": [ - { - "model": "ModelDefinition", - "enabled": true, - "file": "default/ModelDefinition.json", - "ctxId": "default" - } - ] -} \ No newline at end of file diff --git a/test/database-migration/test1/db/0.9.0/default/ModelDefinition.json b/test/database-migration/test1/db/0.9.0/default/ModelDefinition.json deleted file mode 100644 index 5bac6e2..0000000 --- a/test/database-migration/test1/db/0.9.0/default/ModelDefinition.json +++ /dev/null @@ -1,8 +0,0 @@ -[{ - "id": "12378", - "name": "DBMigrationTestModel1", - "base": "BaseEntity", - "properties": { - "name": "string" - } -}] \ No newline at end of file diff --git a/test/database-migration/test1/db/0.9.0/meta.json b/test/database-migration/test1/db/0.9.0/meta.json deleted file mode 100644 index 50008f4..0000000 --- a/test/database-migration/test1/db/0.9.0/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "contexts": { - "default": { - "id": 0, - "tenantId": "default", - "remoteUser": "upload" - } - }, - "files": [ - { - "model": "ModelDefinition", - "enabled": true, - "file": "default/ModelDefinition.json", - "ctxId": "default" - } - ] -} \ No newline at end of file diff --git a/test/database-migration/test1/package.json b/test/database-migration/test1/package.json deleted file mode 100644 index 6c22b77..0000000 --- a/test/database-migration/test1/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "oe-cloud", - "version": "0.9.0" -} \ No newline at end of file diff --git a/test/database-migration/test2/app/app-list.json b/test/database-migration/test2/app/app-list.json deleted file mode 100644 index 54664ce..0000000 --- a/test/database-migration/test2/app/app-list.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "path": "./", - "enabled": true - }, - { - "path": "oe-component-sample-test", - "enabled": true - }, - { - "path": "oe-component-non-existent", - "enabled": true - } -] \ No newline at end of file diff --git a/test/database-migration/test2/db/0.9.1/default/ModelDefinition.json b/test/database-migration/test2/db/0.9.1/default/ModelDefinition.json deleted file mode 100644 index 730f2bc..0000000 --- a/test/database-migration/test2/db/0.9.1/default/ModelDefinition.json +++ /dev/null @@ -1,15 +0,0 @@ -[{ - "id": "12378", - "name": "DBMigrationTestModel1", - "base": "BaseEntity", - "properties": { - "name": "string", - "type": "string" - } -},{ - "name": "DBMigrationTestModel2", - "base": "BaseEntity", - "properties": { - "name": "string" - } -}] \ No newline at end of file diff --git a/test/database-migration/test2/db/0.9.1/default/migration.js b/test/database-migration/test2/db/0.9.1/default/migration.js deleted file mode 100644 index 4bf507a..0000000 --- a/test/database-migration/test2/db/0.9.1/default/migration.js +++ /dev/null @@ -1,11 +0,0 @@ -/** -* -* 2016-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -* Bangalore, India. All Rights Reserved. -* -*/ - -module.exports = function migration(app, cb) { - console.log('Sample Migration .js script executed'); - cb(); -}; \ No newline at end of file diff --git a/test/database-migration/test2/db/0.9.1/meta.json b/test/database-migration/test2/db/0.9.1/meta.json deleted file mode 100644 index ccbc8c1..0000000 --- a/test/database-migration/test2/db/0.9.1/meta.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "contexts": { - "default": { - "id": 0, - "tenantId": "default", - "remoteUser": "upload" - } - }, - "files": [ - { - "model": "ModelDefinition", - "enabled": true, - "file": "default/ModelDefinition.json", - "ctxId": "default" - }, - { - "enabled": true, - "file": "default/migration.js" - } - ] -} \ No newline at end of file diff --git a/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/default/ModelDefinition.json b/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/default/ModelDefinition.json deleted file mode 100644 index f0e4ead..0000000 --- a/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/default/ModelDefinition.json +++ /dev/null @@ -1,7 +0,0 @@ -[{ - "name": "DBMigrationTestModel3", - "base": "BaseEntity", - "properties": { - "name": "string" - } -}] \ No newline at end of file diff --git a/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/meta.json b/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/meta.json deleted file mode 100644 index 50008f4..0000000 --- a/test/database-migration/test2/node_modules/oe-component-sample-test/db/0.1.0/meta.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "contexts": { - "default": { - "id": 0, - "tenantId": "default", - "remoteUser": "upload" - } - }, - "files": [ - { - "model": "ModelDefinition", - "enabled": true, - "file": "default/ModelDefinition.json", - "ctxId": "default" - } - ] -} \ No newline at end of file diff --git a/test/database-migration/test2/node_modules/oe-component-sample-test/package.json b/test/database-migration/test2/node_modules/oe-component-sample-test/package.json deleted file mode 100644 index 8bf0133..0000000 --- a/test/database-migration/test2/node_modules/oe-component-sample-test/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "oe-component-sample-test", - "version": "0.1.0" -} \ No newline at end of file diff --git a/test/database-migration/test2/package.json b/test/database-migration/test2/package.json deleted file mode 100644 index 15d5940..0000000 --- a/test/database-migration/test2/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "oe-cloud", - "version": "0.9.1" -} \ No newline at end of file diff --git a/test/db-migration-test.js b/test/db-migration-test.js deleted file mode 100644 index 6b8d7cc..0000000 --- a/test/db-migration-test.js +++ /dev/null @@ -1,209 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * Author: Pradeep Kumar Tippa - */ -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var path = require('path'); -var app = bootstrap.app; -var expect = bootstrap.chai.expect; -var dbm = require('../lib/db-migrate-helper'); -var modelDefModel = app.models.ModelDefinition; -describe(chalk.blue('Database Migration'), function() { - // To test this test case individually from mocha enable the below timeout - this.timeout(90000); - describe(chalk.green('Migration Switch'), function(){ - - before('Add -m switch to command args', function(done) { - process.argv.push('-m'); - done(); - }); - - after('Remove -m switch to command args', function(done) { - process.argv.pop(); - done(); - }); - - describe(chalk.yellow('Autoupdate'), function() { - - it('Run only Autoupdate', function(done) { - dbm(app, bootstrap.options, true, function(err, data){ - // The validation check for autoupdate is index(es) creation, schema change, or ModelDefinition table updates - expect(err).to.be.undefined; - expect(data).to.be.undefined; - done(); - }); - }); - - }); - - describe(chalk.yellow('With enableMigration'), function() { - - var enableMigrationSetting; - var appHome; - before('Set enableMigration, apphome', function(done) { - enableMigrationSetting = app.get('enableMigration'); - app.set('enableMigration', true); - appHome = app.locals.apphome; - app.locals.apphome = path.join(__dirname, 'database-migration', 'test1', 'app'); - done(); - }); - - after('Reset enableMigration, apphome', function(done) { - app.set('enableMigration', enableMigrationSetting || false); - app.locals.apphome = appHome; - done(); - }); - - it('Run Migration', function(done){ - // This will run autoupdate also again - dbm(app, bootstrap.options, true, function(err, data){ - expect(err).to.be.null; - expect(data).not.to.be.null; - expect(data).not.to.be.undefined; - expect(data.key).to.be.equal('version'); - expect(data._type).to.be.equal('SystemConfig'); - expect(data.value).not.to.be.null; - expect(data.value).not.to.be.undefined; - expect(data.value).to.deep.equal({'oe-cloud':'0.9.0'}); - var filter = { - where: { - or: [ - { - name: 'DBMigrationTestModel0' - }, - { - name: 'DBMigrationTestModel1' - } - ] - } - }; - modelDefModel.find(filter, {ctx: {tenantId: 'default'}}, function(modelDefErr, modelDefdata){ - expect(modelDefErr).to.be.null; - expect(modelDefdata).not.to.be.null; - expect(modelDefdata).not.to.be.undefined; - expect(modelDefdata).to.be.an('array'); - // Data should contain both DBMigrationTestModel0, DBMigrationTestModel1 - // ModelDefinition instances which are uploaded from version 0.8.10, 0.9.0 - expect(modelDefdata.length).to.be.equal(2); - done(); - }); - }); - }); - - it('Run Migration Again', function(done){ - // This will run autoupdate also again - dbm(app, bootstrap.options, true, function(err, data){ - expect(err).to.be.undefined; - // Data will also be empty since there is nothing to migrate - // Since migration completed in previous test case. - expect(data).to.be.undefined; - done(); - }); - }); - - describe(chalk.magenta('Without -m arg'), function() { - var appHome; - before('Remove -m Switch', function(done) { - appHome = app.locals.apphome; - app.locals.apphome = path.join(__dirname, 'database-migration', 'test2', 'app'); - process.argv.pop(); - done(); - }); - - after('Add -m Switch', function(done) { - process.argv.push('-m'); - app.locals.apphome = appHome; - done(); - }); - - it('Check for WAIT mode', function(done) { - dbm(app, bootstrap.options, true, function(err, data){ - expect(err).to.be.undefined; - // Data will also be empty since this is just to Cover the WAIT mode. - expect(data).to.be.undefined; - done(); - }); - }); - - }); - - }); - - describe(chalk.yellow('With enableMigration with next version'), function() { - - var enableMigrationSettingNxtVer; - var appHomeNxtVer; - before('Set enableMigration, apphome', function(done) { - enableMigrationSettingNxtVer = app.get('enableMigration'); - app.set('enableMigration', true); - appHomeNxtVer = app.locals.apphome; - app.locals.apphome = path.join(__dirname, 'database-migration', 'test2', 'app'); - done(); - }); - - after('Reset enableMigration, apphome', function(done) { - app.set('enableMigration', enableMigrationSettingNxtVer || false); - app.locals.apphome = appHomeNxtVer; - done(); - }); - - it('Run Migration with next version', function(done){ - // This will run autoupdate also again - dbm(app, bootstrap.options, true, function(err, data){ - expect(err).to.be.null; - expect(data).not.to.be.null; - expect(data).not.to.be.undefined; - expect(data.key).to.be.equal('version'); - expect(data._type).to.be.equal('SystemConfig'); - expect(data.value).not.to.be.null; - expect(data.value).not.to.be.undefined; - expect(data.value).to.deep.equal({'oe-cloud':'0.9.1', 'oe-component-sample-test': '0.1.0'}); - var filter = { - where: { - or: [ - { - name: 'DBMigrationTestModel1' - }, - { - name: 'DBMigrationTestModel2' - }, - { - name: 'DBMigrationTestModel3' - } - ] - } - }; - modelDefModel.find(filter, {ctx: {tenantId: 'default'}}, function(modelDefErr, modelDefdata){ - expect(modelDefErr).to.be.null; - expect(modelDefdata).not.to.be.null; - expect(modelDefdata).not.to.be.undefined; - expect(modelDefdata).to.be.an('array'); - // Data should contain all DBMigrationTestModel1, DBMigrationTestModel2, DBMigrationTestModel3 - // ModelDefinition instances which are uploaded from version 0.9.1 - // via test2/app and test2/node_modules/oe-component-sample-test - expect(modelDefdata.length).to.be.equal(3); - var modelDefDataJson = JSON.parse(JSON.stringify(modelDefdata)); - expect(modelDefDataJson).not.to.be.null; - expect(modelDefDataJson).not.to.be.undefined; - var updatedModel = modelDefDataJson.find(function(model){ - return model.name === 'DBMigrationTestModel1' - }); - expect(updatedModel).not.to.be.null; - expect(updatedModel).not.to.be.undefined; - expect(updatedModel.properties).to.deep.equal({'name':'string', 'type': 'string'});; - done(); - }); - }); - }); - - }); - - }); - -}); \ No newline at end of file From c09f68e531a4618742dbac2c15798cc98d2fdfaa Mon Sep 17 00:00:00 2001 From: ajith Date: Thu, 25 Oct 2018 12:24:17 +0530 Subject: [PATCH 03/25] Fixed eslint issues --- server/boot/001_db-ds-autoupdate.js | 6 +++--- server/server.js | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/server/boot/001_db-ds-autoupdate.js b/server/boot/001_db-ds-autoupdate.js index 52361f0..572cd85 100644 --- a/server/boot/001_db-ds-autoupdate.js +++ b/server/boot/001_db-ds-autoupdate.js @@ -14,7 +14,7 @@ var log = require('oe-logger')('boot-db-models'); var util = require('../../lib/common/util'); module.exports = function DbDsAutoupdate(app, cb) { - log.debug(util.bootContext(), 'Setting the environment "ENABLE_DS_AUTOUPDATE" variable to true'); - process.env.ENABLE_DS_AUTOUPDATE = true; - cb(); + log.debug(util.bootContext(), 'Setting the environment "ENABLE_DS_AUTOUPDATE" variable to true'); + process.env.ENABLE_DS_AUTOUPDATE = true; + cb(); }; diff --git a/server/server.js b/server/server.js index 494bdec..6344d07 100644 --- a/server/server.js +++ b/server/server.js @@ -158,14 +158,14 @@ function finalBoot(appinstance, options, cb) { appinstance.set('apiInfo', getApiInfo()); - if(process.argv.indexOf('-m') > -1 || process.argv.indexOf('--migrate') > -1) { - console.log("\n\n===============================================================================\n"); - console.log(" Migration functionality using the \"-m\" switch is no longer part of oeCloud. "); - console.log(" Please use the app-list module \"oe-migration\" for the same."); - console.log(" Refer README.md of http://evgit/oecloud.io/oe-migration"); - console.log(" to know how to integrate oe-migration with your oeCloud-based application"); - console.log("\n===============================================================================\n\n"); - process.exit(1); + if (process.argv.indexOf('-m') > -1 || process.argv.indexOf('--migrate') > -1) { + console.log('\n\n===============================================================================\n'); + console.log(' Migration functionality using the "-m" switch is no longer part of oeCloud. '); + console.log(' Please use the app-list module "oe-migration" for the same.'); + console.log(' Refer README.md of http://evgit/oecloud.io/oe-migration'); + console.log(' to know how to integrate oe-migration with your oeCloud-based application'); + console.log('\n===============================================================================\n\n'); + process.exit(1); } boot(appinstance, options, function serverFinalBootCb(err) { From 5cae9f86bffbdcaac4123d3457b35894fa13e9e6 Mon Sep 17 00:00:00 2001 From: ajith Date: Thu, 25 Oct 2018 12:48:59 +0530 Subject: [PATCH 04/25] Removed migration module ref --- test/z-z-batch-job-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/z-z-batch-job-test.js b/test/z-z-batch-job-test.js index 6bf5a9f..6a9d23b 100644 --- a/test/z-z-batch-job-test.js +++ b/test/z-z-batch-job-test.js @@ -15,7 +15,6 @@ var log = logger('batch-job-tests'); var api = bootstrap.api; var async = require('async'); var BatchJobRunner = require('../lib/batchJob-runner'); -var dbm = require('../lib/db-migrate-helper'); var app = bootstrap.app; var accessToken; From 152d457d4c7c33c9f8a3376f93834c00a5670d40 Mon Sep 17 00:00:00 2001 From: ajith Date: Wed, 31 Oct 2018 13:43:48 +0530 Subject: [PATCH 05/25] Disabled Actor Integration Test --- test/actor-tests/actorIntegrationTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/actor-tests/actorIntegrationTest.js b/test/actor-tests/actorIntegrationTest.js index 29be81a..09923dc 100644 --- a/test/actor-tests/actorIntegrationTest.js +++ b/test/actor-tests/actorIntegrationTest.js @@ -109,7 +109,7 @@ describe(chalk.blue('integrationTest'), function() { } -it('should log in', function(done) { +xit('should log in', function(done) { console.log('Base Url is ', baseurl); request.post( baseurl + "BaseUsers/login", { From 79a3b4f03922eb02bf5572d80e04a98e68a014fd Mon Sep 17 00:00:00 2001 From: M V Subhash Date: Mon, 19 Nov 2018 14:52:38 +0530 Subject: [PATCH 06/25] re-attaching switchVersion method inside switch-datasource-mixin, after datasource switching on model --- common/mixins/switch-datasource-mixin.js | 4 + common/mixins/version-mixin.js | 109 ++++++++++++----------- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/common/mixins/switch-datasource-mixin.js b/common/mixins/switch-datasource-mixin.js index 206d066..049eb61 100644 --- a/common/mixins/switch-datasource-mixin.js +++ b/common/mixins/switch-datasource-mixin.js @@ -14,6 +14,7 @@ var logger = require('oe-logger'); var log = logger('switch-datasource-mixin'); var appinstance = require('../../server/server.js').app; +var versionMixin = require('./version-mixin.js'); function getScopeMatchedDS(model, list, scope) { var matchedds; @@ -225,6 +226,9 @@ module.exports = function SwitchDatasourceMixin(model) { if (ds) { // console.log('switch datasource ', modelName, ds.settings.name); model.attachTo(ds); + // re-attaching switchVersion method after datasource switching on any model. so that _version will be created properly + // because switchVersion method will be overridden by the dao switchVersion method which simply return cb() when datasource switching happen on any model. + model.switchVersion = versionMixin.switchVersion; return ds; } } diff --git a/common/mixins/version-mixin.js b/common/mixins/version-mixin.js index 7c2966b..e14ec38 100644 --- a/common/mixins/version-mixin.js +++ b/common/mixins/version-mixin.js @@ -20,6 +20,59 @@ var uuidv4 = require('uuid/v4'); +function versionMixinBeforeSave(ctx, next) { + // if (Model.modelName !== ctx.Model.modelName) { + // return next(); + // } + var data = ctx.data || ctx.instance; + var error; + if (ctx.isNewInstance) { + data._version = data._newVersion || data._version || uuidv4(); + delete data._oldVersion; + delete data._newVersion; + } else if (ctx.currentInstance) { + if (ctx.currentInstance.__remoteInvoked) { + if (!data._version) { + error = new Error(); + error.name = 'Data Error'; + error.message = 'current version must be specified in _version field'; + error.code = 'DATA_ERROR_071'; + error.type = 'DataModifiedError'; + error.retriable = false; + error.status = 422; + return next(error); + } + } + var version = data._version || ctx.currentInstance._version; + if (data._newVersion && data._newVersion === version) { + error = new Error(); + error.name = 'Data Error'; + error.message = 'current version and new version must be different'; + error.code = 'DATA_ERROR_071'; + error.type = 'DataModifiedError'; + error.retriable = false; + error.status = 422; + return next(error); + } + if (version.toString() !== ctx.currentInstance._version.toString()) { + error = new Error(); + error.name = 'Data Error'; + error.message = 'No record with version specified'; + error.code = 'DATA_ERROR_071'; + error.type = 'DataModifiedError'; + error.retriable = false; + error.status = 422; + return next(error); + } + data._oldVersion = version; + data._version = data._newVersion || uuidv4(); + delete data._newVersion; + } + // TODO replaceById will have ctx.instance, and not + // ctx.currentinstance, need to analyze that + next(); +}; + module.exports = function VersionMixin(Model) { if (Model.modelName === 'BaseEntity') { return; @@ -56,59 +109,7 @@ module.exports = function VersionMixin(Model) { next(); }); - Model.switchVersion = function versionMixinBeforeSave(ctx, next) { - // if (Model.modelName !== ctx.Model.modelName) { - // return next(); - // } - var data = ctx.data || ctx.instance; - var error; - if (ctx.isNewInstance) { - data._version = data._newVersion || data._version || uuidv4(); - delete data._oldVersion; - delete data._newVersion; - } else if (ctx.currentInstance) { - if (ctx.currentInstance.__remoteInvoked) { - if (!data._version) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'current version must be specified in _version field'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - } - var version = data._version || ctx.currentInstance._version; - if (data._newVersion && data._newVersion === version) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'current version and new version must be different'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - if (version.toString() !== ctx.currentInstance._version.toString()) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'No record with version specified'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - data._oldVersion = version; - data._version = data._newVersion || uuidv4(); - delete data._newVersion; - } - // TODO replaceById will have ctx.instance, and not - // ctx.currentinstance, need to analyze that - next(); - }; - + Model.switchVersion = versionMixinBeforeSave; // lock current _version Model.evObserve('persist', function versionMixinPersistsFn(ctx, next) { delete ctx.data._newVersion; @@ -143,3 +144,5 @@ module.exports = function VersionMixin(Model) { } }); }; + +module.exports.switchVersion = versionMixinBeforeSave; \ No newline at end of file From fb7070f48b2118404d9a57f237a54d11fc9125f6 Mon Sep 17 00:00:00 2001 From: M V Subhash Date: Mon, 19 Nov 2018 14:59:36 +0530 Subject: [PATCH 07/25] eslint fixes --- common/mixins/version-mixin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/mixins/version-mixin.js b/common/mixins/version-mixin.js index e14ec38..4edeea5 100644 --- a/common/mixins/version-mixin.js +++ b/common/mixins/version-mixin.js @@ -71,7 +71,7 @@ function versionMixinBeforeSave(ctx, next) { // TODO replaceById will have ctx.instance, and not // ctx.currentinstance, need to analyze that next(); -}; +} module.exports = function VersionMixin(Model) { if (Model.modelName === 'BaseEntity') { @@ -145,4 +145,4 @@ module.exports = function VersionMixin(Model) { }); }; -module.exports.switchVersion = versionMixinBeforeSave; \ No newline at end of file +module.exports.switchVersion = versionMixinBeforeSave; From 140f8c664e94f2922538ebb6286041ff07ddc02b Mon Sep 17 00:00:00 2001 From: M V Subhash Date: Tue, 20 Nov 2018 10:45:36 +0530 Subject: [PATCH 08/25] added condition for mixin enable before reattaching the switchVersion --- common/mixins/switch-datasource-mixin.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/mixins/switch-datasource-mixin.js b/common/mixins/switch-datasource-mixin.js index 049eb61..4c8657d 100644 --- a/common/mixins/switch-datasource-mixin.js +++ b/common/mixins/switch-datasource-mixin.js @@ -228,7 +228,10 @@ module.exports = function SwitchDatasourceMixin(model) { model.attachTo(ds); // re-attaching switchVersion method after datasource switching on any model. so that _version will be created properly // because switchVersion method will be overridden by the dao switchVersion method which simply return cb() when datasource switching happen on any model. - model.switchVersion = versionMixin.switchVersion; + // check if version mixin enabled on model too. + if (model.settings.mixins.VersionMixin) { + model.switchVersion = versionMixin.switchVersion; + } return ds; } } From 63a8be1629c6efe741f3fabd5afb7dd31b265f1e Mon Sep 17 00:00:00 2001 From: ajith Date: Thu, 13 Dec 2018 09:43:23 +0530 Subject: [PATCH 09/25] Added properties to BatchStatus and BatchRun models --- common/models/framework/batch-run.json | 37 +++++++++++++++-------- common/models/framework/batch-status.json | 33 ++++++++++++-------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/common/models/framework/batch-run.json b/common/models/framework/batch-run.json index f881e80..b74702c 100644 --- a/common/models/framework/batch-run.json +++ b/common/models/framework/batch-run.json @@ -1,13 +1,26 @@ { - "name": "BatchRun", - "plural": "BatchRuns", - "base": "BaseEntity", - "strict": false, - "idInjection": true, - "properties": {}, - "validations": [], - "relations": { - }, - "acls": [], - "methods": {} -} + "name": "BatchRun", + "plural": "BatchRuns", + "base": "BaseEntity", + "strict": true, + "idInjection": true, + "properties": { + "startTimeMillis": "number", + "endTimeMillis": "number", + "durationMillis": "number", + "totalRecordCount": "number", + "successCount": "number", + "failureCount": "number", + "startTime": "date", + "endTime": "date", + "filePath": "string", + "options": "object", + "error": "object" + }, + "validations": [], + "relations": { + }, + "acls": [], + "methods": {} + } + \ No newline at end of file diff --git a/common/models/framework/batch-status.json b/common/models/framework/batch-status.json index 41a7c01..c9ba44f 100644 --- a/common/models/framework/batch-status.json +++ b/common/models/framework/batch-status.json @@ -1,13 +1,22 @@ { - "name": "BatchStatus", - "plural": "BatchStatus", - "base": "BaseEntity", - "strict": false, - "idInjection": true, - "properties": {}, - "validations": [], - "relations": { - }, - "acls": [], - "methods": {} -} + "name": "BatchStatus", + "plural": "BatchStatus", + "base": "BaseEntity", + "strict": true, + "idInjection": true, + "properties": { + "requestOpts": "object", + "response": "object", + "fileRecordData": "object", + "payload": "object", + "error": "object", + "statusText": "string", + "statusCode": "number" + }, + "validations": [], + "relations": { + }, + "acls": [], + "methods": {} + } + \ No newline at end of file From fca4eab97a9ca11add0efab02e696338e4cba116 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Thu, 10 Jan 2019 10:00:50 +0530 Subject: [PATCH 10/25] Update base-user.js --- common/models/framework/base-user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/framework/base-user.js b/common/models/framework/base-user.js index 1e08fb5..30fe6be 100644 --- a/common/models/framework/base-user.js +++ b/common/models/framework/base-user.js @@ -325,7 +325,7 @@ module.exports = function BaseUser(BaseUser) { // User can switch tenantId for a session, // tenantId stores current tenantId // userTenantid stores users tenantId - callback(null, rolesArr ? rolesArr : []); + callback(null, rolesArr && rolesArr.length>0 ? rolesArr : options.ctx.roles); }); }); }, From 199f46ea3b6fcb1d8f95fce0184b4e5c372a0fe2 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Thu, 10 Jan 2019 10:02:58 +0530 Subject: [PATCH 11/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index e86c439..a328ecf 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -33,6 +33,7 @@ var async = require('async'); var loopbackAccessContext = require('loopback/lib/access-context'); var AccessContext = loopbackAccessContext.AccessContext; var errorUtil = require('../../../lib/common/error-utils').getValidationError; +var RoleMapping = loopback.RoleMapping; // Gets specified `value` on `target` going levels down if required. function getValue(target, field) { @@ -140,7 +141,13 @@ module.exports = function DataACLFn(DataACL) { accessType: accessTypeQuery, remotingContext: ctx }); - + // Checking the AccessContext has accessToken with roles attached + if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { + // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE + ctx.req.accessToken.roles.forEach((role) => { + context.addPrincipal(RoleMapping.ROLE, role); + }); + } var errorCode; dataacls.forEach(function dataaclsForEach(dataacl) { dataacl.filter = dataacl.filter || {}; From d4b976ebcd74a5b1aa9b9cde9b98b16b8af41c32 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Thu, 10 Jan 2019 11:31:25 +0530 Subject: [PATCH 12/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index a328ecf..31d6983 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -224,8 +224,9 @@ module.exports = function DataACLFn(DataACL) { log.debug(callContext, 'filter in dataacl ', filter); var failed = []; + ctx.args.filter = filter; if (accessType === 'READ') { - ctx.args.filter = filter; + //ctx.args.filter = filter; return callback(); } var coll = []; From 7f0a87e562cc825c7cb2c302781b9b1f0bd15b18 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Fri, 11 Jan 2019 11:19:28 +0530 Subject: [PATCH 13/25] Update base-user.js --- common/models/framework/base-user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/framework/base-user.js b/common/models/framework/base-user.js index 30fe6be..abb5c49 100644 --- a/common/models/framework/base-user.js +++ b/common/models/framework/base-user.js @@ -325,7 +325,7 @@ module.exports = function BaseUser(BaseUser) { // User can switch tenantId for a session, // tenantId stores current tenantId // userTenantid stores users tenantId - callback(null, rolesArr && rolesArr.length>0 ? rolesArr : options.ctx.roles); + callback(null, (rolesArr && rolesArr.length>0) ? rolesArr : options.ctx.roles); }); }); }, From 33647ae666ab09bfa7ab1f4480d2724c3d9fb972 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:04:34 +0530 Subject: [PATCH 14/25] Update base-user.js --- common/models/framework/base-user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/framework/base-user.js b/common/models/framework/base-user.js index abb5c49..78156fe 100644 --- a/common/models/framework/base-user.js +++ b/common/models/framework/base-user.js @@ -325,7 +325,7 @@ module.exports = function BaseUser(BaseUser) { // User can switch tenantId for a session, // tenantId stores current tenantId // userTenantid stores users tenantId - callback(null, (rolesArr && rolesArr.length>0) ? rolesArr : options.ctx.roles); + callback(null, (rolesArr && rolesArr.length > 0) ? rolesArr : options.ctx.roles); }); }); }, From 13c72d68e07bea76464533759f91ad58d48fd856 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:13:14 +0530 Subject: [PATCH 15/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index 31d6983..5dfa599 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -142,12 +142,12 @@ module.exports = function DataACLFn(DataACL) { remotingContext: ctx }); // Checking the AccessContext has accessToken with roles attached - if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { - // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE - ctx.req.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); - }); - } + if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { + // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE + ctx.req.accessToken.roles.forEach((role) => { + context.addPrincipal(RoleMapping.ROLE, role); + }); + } var errorCode; dataacls.forEach(function dataaclsForEach(dataacl) { dataacl.filter = dataacl.filter || {}; From 20c3a69a5ebcc26731cbe58854a7306a2ea98019 Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:24:07 +0530 Subject: [PATCH 16/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index 5dfa599..b02a63d 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -141,13 +141,14 @@ module.exports = function DataACLFn(DataACL) { accessType: accessTypeQuery, remotingContext: ctx }); - // Checking the AccessContext has accessToken with roles attached - if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { - // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE - ctx.req.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); - }); - } + + // Checking the AccessContext has accessToken with roles attached + if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { + // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE + ctx.req.accessToken.roles.forEach((role) => { + context.addPrincipal(RoleMapping.ROLE, role); + }); + } var errorCode; dataacls.forEach(function dataaclsForEach(dataacl) { dataacl.filter = dataacl.filter || {}; From 6957f95295d792971b351a9489845305ea0c9a9f Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:29:29 +0530 Subject: [PATCH 17/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index b02a63d..859a0fe 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -146,7 +146,7 @@ module.exports = function DataACLFn(DataACL) { if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE ctx.req.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); + context.addPrincipal(RoleMapping.ROLE, role); }); } var errorCode; From 18c4c2da609f21e6e16243d3db52837d031e8efe Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:32:55 +0530 Subject: [PATCH 18/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index 859a0fe..22e4fcd 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -146,7 +146,7 @@ module.exports = function DataACLFn(DataACL) { if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE ctx.req.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); + context.addPrincipal(RoleMapping.ROLE, role); }); } var errorCode; @@ -227,7 +227,7 @@ module.exports = function DataACLFn(DataACL) { var failed = []; ctx.args.filter = filter; if (accessType === 'READ') { - //ctx.args.filter = filter; + // ctx.args.filter = filter; return callback(); } var coll = []; From 1b6b78e831764de7bbbcc6f93a5be327465efd8e Mon Sep 17 00:00:00 2001 From: vishnu choudhary Date: Mon, 21 Jan 2019 12:43:26 +0530 Subject: [PATCH 19/25] Update data-ACL.js --- common/models/framework/data-ACL.js | 1 - 1 file changed, 1 deletion(-) diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js index 22e4fcd..0cc4f4f 100644 --- a/common/models/framework/data-ACL.js +++ b/common/models/framework/data-ACL.js @@ -141,7 +141,6 @@ module.exports = function DataACLFn(DataACL) { accessType: accessTypeQuery, remotingContext: ctx }); - // Checking the AccessContext has accessToken with roles attached if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE From 28d41d41b913fac64a8e4efa41c41c5262dd85b3 Mon Sep 17 00:00:00 2001 From: ujwala Date: Mon, 18 Feb 2019 16:14:25 +0530 Subject: [PATCH 20/25] Moving to oec-next --- .dockerignore | 0 .eslintignore | 4 +- .gitattributes | 2 - .gitignore | 1 + .gitlab-ci.yml | 319 +- CONTRIBUTION.md | 28 - CORPORATE_CLA.md | 56 - Coding_Guidelines.md | 92 - Dockerfile | 15 - Dockerfile_Oracle | 21 - Gruntfile.js | 113 +- INDIVIDUAL_CLA.md | 39 - LICENSE | 2 +- README.md | 579 ++- STYLEGUIDE.md | 1671 -------- client/explorer/css/oeCloud.css | 29 - client/explorer/favicon.ico | Bin 15086 -> 0 bytes client/explorer/images/logo_small.png | Bin 6528 -> 0 bytes client/explorer/index.html | 112 - client/favicon.ico | Bin 15086 -> 0 bytes client/index.html | 77 - common/mixins/audit-fields-mixin.js | 132 - common/mixins/auto-fields-mixin.js | 76 - common/mixins/business-rule-mixin.js | 58 - common/mixins/cache-mixin.js | 76 - common/mixins/crypto-mixin.js | 160 - common/mixins/data-hierarchy-mixin.js | 397 -- common/mixins/data-personalization-mixin.js | 834 ---- .../mixins/expression-ast-populator-mixin.js | 72 - common/mixins/failsafe-observer-mixin.js | 289 -- common/mixins/history-mixin.js | 295 -- common/mixins/idempotent-mixin.js | 137 - common/mixins/model-validations.js | 550 --- common/mixins/observer-mixin.js | 69 +- common/mixins/property-expression-mixin.js | 145 - common/mixins/read-only-mixin.js | 30 - common/mixins/rest-api-actors-mixin.js | 95 - common/mixins/retry-support-mixin.js | 70 - common/mixins/soft-delete-mixin.js | 71 - common/mixins/switch-datasource-mixin.js | 302 -- common/mixins/version-mixin.js | 148 - common/models/base-entity.js | 20 + common/models/base-entity.json | 29 + common/models/data-source-definition.js | 35 + common/models/data-source-definition.json | 65 + common/models/{framework => }/enum-base.js | 0 common/models/{framework => }/enum-base.json | 0 common/models/error.json | 29 + common/models/framework/actor-activity.json | 42 - common/models/framework/admin.js | 44 - common/models/framework/admin.json | 14 - common/models/framework/app-config.json | 27 - common/models/framework/auth-session.js | 278 -- common/models/framework/auth-session.json | 55 - common/models/framework/base-ACL.js | 221 -- common/models/framework/base-ACL.json | 34 - common/models/framework/base-actor-entity.js | 803 ---- .../models/framework/base-actor-entity.json | 30 - common/models/framework/base-entity.js | 131 - common/models/framework/base-entity.json | 53 - .../models/framework/base-journal-entity.js | 286 -- .../models/framework/base-journal-entity.json | 47 - common/models/framework/base-otp.js | 505 --- common/models/framework/base-otp.json | 47 - .../models/framework/base-role-mapping.json | 42 - common/models/framework/base-role.js | 225 -- common/models/framework/base-role.json | 38 - common/models/framework/base-type.json | 9 - common/models/framework/base-user-identity.js | 104 - .../models/framework/base-user-identity.json | 13 - common/models/framework/base-user.js | 737 ---- common/models/framework/base-user.json | 118 - common/models/framework/batch-run.json | 26 - common/models/framework/batch-status.json | 22 - common/models/framework/business-rule.js | 36 - common/models/framework/business-rule.json | 43 - common/models/framework/cache-manager.js | 390 -- common/models/framework/cache-manager.json | 16 - common/models/framework/change-request.js | 266 -- common/models/framework/change-request.json | 35 - common/models/framework/data-ACL.js | 285 -- common/models/framework/data-ACL.json | 60 - .../framework/data-source-definition.js | 57 - .../framework/data-source-definition.json | 59 - .../models/framework/data-source-mapping.js | 55 - .../models/framework/data-source-mapping.json | 27 - common/models/framework/db-transaction.js | 144 - common/models/framework/db-transaction.json | 52 - common/models/framework/decision-graph.js | 143 - common/models/framework/decision-graph.json | 54 - common/models/framework/decision-service.js | 131 - common/models/framework/decision-service.json | 35 - common/models/framework/decision-table.js | 349 -- common/models/framework/decision-table.json | 39 - common/models/framework/dev.js | 201 - common/models/framework/dev.json | 33 - common/models/framework/document-data.json | 26 - common/models/framework/document.js | 35 - common/models/framework/document.json | 28 - common/models/framework/error.js | 19 - common/models/framework/error.json | 34 - common/models/framework/event-history.json | 31 - .../models/framework/failed-observer-log.json | 37 - .../models/framework/ldap-role-mapping.json | 19 - common/models/framework/lock.json | 26 - common/models/framework/logger-config.js | 120 - common/models/framework/logger-config.json | 15 - common/models/framework/model-definition.js | 591 --- common/models/framework/model-rule.json | 38 - common/models/framework/pending-journal.js | 72 - .../framework/personalization-rule.json | 39 - common/models/framework/save-point.json | 22 - common/models/framework/state.js | 10 - common/models/framework/state.json | 43 - common/models/framework/system-config.json | 15 - common/models/framework/tenant.json | 27 - common/models/framework/trusted-app.js | 127 - common/models/framework/trusted-app.json | 59 - common/models/framework/user-credential.json | 18 - common/models/framework/user-identity.json | 18 - common/models/framework/user-profile.json | 31 - common/models/model-definition.js | 44 + .../{framework => }/model-definition.json | 119 +- .../models/{framework => }/ref-code-base.js | 0 .../models/{framework => }/ref-code-base.json | 0 common/models/ui/designer-element.json | 39 - common/models/ui/draft-data.json | 29 - common/models/ui/field.js | 36 - common/models/ui/field.json | 82 - common/models/ui/gridColumnConfig.js | 82 - common/models/ui/gridColumnConfig.json | 62 - common/models/ui/gridColumnDefinition.js | 26 - common/models/ui/gridColumnDefinition.json | 23 - common/models/ui/gridConfig.js | 135 - common/models/ui/gridConfig.json | 37 - common/models/ui/gridMetaData.js | 191 - common/models/ui/gridMetaData.json | 41 - common/models/ui/literal.js | 129 - common/models/ui/literal.json | 29 - common/models/ui/modelView.js | 18 - common/models/ui/modelView.json | 33 - common/models/ui/navigationLink.js | 24 - common/models/ui/navigationLink.json | 60 - common/models/ui/type-mapping.json | 29 - common/models/ui/ui-attribute.json | 24 - common/models/ui/ui-base.js | 13 - common/models/ui/ui-base.json | 20 - common/models/ui/ui-component.js | 734 ---- common/models/ui/ui-component.json | 113 - common/models/ui/ui-element.json | 71 - common/models/ui/ui-manager.js | 148 - common/models/ui/ui-manager.json | 16 - common/models/ui/uimetadata.js | 571 --- common/models/ui/uimetadata.json | 72 - common/models/ui/uiresource.js | 84 - common/models/ui/uiresource.json | 38 - common/models/ui/uiroute.js | 71 - common/models/ui/uiroute.json | 66 - common/models/ui/validation.js | 19 - common/models/ui/validation.json | 29 - docker-compose.env.yml | 53 - docker-compose.test.env.yml | 28 - drop.js | 6 + index.js | 9 + lib/actor-pool.js | 189 - lib/auto-fields.js | 50 - lib/batchJob-runner.js | 102 - lib/client-sdk.template | 164 - lib/common/broadcaster-client.js | 261 -- lib/common/ev-global-messaging.js | 15 - lib/common/global-messaging.js | 17 - lib/common/property-expression-utils.js | 36 - lib/common/util.js | 879 +---- lib/common/validation-builder.js | 182 - lib/common/validation-utils.js | 1203 ------ lib/encryption.js | 81 - lib/{common => }/error-utils.js | 36 +- lib/ev-wait-lock.js | 24 - lib/event-history-manager.js | 364 -- .../expression-ast-parser.js | 1731 -------- .../expression-language.js | 46 - .../expression-syntax-parser.jison | 2027 ---------- .../expression-syntax-parser.js | 2103 ---------- lib/js-feel-plugins/index.js | 13 - lib/js-feel-plugins/js-feel-cache-plugin.js | 28 - .../js-feel-external-function-plugin.js | 19 - .../js-feel-relations-plugin.js | 119 - lib/jwt-token-util.js | 89 - lib/load.js | 268 ++ lib/loopback-boot-utility/index.js | 55 + .../coerce.js | 286 ++ .../dao-wrapper.js | 415 ++ .../index.js | 18 + .../model-builder-wrapper.js | 73 + .../relation-definition.js | 71 + lib/merge-util.js | 445 +-- lib/mixin-util.js | 111 + lib/model-personalizer.js | 26 - lib/passport.js | 101 - lib/preboot.js | 372 -- lib/proxy-context.js | 125 - lib/queue-consumer.js | 92 - lib/secrets-manager.js | 63 - lib/server-monitor.js | 93 - lib/service-personalizer.js | 917 ----- lib/tenant-util.js | 145 - lib/uws-client.js | 89 - merge.sh | 30 - oe-modularization.png | Bin 0 -> 60688 bytes package.json | 141 +- resources/triggers.json | 6 - seed/ErrorDetail.json | 433 +- server/app-list.json | 6 - server/boot/000_methodoverride.js | 112 - server/boot/001_db-ds-autoupdate.js | 20 - server/boot/01_error-response-load.js | 39 - server/boot/02_load-datasource-mappings.js | 48 - server/boot/03_db-models.js | 142 - server/boot/04_jsfeel-init.js | 44 - server/boot/04_model-rules.js | 258 -- server/boot/05_mark-as-cache-able.js | 56 - server/boot/06_trusted-ca-certs.js | 86 - server/boot/addStrictObjectIDCoercion.js | 13 + server/boot/authentication.js | 152 + server/boot/create-admin.js | 128 - server/boot/create-client-sdk.js | 20 - server/boot/data-acl.js | 128 - ...reate-datasources.js => db-dataSources.js} | 27 +- server/boot/db-models.js | 88 + server/boot/explorer.js | 47 - server/boot/health.js | 65 - server/boot/heapdump.js | 113 - server/boot/inject-proxy-call.js | 61 - server/boot/namespace.js | 17 - server/boot/oe-studio.js | 507 --- server/boot/redirect-for-uiroutes.js | 56 - server/boot/response-logger.js | 17 - server/boot/rest-api.js | 11 - server/boot/routes.js | 36 - server/boot/service-personalization.js | 345 -- server/boot/set-app-url.js | 15 - server/boot/uws-boot.js | 90 - server/boot/zzzz-update-logger-config.js | 153 - server/config.development.json | 91 - server/config.env.js | 13 - server/config.instance2.json | 55 - server/config.js | 57 - server/config.json | 129 - server/config.json.readme | 86 - server/config.nologin.json | 60 - server/config.test.json | 91 - server/datasources.docker.js | 46 - server/datasources.env.js | 12 - server/datasources.json | 39 - server/datasources.postgres.js | 50 - server/datasources.test.js | 18 - server/dropdb.js | 15 - server/middleware.json | 117 +- .../http-method-overriding-filter.js | 31 - server/middleware/model-discovery-filter.js | 34 - .../middleware/post-auth-context-populator.js | 60 - .../middleware/pre-auth-context-populator.js | 113 - server/middleware/recaptcha-validate.js | 64 - server/middleware/req-logging-filter.js | 22 - server/middleware/url-not-found.js | 15 - .../middleware/useragent-populator-filter.js | 26 - server/model-config.json | 345 +- server/providers.env.js | 12 - server/providers.json | 69 - server/server.js | 604 --- test/01-cleanup.js | 107 - test/actor-tests/actorIntegrationTest.js | 293 -- test/actor-tests/prepare-test.js | 18 - test/actor-tests/test-files/Dockerfile | 8 - .../common/models/test/inventory.js | 46 - .../common/models/test/inventory.json | 24 - .../models/test/inventoryTransaction.js | 11 - .../models/test/inventoryTransaction.json | 11 - test/actor-tests/test-files/qqq.yml | 37 - test/aggregation-fn-groupby-test.js | 964 ----- test/aggregation-fn-having-test.js | 725 ---- test/app-list.json | 15 + .../async-service-integration-test.js | 86 - test/async-service-tests/prepare-test.js | 18 - .../async-service-tests/test-files/Dockerfile | 7 - .../common/models/test/RetryTestModel.js | 17 - .../common/models/test/RetryTestModel.json | 27 - .../test-files/oecloud.yml | 59 - .../test-files/retry-service.yml | 57 - test/audit-field-mixin-test.js | 262 -- test/auto-fields-test.js | 105 - test/base-user-test.js | 468 --- test/basic-api-test.js | 285 -- test/basic-crud.js | 227 -- test/basic-test.js | 157 - test/bootstrap.js | 442 --- .../ApplicantRiskRating.xlsx | Bin 10557 -> 0 bytes test/business-rule-data/DecisionTable.json | 106 - test/business-rule-data/ElectricityBill.xlsx | Bin 10789 -> 0 bytes test/business-rule-data/HolidaysCount.xlsx | Bin 10628 -> 0 bytes test/business-rule-data/HolidaysMax.xlsx | Bin 10629 -> 0 bytes test/business-rule-data/HolidaysMin.xlsx | Bin 10629 -> 0 bytes .../LoanEligibilityCount.xlsx | Bin 9764 -> 0 bytes .../LoanEligibilityMax.xlsx | Bin 9763 -> 0 bytes .../LoanEligibilityMin.xlsx | Bin 9764 -> 0 bytes .../LoanEligibilitySum.xlsx | Bin 9753 -> 0 bytes test/business-rule-data/MembershipCount.xlsx | Bin 9750 -> 0 bytes test/business-rule-data/MembershipMax.xlsx | Bin 9750 -> 0 bytes test/business-rule-data/MembershipMin.xlsx | Bin 9750 -> 0 bytes test/business-rule-data/MembershipSum.xlsx | Bin 9740 -> 0 bytes test/business-rule-data/RoutingRules.xlsx | Bin 10853 -> 0 bytes .../RoutingRulesOutput.xlsx | Bin 10855 -> 0 bytes test/business-rule-hit-policy-test.js | 291 -- test/business-rule-mixin-test.js | 309 -- test/caching-test.js | 259 -- test/client-sdk-test.js | 39 - test/common/mixins/new-mixin.js | 3 + test/common/mixins/test-mixin.js | 2 + test/common/models/Customer.json | 24 + test/common/models/Spouse.json | 17 + test/common/models/base-entity-test.js | 4 + test/component-config.json | 5 + test/composite-model-test.js | 924 ----- test/concurrency-test.js | 206 - test/config-merge-test.js | 476 --- test/config.json | 24 + test/consistenthash/client.js | 325 -- test/consistenthash/haproxy.cfg | 22 - test/consistenthash/server.js | 217 -- test/cr-model-test.js | 203 - test/crypto-test.js | 202 - test/data-acl-test.js | 437 --- test/data-hierarchy-test.js | 1734 --------- test/data-personalization-test.js | 3468 ----------------- test/datasource-personalization.js | 267 -- test/datasources-pg.json1 | 37 + test/datasources.json | 24 + test/datasources.mongo.js | 31 + {server => test}/datasources.oracle.js | 27 +- test/datasources.postgres.js | 32 + test/datasources_alldb.json1 | 46 + test/decision-graph-tests.js | 90 - test/decision-service-tests.js | 153 - test/decision-table-test.js | 146 - test/delete-test.js | 403 -- test/embedded-many-test.js | 187 - test/enum-test.js | 138 - test/fail-test.js | 169 - test/failSafe-tests/cleanupIntegrationTest.js | 27 - test/failSafe-tests/failsafe.yml | 66 - test/failSafe-tests/integrationTest.js | 149 - test/failSafe-tests/prepareIntegrationTest.js | 37 - test/failsafe-observer-test.js | 550 --- test/file-upload-test.js | 75 - test/gridconfig-test.js | 189 - test/gridmetadata-test.js | 250 -- test/health-test.js | 39 - test/history-mixin-test.js | 234 -- test/idempotency-attribute-update-test.js | 152 - test/idempotency-test.js | 112 - test/idempotent-behavior-test.js | 1239 ------ test/idempotent-mixin-test.js | 1029 ----- test/inheritance-util-tests.js | 74 - test/instance-caching-test.js | 1193 ------ .../integration-test-model.js | 423 -- .../integration-test-user.js | 160 - test/job-scheduler-service-tests/oe-cloud.yml | 93 - .../prepare-test.js | 35 - .../scheduler-test.js | 253 -- test/jwt-for-access-token-test.js | 87 - .../ldap-authentication-test.js | 119 - test/ldap-auth-tests/oe-cloud.yml | 69 - test/literal-test.js | 158 - test/manual-scope-update.js | 155 - test/middleware.json | 37 + test/misclaneous-test.js | 66 - test/model-collection-test.js | 211 - test/model-config.json | 45 + test/model-definition-ACL-test.js | 426 -- test/model-definition-inheritance-test.js | 831 ---- test/model-definition-relation-test.js | 504 --- test/model-definition-test.js | 637 --- test/model-definition-test2.js | 269 -- test/model-definition-validation-test.js | 139 - test/model-feel-belongs-to-relation-test.js | 194 - ...odel-feel-belongs-to-self-relation-test.js | 162 - ...eel-decision-table-blank-object-payload.js | 278 -- test/model-feel-execution-logging.js | 103 - test/model-personalization-test.js | 580 --- test/model-personalization-variant-test.js | 108 - ...odel-rule-base-entity-inheritance-tests.js | 406 -- test/model-rule-data/PropertyPopulator.xlsx | Bin 9751 -> 0 bytes .../model-rule-data/PropertyPopulatorOne.xlsx | Bin 9756 -> 0 bytes test/model-rule-data/approve-feel.json | 1 - test/model-rule-data/approve-graph.json | 1 - test/model-rule-data/blank_object.xlsx | Bin 8825 -> 0 bytes test/model-rule-data/blank_object2.xlsx | Bin 8511 -> 0 bytes test/model-rule-data/blank_object3.xlsx | Bin 8771 -> 0 bytes test/model-rule-data/category-feel.json | 1 - test/model-rule-data/category-graph.json | 1 - test/model-rule-data/corrupt.xlsx | Bin 10828 -> 0 bytes test/model-rule-data/data/functions/index.js | 14 - test/model-rule-data/eligibilityUSA.xlsx | Bin 10297 -> 0 bytes test/model-rule-data/employee_validation.xlsx | Bin 10220 -> 0 bytes test/model-rule-data/fetch_relations.xlsx | Bin 8281 -> 0 bytes test/model-rule-data/peoples.json | 68 - test/model-rule-data/populator1.xlsx | Bin 8394 -> 0 bytes test/model-rule-data/validate_entry.xlsx | Bin 8420 -> 0 bytes test/model-rule-data/validation.xlsx | Bin 11311 -> 0 bytes ...-test-decision-service-invocation-tests.js | 257 -- test/model-rule-test.js | 546 --- test/model-transaction-test.js | 315 -- ...el-validation-composite-uniqueness-test.js | 135 - test/model-validation-custom-function-test.js | 124 - test/model-validation-embeddedModel-test.js | 307 -- ...del-validation-oeValidation-custom-test.js | 280 -- test/model-validation-oeValidation-test.js | 265 -- test/model-validation-refcode-test.js | 232 -- test/model-validation-relation-test.js | 405 -- test/model-validation-test.js | 452 --- test/model-validation-validateWhen.js | 435 --- test/model-validation-xmodelvalidate-test.js | 95 - test/model-variant-of-test.js | 276 -- test/multi-tenancy-test.js | 449 --- test/oe-studio-test.js | 380 -- {server => test}/oracle-utility.js | 113 +- test/otp-test.js | 232 -- test/pg-test/basic-crud-test.js | 120 - test/property-expressions-test.js | 271 -- test/providers.json | 69 - test/redirect-for-uiroute-test.js | 70 - test/relation-has-one-test.js | 150 - test/relation-references-many-test.js | 192 - test/relation-references-many2-test.js | 147 - test/retry-support-mixin-test.js | 156 - .../cluster-tests.js | 383 -- .../rule-engine-cluster-setup.yml | 66 - test/rule-engine-cluster-tests/wait-all.sh | 21 - test/rule-engine-cluster-tests/wait-for-up.sh | 38 - test/secrets-manager-test.js | 52 - test/secrets-test-data/TESTMONGOHOST | 1 - test/secrets-test-data/TESTMONGOUSER | 1 - test/select-for-update-test.js | 228 -- test/server.js | 29 + test/service-personalization-relation-test.js | 331 -- test/service-personalization-test.js | 1789 --------- test/soft-delete-mixin-test.js | 196 - test/switch-data-source-test.js | 439 --- test/test.js | 648 +++ test/ui-manager-test.js | 109 - test/uicomponent-test.js | 420 -- test/uimetadata-test.js | 1221 ------ test/uiresource-test.js | 122 - test/unauthorised-write.js | 44 - test/update-data-acl-test.js | 405 -- test/update-default-tenant-record.js | 150 - test/upload-file-data/x.png | Bin 2776 -> 0 bytes test/version-mixin-test.js | 346 -- test/z-jwt-assertion-test.js | 1020 ----- test/z-remove-demo-user-test.js | 25 - test/z-z-batch-job-test.js | 307 -- test/z-z-business-validations-tests.js | 709 ---- test/z-z-rest-api-actors-mixin-tests.js | 334 -- ...z-z-z-actor-pattern-activity-check-test.js | 208 - ...ctor-pattern-aqcuire-db-lock-test-mongo.js | 246 -- test/z-z-z-actor-pattern-db-lock-test.js | 1294 ------ test/z-z-z-actor-pattern-test.js | 1274 ------ test/z-z-z-actor-startup-test.js | 296 -- test/z-z-z-logger-config-test.js | 172 - test/z-z-z-z-mark-as-cache-able-test.js | 84 - tools/cleanPostgresDB.bat | 7 - tools/cleandb.bat | 6 - 472 files changed, 4337 insertions(+), 80814 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .gitattributes delete mode 100644 CONTRIBUTION.md delete mode 100644 CORPORATE_CLA.md delete mode 100644 Coding_Guidelines.md delete mode 100644 Dockerfile delete mode 100644 Dockerfile_Oracle delete mode 100644 INDIVIDUAL_CLA.md delete mode 100644 STYLEGUIDE.md delete mode 100644 client/explorer/css/oeCloud.css delete mode 100644 client/explorer/favicon.ico delete mode 100644 client/explorer/images/logo_small.png delete mode 100644 client/explorer/index.html delete mode 100644 client/favicon.ico delete mode 100644 client/index.html delete mode 100644 common/mixins/audit-fields-mixin.js delete mode 100644 common/mixins/auto-fields-mixin.js delete mode 100644 common/mixins/business-rule-mixin.js delete mode 100644 common/mixins/cache-mixin.js delete mode 100644 common/mixins/crypto-mixin.js delete mode 100644 common/mixins/data-hierarchy-mixin.js delete mode 100644 common/mixins/data-personalization-mixin.js delete mode 100644 common/mixins/expression-ast-populator-mixin.js delete mode 100644 common/mixins/failsafe-observer-mixin.js delete mode 100644 common/mixins/history-mixin.js delete mode 100644 common/mixins/idempotent-mixin.js delete mode 100644 common/mixins/model-validations.js delete mode 100644 common/mixins/property-expression-mixin.js delete mode 100644 common/mixins/read-only-mixin.js delete mode 100644 common/mixins/rest-api-actors-mixin.js delete mode 100644 common/mixins/retry-support-mixin.js delete mode 100644 common/mixins/soft-delete-mixin.js delete mode 100644 common/mixins/switch-datasource-mixin.js delete mode 100644 common/mixins/version-mixin.js create mode 100644 common/models/base-entity.js create mode 100644 common/models/base-entity.json create mode 100644 common/models/data-source-definition.js create mode 100644 common/models/data-source-definition.json rename common/models/{framework => }/enum-base.js (100%) rename common/models/{framework => }/enum-base.json (100%) create mode 100644 common/models/error.json delete mode 100644 common/models/framework/actor-activity.json delete mode 100644 common/models/framework/admin.js delete mode 100644 common/models/framework/admin.json delete mode 100644 common/models/framework/app-config.json delete mode 100644 common/models/framework/auth-session.js delete mode 100644 common/models/framework/auth-session.json delete mode 100644 common/models/framework/base-ACL.js delete mode 100644 common/models/framework/base-ACL.json delete mode 100644 common/models/framework/base-actor-entity.js delete mode 100644 common/models/framework/base-actor-entity.json delete mode 100644 common/models/framework/base-entity.js delete mode 100644 common/models/framework/base-entity.json delete mode 100644 common/models/framework/base-journal-entity.js delete mode 100644 common/models/framework/base-journal-entity.json delete mode 100644 common/models/framework/base-otp.js delete mode 100644 common/models/framework/base-otp.json delete mode 100644 common/models/framework/base-role-mapping.json delete mode 100644 common/models/framework/base-role.js delete mode 100644 common/models/framework/base-role.json delete mode 100644 common/models/framework/base-type.json delete mode 100644 common/models/framework/base-user-identity.js delete mode 100644 common/models/framework/base-user-identity.json delete mode 100644 common/models/framework/base-user.js delete mode 100644 common/models/framework/base-user.json delete mode 100644 common/models/framework/batch-run.json delete mode 100644 common/models/framework/batch-status.json delete mode 100644 common/models/framework/business-rule.js delete mode 100644 common/models/framework/business-rule.json delete mode 100644 common/models/framework/cache-manager.js delete mode 100644 common/models/framework/cache-manager.json delete mode 100644 common/models/framework/change-request.js delete mode 100644 common/models/framework/change-request.json delete mode 100644 common/models/framework/data-ACL.js delete mode 100644 common/models/framework/data-ACL.json delete mode 100644 common/models/framework/data-source-definition.js delete mode 100644 common/models/framework/data-source-definition.json delete mode 100644 common/models/framework/data-source-mapping.js delete mode 100644 common/models/framework/data-source-mapping.json delete mode 100644 common/models/framework/db-transaction.js delete mode 100644 common/models/framework/db-transaction.json delete mode 100644 common/models/framework/decision-graph.js delete mode 100644 common/models/framework/decision-graph.json delete mode 100644 common/models/framework/decision-service.js delete mode 100644 common/models/framework/decision-service.json delete mode 100644 common/models/framework/decision-table.js delete mode 100644 common/models/framework/decision-table.json delete mode 100644 common/models/framework/dev.js delete mode 100644 common/models/framework/dev.json delete mode 100644 common/models/framework/document-data.json delete mode 100644 common/models/framework/document.js delete mode 100644 common/models/framework/document.json delete mode 100644 common/models/framework/error.js delete mode 100644 common/models/framework/error.json delete mode 100644 common/models/framework/event-history.json delete mode 100644 common/models/framework/failed-observer-log.json delete mode 100644 common/models/framework/ldap-role-mapping.json delete mode 100644 common/models/framework/lock.json delete mode 100644 common/models/framework/logger-config.js delete mode 100644 common/models/framework/logger-config.json delete mode 100644 common/models/framework/model-definition.js delete mode 100644 common/models/framework/model-rule.json delete mode 100644 common/models/framework/pending-journal.js delete mode 100644 common/models/framework/personalization-rule.json delete mode 100644 common/models/framework/save-point.json delete mode 100644 common/models/framework/state.js delete mode 100644 common/models/framework/state.json delete mode 100644 common/models/framework/system-config.json delete mode 100644 common/models/framework/tenant.json delete mode 100644 common/models/framework/trusted-app.js delete mode 100644 common/models/framework/trusted-app.json delete mode 100644 common/models/framework/user-credential.json delete mode 100644 common/models/framework/user-identity.json delete mode 100644 common/models/framework/user-profile.json create mode 100644 common/models/model-definition.js rename common/models/{framework => }/model-definition.json (59%) rename common/models/{framework => }/ref-code-base.js (100%) rename common/models/{framework => }/ref-code-base.json (100%) delete mode 100644 common/models/ui/designer-element.json delete mode 100644 common/models/ui/draft-data.json delete mode 100644 common/models/ui/field.js delete mode 100644 common/models/ui/field.json delete mode 100644 common/models/ui/gridColumnConfig.js delete mode 100644 common/models/ui/gridColumnConfig.json delete mode 100644 common/models/ui/gridColumnDefinition.js delete mode 100644 common/models/ui/gridColumnDefinition.json delete mode 100644 common/models/ui/gridConfig.js delete mode 100644 common/models/ui/gridConfig.json delete mode 100644 common/models/ui/gridMetaData.js delete mode 100644 common/models/ui/gridMetaData.json delete mode 100644 common/models/ui/literal.js delete mode 100644 common/models/ui/literal.json delete mode 100644 common/models/ui/modelView.js delete mode 100644 common/models/ui/modelView.json delete mode 100644 common/models/ui/navigationLink.js delete mode 100644 common/models/ui/navigationLink.json delete mode 100644 common/models/ui/type-mapping.json delete mode 100644 common/models/ui/ui-attribute.json delete mode 100644 common/models/ui/ui-base.js delete mode 100644 common/models/ui/ui-base.json delete mode 100644 common/models/ui/ui-component.js delete mode 100644 common/models/ui/ui-component.json delete mode 100644 common/models/ui/ui-element.json delete mode 100644 common/models/ui/ui-manager.js delete mode 100644 common/models/ui/ui-manager.json delete mode 100644 common/models/ui/uimetadata.js delete mode 100644 common/models/ui/uimetadata.json delete mode 100644 common/models/ui/uiresource.js delete mode 100644 common/models/ui/uiresource.json delete mode 100644 common/models/ui/uiroute.js delete mode 100644 common/models/ui/uiroute.json delete mode 100644 common/models/ui/validation.js delete mode 100644 common/models/ui/validation.json delete mode 100644 docker-compose.env.yml delete mode 100644 docker-compose.test.env.yml create mode 100644 drop.js create mode 100644 index.js delete mode 100644 lib/actor-pool.js delete mode 100644 lib/auto-fields.js delete mode 100644 lib/batchJob-runner.js delete mode 100644 lib/client-sdk.template delete mode 100644 lib/common/broadcaster-client.js delete mode 100644 lib/common/ev-global-messaging.js delete mode 100644 lib/common/global-messaging.js delete mode 100644 lib/common/property-expression-utils.js delete mode 100644 lib/common/validation-builder.js delete mode 100644 lib/common/validation-utils.js delete mode 100644 lib/encryption.js rename lib/{common => }/error-utils.js (78%) delete mode 100644 lib/ev-wait-lock.js delete mode 100644 lib/event-history-manager.js delete mode 100644 lib/expression-language/expression-ast-parser.js delete mode 100644 lib/expression-language/expression-language.js delete mode 100644 lib/expression-language/expression-syntax-parser.jison delete mode 100644 lib/expression-language/expression-syntax-parser.js delete mode 100644 lib/js-feel-plugins/index.js delete mode 100644 lib/js-feel-plugins/js-feel-cache-plugin.js delete mode 100644 lib/js-feel-plugins/js-feel-external-function-plugin.js delete mode 100644 lib/js-feel-plugins/js-feel-relations-plugin.js delete mode 100644 lib/jwt-token-util.js create mode 100644 lib/load.js create mode 100644 lib/loopback-boot-utility/index.js create mode 100644 lib/loopback-datasource-juggler-wrapper/coerce.js create mode 100644 lib/loopback-datasource-juggler-wrapper/dao-wrapper.js create mode 100644 lib/loopback-datasource-juggler-wrapper/index.js create mode 100644 lib/loopback-datasource-juggler-wrapper/model-builder-wrapper.js create mode 100644 lib/loopback-datasource-juggler-wrapper/relation-definition.js create mode 100644 lib/mixin-util.js delete mode 100644 lib/model-personalizer.js delete mode 100644 lib/passport.js delete mode 100644 lib/preboot.js delete mode 100755 lib/proxy-context.js delete mode 100644 lib/queue-consumer.js delete mode 100644 lib/secrets-manager.js delete mode 100644 lib/server-monitor.js delete mode 100644 lib/service-personalizer.js delete mode 100644 lib/tenant-util.js delete mode 100755 lib/uws-client.js delete mode 100644 merge.sh create mode 100644 oe-modularization.png delete mode 100644 resources/triggers.json delete mode 100644 server/app-list.json delete mode 100644 server/boot/000_methodoverride.js delete mode 100644 server/boot/001_db-ds-autoupdate.js delete mode 100644 server/boot/01_error-response-load.js delete mode 100644 server/boot/02_load-datasource-mappings.js delete mode 100644 server/boot/03_db-models.js delete mode 100644 server/boot/04_jsfeel-init.js delete mode 100644 server/boot/04_model-rules.js delete mode 100644 server/boot/05_mark-as-cache-able.js delete mode 100644 server/boot/06_trusted-ca-certs.js create mode 100644 server/boot/addStrictObjectIDCoercion.js delete mode 100644 server/boot/create-admin.js delete mode 100644 server/boot/create-client-sdk.js delete mode 100644 server/boot/data-acl.js rename server/boot/{02_create-datasources.js => db-dataSources.js} (64%) create mode 100644 server/boot/db-models.js delete mode 100644 server/boot/explorer.js delete mode 100644 server/boot/health.js delete mode 100644 server/boot/heapdump.js delete mode 100644 server/boot/inject-proxy-call.js delete mode 100644 server/boot/namespace.js delete mode 100644 server/boot/oe-studio.js delete mode 100644 server/boot/redirect-for-uiroutes.js delete mode 100644 server/boot/response-logger.js delete mode 100644 server/boot/rest-api.js delete mode 100644 server/boot/routes.js delete mode 100644 server/boot/service-personalization.js delete mode 100644 server/boot/set-app-url.js delete mode 100755 server/boot/uws-boot.js delete mode 100644 server/boot/zzzz-update-logger-config.js delete mode 100644 server/config.development.json delete mode 100644 server/config.env.js delete mode 100644 server/config.instance2.json delete mode 100644 server/config.js delete mode 100644 server/config.json delete mode 100644 server/config.json.readme delete mode 100644 server/config.nologin.json delete mode 100644 server/config.test.json delete mode 100644 server/datasources.docker.js delete mode 100644 server/datasources.env.js delete mode 100644 server/datasources.json delete mode 100644 server/datasources.postgres.js delete mode 100644 server/datasources.test.js delete mode 100644 server/dropdb.js delete mode 100644 server/middleware/http-method-overriding-filter.js delete mode 100644 server/middleware/model-discovery-filter.js delete mode 100644 server/middleware/post-auth-context-populator.js delete mode 100644 server/middleware/pre-auth-context-populator.js delete mode 100644 server/middleware/recaptcha-validate.js delete mode 100644 server/middleware/req-logging-filter.js delete mode 100644 server/middleware/url-not-found.js delete mode 100644 server/middleware/useragent-populator-filter.js delete mode 100644 server/providers.env.js delete mode 100644 server/providers.json delete mode 100644 server/server.js delete mode 100644 test/01-cleanup.js delete mode 100644 test/actor-tests/actorIntegrationTest.js delete mode 100644 test/actor-tests/prepare-test.js delete mode 100644 test/actor-tests/test-files/Dockerfile delete mode 100644 test/actor-tests/test-files/common/models/test/inventory.js delete mode 100644 test/actor-tests/test-files/common/models/test/inventory.json delete mode 100644 test/actor-tests/test-files/common/models/test/inventoryTransaction.js delete mode 100644 test/actor-tests/test-files/common/models/test/inventoryTransaction.json delete mode 100644 test/actor-tests/test-files/qqq.yml delete mode 100644 test/aggregation-fn-groupby-test.js delete mode 100644 test/aggregation-fn-having-test.js create mode 100644 test/app-list.json delete mode 100644 test/async-service-tests/async-service-integration-test.js delete mode 100644 test/async-service-tests/prepare-test.js delete mode 100644 test/async-service-tests/test-files/Dockerfile delete mode 100644 test/async-service-tests/test-files/common/models/test/RetryTestModel.js delete mode 100644 test/async-service-tests/test-files/common/models/test/RetryTestModel.json delete mode 100644 test/async-service-tests/test-files/oecloud.yml delete mode 100644 test/async-service-tests/test-files/retry-service.yml delete mode 100644 test/audit-field-mixin-test.js delete mode 100644 test/auto-fields-test.js delete mode 100644 test/base-user-test.js delete mode 100644 test/basic-api-test.js delete mode 100644 test/basic-crud.js delete mode 100644 test/basic-test.js delete mode 100644 test/bootstrap.js delete mode 100644 test/business-rule-data/ApplicantRiskRating.xlsx delete mode 100644 test/business-rule-data/DecisionTable.json delete mode 100644 test/business-rule-data/ElectricityBill.xlsx delete mode 100644 test/business-rule-data/HolidaysCount.xlsx delete mode 100644 test/business-rule-data/HolidaysMax.xlsx delete mode 100644 test/business-rule-data/HolidaysMin.xlsx delete mode 100644 test/business-rule-data/LoanEligibilityCount.xlsx delete mode 100644 test/business-rule-data/LoanEligibilityMax.xlsx delete mode 100644 test/business-rule-data/LoanEligibilityMin.xlsx delete mode 100644 test/business-rule-data/LoanEligibilitySum.xlsx delete mode 100644 test/business-rule-data/MembershipCount.xlsx delete mode 100644 test/business-rule-data/MembershipMax.xlsx delete mode 100644 test/business-rule-data/MembershipMin.xlsx delete mode 100644 test/business-rule-data/MembershipSum.xlsx delete mode 100644 test/business-rule-data/RoutingRules.xlsx delete mode 100644 test/business-rule-data/RoutingRulesOutput.xlsx delete mode 100644 test/business-rule-hit-policy-test.js delete mode 100644 test/business-rule-mixin-test.js delete mode 100644 test/caching-test.js delete mode 100644 test/client-sdk-test.js create mode 100644 test/common/mixins/new-mixin.js create mode 100644 test/common/mixins/test-mixin.js create mode 100644 test/common/models/Customer.json create mode 100644 test/common/models/Spouse.json create mode 100644 test/common/models/base-entity-test.js create mode 100644 test/component-config.json delete mode 100755 test/composite-model-test.js delete mode 100644 test/concurrency-test.js delete mode 100644 test/config-merge-test.js create mode 100644 test/config.json delete mode 100644 test/consistenthash/client.js delete mode 100644 test/consistenthash/haproxy.cfg delete mode 100644 test/consistenthash/server.js delete mode 100644 test/cr-model-test.js delete mode 100644 test/crypto-test.js delete mode 100644 test/data-acl-test.js delete mode 100644 test/data-hierarchy-test.js delete mode 100644 test/data-personalization-test.js delete mode 100644 test/datasource-personalization.js create mode 100644 test/datasources-pg.json1 create mode 100644 test/datasources.json create mode 100644 test/datasources.mongo.js rename {server => test}/datasources.oracle.js (66%) create mode 100644 test/datasources.postgres.js create mode 100644 test/datasources_alldb.json1 delete mode 100644 test/decision-graph-tests.js delete mode 100644 test/decision-service-tests.js delete mode 100644 test/decision-table-test.js delete mode 100644 test/delete-test.js delete mode 100644 test/embedded-many-test.js delete mode 100644 test/enum-test.js delete mode 100644 test/fail-test.js delete mode 100644 test/failSafe-tests/cleanupIntegrationTest.js delete mode 100644 test/failSafe-tests/failsafe.yml delete mode 100644 test/failSafe-tests/integrationTest.js delete mode 100644 test/failSafe-tests/prepareIntegrationTest.js delete mode 100644 test/failsafe-observer-test.js delete mode 100644 test/file-upload-test.js delete mode 100644 test/gridconfig-test.js delete mode 100644 test/gridmetadata-test.js delete mode 100644 test/health-test.js delete mode 100644 test/history-mixin-test.js delete mode 100644 test/idempotency-attribute-update-test.js delete mode 100644 test/idempotency-test.js delete mode 100644 test/idempotent-behavior-test.js delete mode 100644 test/idempotent-mixin-test.js delete mode 100644 test/inheritance-util-tests.js delete mode 100644 test/instance-caching-test.js delete mode 100644 test/integration-tests/integration-test-model.js delete mode 100644 test/integration-tests/integration-test-user.js delete mode 100644 test/job-scheduler-service-tests/oe-cloud.yml delete mode 100644 test/job-scheduler-service-tests/prepare-test.js delete mode 100644 test/job-scheduler-service-tests/scheduler-test.js delete mode 100644 test/jwt-for-access-token-test.js delete mode 100644 test/ldap-auth-tests/ldap-authentication-test.js delete mode 100644 test/ldap-auth-tests/oe-cloud.yml delete mode 100644 test/literal-test.js delete mode 100644 test/manual-scope-update.js create mode 100644 test/middleware.json delete mode 100644 test/misclaneous-test.js delete mode 100644 test/model-collection-test.js create mode 100644 test/model-config.json delete mode 100644 test/model-definition-ACL-test.js delete mode 100644 test/model-definition-inheritance-test.js delete mode 100644 test/model-definition-relation-test.js delete mode 100644 test/model-definition-test.js delete mode 100644 test/model-definition-test2.js delete mode 100644 test/model-definition-validation-test.js delete mode 100644 test/model-feel-belongs-to-relation-test.js delete mode 100644 test/model-feel-belongs-to-self-relation-test.js delete mode 100644 test/model-feel-decision-table-blank-object-payload.js delete mode 100644 test/model-feel-execution-logging.js delete mode 100755 test/model-personalization-test.js delete mode 100644 test/model-personalization-variant-test.js delete mode 100644 test/model-rule-base-entity-inheritance-tests.js delete mode 100644 test/model-rule-data/PropertyPopulator.xlsx delete mode 100644 test/model-rule-data/PropertyPopulatorOne.xlsx delete mode 100644 test/model-rule-data/approve-feel.json delete mode 100644 test/model-rule-data/approve-graph.json delete mode 100644 test/model-rule-data/blank_object.xlsx delete mode 100644 test/model-rule-data/blank_object2.xlsx delete mode 100644 test/model-rule-data/blank_object3.xlsx delete mode 100644 test/model-rule-data/category-feel.json delete mode 100644 test/model-rule-data/category-graph.json delete mode 100644 test/model-rule-data/corrupt.xlsx delete mode 100644 test/model-rule-data/data/functions/index.js delete mode 100644 test/model-rule-data/eligibilityUSA.xlsx delete mode 100644 test/model-rule-data/employee_validation.xlsx delete mode 100644 test/model-rule-data/fetch_relations.xlsx delete mode 100644 test/model-rule-data/peoples.json delete mode 100644 test/model-rule-data/populator1.xlsx delete mode 100644 test/model-rule-data/validate_entry.xlsx delete mode 100644 test/model-rule-data/validation.xlsx delete mode 100644 test/model-rule-test-decision-service-invocation-tests.js delete mode 100644 test/model-rule-test.js delete mode 100644 test/model-transaction-test.js delete mode 100644 test/model-validation-composite-uniqueness-test.js delete mode 100644 test/model-validation-custom-function-test.js delete mode 100644 test/model-validation-embeddedModel-test.js delete mode 100644 test/model-validation-oeValidation-custom-test.js delete mode 100644 test/model-validation-oeValidation-test.js delete mode 100644 test/model-validation-refcode-test.js delete mode 100644 test/model-validation-relation-test.js delete mode 100644 test/model-validation-test.js delete mode 100644 test/model-validation-validateWhen.js delete mode 100644 test/model-validation-xmodelvalidate-test.js delete mode 100644 test/model-variant-of-test.js delete mode 100644 test/multi-tenancy-test.js delete mode 100644 test/oe-studio-test.js rename {server => test}/oracle-utility.js (54%) delete mode 100644 test/otp-test.js delete mode 100644 test/pg-test/basic-crud-test.js delete mode 100644 test/property-expressions-test.js delete mode 100644 test/providers.json delete mode 100644 test/redirect-for-uiroute-test.js delete mode 100644 test/relation-has-one-test.js delete mode 100644 test/relation-references-many-test.js delete mode 100644 test/relation-references-many2-test.js delete mode 100644 test/retry-support-mixin-test.js delete mode 100644 test/rule-engine-cluster-tests/cluster-tests.js delete mode 100644 test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml delete mode 100644 test/rule-engine-cluster-tests/wait-all.sh delete mode 100644 test/rule-engine-cluster-tests/wait-for-up.sh delete mode 100644 test/secrets-manager-test.js delete mode 100644 test/secrets-test-data/TESTMONGOHOST delete mode 100644 test/secrets-test-data/TESTMONGOUSER delete mode 100644 test/select-for-update-test.js create mode 100644 test/server.js delete mode 100644 test/service-personalization-relation-test.js delete mode 100644 test/service-personalization-test.js delete mode 100644 test/soft-delete-mixin-test.js delete mode 100644 test/switch-data-source-test.js create mode 100644 test/test.js delete mode 100644 test/ui-manager-test.js delete mode 100644 test/uicomponent-test.js delete mode 100644 test/uimetadata-test.js delete mode 100644 test/uiresource-test.js delete mode 100644 test/unauthorised-write.js delete mode 100644 test/update-data-acl-test.js delete mode 100644 test/update-default-tenant-record.js delete mode 100644 test/upload-file-data/x.png delete mode 100644 test/version-mixin-test.js delete mode 100644 test/z-jwt-assertion-test.js delete mode 100644 test/z-remove-demo-user-test.js delete mode 100644 test/z-z-batch-job-test.js delete mode 100644 test/z-z-business-validations-tests.js delete mode 100644 test/z-z-rest-api-actors-mixin-tests.js delete mode 100644 test/z-z-z-actor-pattern-activity-check-test.js delete mode 100644 test/z-z-z-actor-pattern-aqcuire-db-lock-test-mongo.js delete mode 100644 test/z-z-z-actor-pattern-db-lock-test.js delete mode 100644 test/z-z-z-actor-pattern-test.js delete mode 100644 test/z-z-z-actor-startup-test.js delete mode 100644 test/z-z-z-logger-config-test.js delete mode 100644 test/z-z-z-z-mark-as-cache-able-test.js delete mode 100644 tools/cleanPostgresDB.bat delete mode 100644 tools/cleandb.bat diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index e69de29..0000000 diff --git a/.eslintignore b/.eslintignore index 717b21f..a042a5f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,6 @@ /node_modules/ server/dropdb.js /lib/expression-language/expression-syntax-parser.js -/test/ \ No newline at end of file +/test/ +drop.js + diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index bb5e158..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -* text=auto -*.js text \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2a180a0..9912bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ out/ .vimrc *.heapsnapshot *.zip +package-lock.json /common/models/test diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4a34f9b..944ae0b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,26 +1,9 @@ -# ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -#Bangalore, India. All Rights Reserved. - stages: - pre-build - pre-build-test - - build - - swarm-actor-pattern - - retry-service - - ldap-auth - - job-scheduler-service - - rule-engine-cluster-tests - -cache: - untracked: true - paths: - - node_modules/ image: $REGISTRY/evfoundation-executor-docker13:node8alpine36 - variables: - USE_BROADCASTER: "false" - #DOMAIN_NAME: oecloud.local REGISTRY: registry.${DOMAIN_NAME} before_script: @@ -28,15 +11,6 @@ before_script: - export group=${CI_PROJECT_NAMESPACE//[^[:alnum:]]/} - export branch=${CI_BUILD_REF_NAME//[^[:alnum:]]/} - export pipelineId=${CI_PIPELINE_ID//[^[:alnum:]]/} - - if [ $branch == "master" ]; then export APP_IMAGE_NAME=$group"-"$project; else export APP_IMAGE_NAME=$group"-"$branch"-"$project; fi - - export APP_IMAGE_NAME=$(echo $APP_IMAGE_NAME | tr '[:upper:]' '[:lower:]') - - npm config set registry $NPM_REGISTRY - - npm config set fetch-retries 0 - #- export DOMAIN_NAME=oecloud.local - #- export REGISTRY=registry.${DOMAIN_NAME} - - export NETWORK_NAME=${APP_IMAGE_NAME} - - export APP_TAG=latest - - export ROUTER=cep_router npminstall: stage: pre-build @@ -45,35 +19,40 @@ npminstall: - status_eslint=0 - exit_status=0 - npm set progress=false - - time npm install eslint babel-eslint grunt grunt-cli grunt-check-copyright --no-optional + - npm config set registry http://10.188.25.62:9002/ + - time npm install eslint babel-eslint --no-optional - if npm run lint; then status_eslint=0; else status_eslint=1; fi - - if npm run check-copyright; then status_copyright=0; else status_copyright=1; fi - if [ $status_eslint == 1 ]; then exit_status=1 && echo "esLint errors exist"; fi - - if [ $status_copyright == 1 ]; then exit_status=1 && echo "copyright not present in files"; fi - if [ $exit_status == 1 ]; then exit $exit_status; fi - - echo "Code style and copyright check done successfully" + - echo "Code style check done successfully" - time npm install --no-optional - - sed 's/\$REGISTRY/'"$REGISTRY"'/g' Dockerfile > Dockerfile2 - - mv -f Dockerfile2 Dockerfile - echo "Node Modules Installed" + - npm config rm registry + - npm audit --json >vulnerabilities.json + - status_vulnerabilities='echo $?' + - npm audit + - if [ $status_vulnerabilities == 1 ]; then exit_status=1 && echo "Dependency vulnerabilities exist"; fi + - if [ $exit_status == 1 ]; then exit $exit_status; fi artifacts: untracked: true expire_in: 2h paths: - node_modules/ tags: - - CEP_RUNNER + - CEP_RUNNER_EE + mongotest: stage: pre-build-test script: - - export NODE_ENV=test + - echo 'Performing MongoDB Test' + - export NODE_ENV=mongo - export MONGO_HOST="10.73.53.144" - export DB=mongo - export DB_NAME=${CI_PIPELINE_ID}_mongo + - npm config set registry http://10.188.25.62:9002/ - time npm install --no-optional - npm run grunt-cover - - mocha test/01-cleanup.js artifacts: untracked: true expire_in: 2h @@ -81,280 +60,48 @@ mongotest: - coverage/ when: on_failure tags: - - CEP_RUNNER + - CEP_RUNNER_EE -.postgrestest: +postgrestest: stage: pre-build-test script: - - export NODE_ENV=test - - export POSTGRES_HOST="10.73.53.144" - - export MONGO_HOST="10.73.53.144" + - echo 'Performing PostgreSQL Test' + - export NODE_ENV=postgres - export DB=postgres + - export POSTGRES_HOST="10.73.53.144" - export DB_NAME=${CI_PIPELINE_ID}_pg + - export ENABLE_DS_AUTOUPDATE=true + - npm config set registry http://10.188.25.62:9002/ - time npm install --no-optional - npm run grunt-cover - - mocha test/01-cleanup.js - artifacts: - untracked: true - expire_in: 2h - paths: - - coverage/ - when: on_failure tags: - - CEP_RUNNER - -.oracletest: + - CEP_RUNNER_EE + +oracletest: image: $REGISTRY/debian-node-oracle-docker stage: pre-build-test script: - - cd .. && export PROJECT_DIR=$(pwd) && mv oe-cloud/ /root/ && cd /root/oe-cloud + - echo 'Performing Oracle Test' - export APP_TAG=oracle - - export NODE_ENV=test - - export POSTGRES_HOST="10.73.53.144" - - export MONGO_HOST="10.73.53.144" + - export NODE_ENV=oracle - export ORACLE_HOST=10.73.53.144 - export ORACLE_SYSUSER=oeadmin - export ORACLE_SYSPASSWORD=oeadmin - export ORACLE_SID=ORCLCDB + - export DB_NAME=${CI_PIPELINE_ID}_pg + - export ENABLE_DS_AUTOUPDATE=true + - npm config set registry http://10.188.25.62:9002/ + - time npm install git+http://evgit/oecloud.io/oe-connector-oracle.git --no-optional - time npm install --no-optional - - npm install nan async --no-optional - - npm install git+http://10.73.97.24/oecloud.io/oe-connector-oracle.git --no-optional - mv /oracledb node_modules/ - #- export TMP_CI_PROJECT_NAMESPACE=${CI_PROJECT_NAMESPACE} - export CI_PROJECT_NAMESPACE=${group} - - node server/oracle-utility.js - #- export CI_PROJECT_NAMESPACE=${TMP_CI_PROJECT_NAMESPACE} - # Removing decision test cases which are frequently failing in oracle - - rm test/model-rule-test.js test/decision-graph-tests.js test/business-rule-hit-policy-test.js test/decision-service-tests.js test/decision-table-test.js + - node test/oracle-utility.js - export ORACLE_USERNAME=${CI_PROJECT_NAMESPACE}"-"${CI_PROJECT_NAME} - export ORACLE_USERNAME=$(echo $ORACLE_USERNAME | tr '[:lower:]' '[:upper:]') - export ORACLE_PASSWORD=$(echo $CI_PROJECT_NAMESPACE | tr '[:upper:]' '[:lower:]') - echo "Oracle user details:"${ORACLE_USERNAME}"/"${ORACLE_PASSWORD} - - npm run grunt-test - - node server/oracle-utility.js - - cd .. && mv oe-cloud/ ${PROJECT_DIR} && cd ${PROJECT_DIR}/oe-cloud - tags: - - CEP_RUNNER - -consistenthashtest: - stage: pre-build-test - script: - - mkdir -p /data/db - - mongod --dbpath /data/db & - - haproxy -f ./test/consistenthash/haproxy.cfg -p /run/haproxy.pid -Ds & - - export APP_URL=http://localhost:8080/api - - NO_PROXY=$NO_PROXY,localhost,127.0.0.1 - - git clone http://10.73.97.24/oecloud.io/oe-tx-router.git - - cd oe-tx-router - - time npm install --no-optional - - node . & - - cd .. - - time npm install --no-optional - - mocha test/consistenthash/server.js & - - sleep 60 - - mocha test/consistenthash/server.js 3100 & - - sleep 60 - - mocha test/consistenthash/client.js 3200 - tags: - - CEP_RUNNER - -buildjob: - stage: build - script: - - sed 's/\$REGISTRY/'"$REGISTRY"'/g' Dockerfile > Dockerfile2 - - mv -f Dockerfile2 Dockerfile - - time npm install --no-optional - - time docker build -t ${REGISTRY}/${APP_IMAGE_NAME}:latest --no-cache --pull . - - docker push ${REGISTRY}/${APP_IMAGE_NAME}:latest - - echo "Image (${REGISTRY}/${APP_IMAGE_NAME}:latest) built and pushed to registry" - tags: - - CEP_RUNNER - -swarm-actor-pattern: - stage: swarm-actor-pattern - script: - - export STARTUP_CMD="npm run grunt-cover" - - export FILE_NAME=docker-compose.test.env.yml - - export DB_NAME=${APP_IMAGE_NAME}_mongo - - export DB=mongo - - export STACK_NAME=${APP_IMAGE_NAME} - - export NETWORK_NAME=${APP_IMAGE_NAME} - - export exit_status="0" - - export nameWebService=${STACK_NAME}"_web" - - export nameDBService=${STACK_NAME}"_"${DB} - #- export HAPROXY=10.73.96.214 - - echo "docker stack rm ${STACK_NAME}" > /removeStack.sh - - docker stack rm ${APP_IMAGE_NAME} - - time npm install --no-optional - - mkdir test/actor-tests/test-files/server - - node test/actor-tests/prepare-test.js - - cd test/actor-tests/test-files - - sed 's/\$REGISTRY/'"$REGISTRY"'/g' Dockerfile > Dockerfile2 - - mv -f Dockerfile2 Dockerfile - - time docker build --build-arg REGISTRY=${REGISTRY} --build-arg APP_IMAGE_NAME=${APP_IMAGE_NAME} -t ${REGISTRY}/${APP_IMAGE_NAME}:testing . - - time docker push ${REGISTRY}/${APP_IMAGE_NAME}:testing - - sed 's/\$NETWORK_NAME/'"$NETWORK_NAME"'/g' qqq.yml > qqq2.yml - - mv -f qqq2.yml qqq.yml - - sed 's/\$EVFURL/'${APP_IMAGE_NAME}.${DOMAIN_NAME}'/g' ../actorIntegrationTest.js > ../actorIntegrationTest2.js - - mv -f ../actorIntegrationTest2.js ../actorIntegrationTest.js - - docker stack deploy -c qqq.yml ${APP_IMAGE_NAME} - - echo "${HAPROXY} ${APP_IMAGE_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - docker service logs -f ${nameWebService} & - - isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/) - - export countElapsed=0 - - export app_exit_status=0 - - while [ $isStarted -ne 200 ]; do let countElapsed=countElapsed+1; echo ""; echo -n "Waiting till the URL is up..."; export isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/); echo $isStarted; sleep 15; if [ $countElapsed -eq 18 ] ; then export app_exit_status=1; export isStarted=200; fi; done - - if [ ${app_exit_status} -ne 0 ]; then echo "Failed to start app..."; docker stack rm ${APP_IMAGE_NAME}; exit ${app_exit_status}; fi - - cd ../../.. - - time mocha test/actor-tests/actorIntegrationTest.js - after_script: - - sh /removeStack.sh - tags: - - CEP_RUNNER - -retry-service: - stage: retry-service - script: - - export ROUTER="cep_router" - #- export HAPROXY=10.73.96.214 - - export ORIG_APP_IMAGE_NAME=${APP_IMAGE_NAME} - - export APP_IMAGE_NAME=${APP_IMAGE_NAME}-r - - export STACK_NAME=${APP_IMAGE_NAME} - - export NETWORK_NAME=${STACK_NAME} - - export RETRY_SERVICE_NAME=${APP_IMAGE_NAME}-retry-service - - export RETRY_STACK_NAME=${APP_IMAGE_NAME}-rs - - export RETRY_NETWORK_NAME=${RETRY_STACK_NAME} - - echo "docker stack rm ${STACK_NAME}" >> /removeStacks.sh - - echo "docker stack rm ${RETRY_STACK_NAME}" >> /removeStacks.sh - - docker stack rm ${STACK_NAME} - - docker stack rm ${RETRY_STACK_NAME} - - sleep 10 - - time npm install --no-optional - - mkdir test/async-service-tests/test-files/server - - node test/async-service-tests/prepare-test.js - - cd test/async-service-tests/test-files - - sed 's/\$REGISTRY/'"$REGISTRY"'/g' Dockerfile > Dockerfile2 - - mv -f Dockerfile2 Dockerfile - - time docker build --build-arg REGISTRY=${REGISTRY} --build-arg APP_IMAGE_NAME=${ORIG_APP_IMAGE_NAME} -t ${REGISTRY}/${ORIG_APP_IMAGE_NAME}:async-test . - - time docker push ${REGISTRY}/${ORIG_APP_IMAGE_NAME}:async-test - - sed 's/\$NETWORK_NAME/'"$NETWORK_NAME"'/g' oecloud.yml > oecloud2.yml - - mv -f oecloud2.yml oecloud.yml - - docker stack deploy -c oecloud.yml ${STACK_NAME} - - sed 's/\$RETRY_NETWORK_NAME/'"$RETRY_NETWORK_NAME"'/g' retry-service.yml > retry-service2.yml - - mv -f retry-service2.yml retry-service.yml - - docker stack deploy -c retry-service.yml ${RETRY_STACK_NAME} - - docker service logs -f ${STACK_NAME}_web & - - docker service logs -f ${RETRY_STACK_NAME}_server & - - docker service logs -f ${RETRY_STACK_NAME}_processor & - - echo "${HAPROXY} ${APP_IMAGE_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - echo "${HAPROXY} ${RETRY_SERVICE_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/) - - export countElapsed=0 - - export app_exit_status=0 - - while [ $isStarted -ne 200 ]; do let countElapsed=countElapsed+1; echo ""; echo -n "Waiting till the URL is up..."; export isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/); echo $isStarted; sleep 15; if [ $countElapsed -eq 18 ] ; then export app_exit_status=1; export isStarted=200; fi; done - - if [ ${app_exit_status} -ne 0 ]; then echo "Failed to start app..."; docker stack rm ${APP_IMAGE_NAME}; exit ${app_exit_status}; fi - - isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${RETRY_SERVICE_NAME}.${DOMAIN_NAME}/) - - export countElapsed=0 - - export app_exit_status=0 - - while [ $isStarted -ne 200 ]; do let countElapsed=countElapsed+1; echo ""; echo -n "Waiting till the URL is up..."; export isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/); echo $isStarted; sleep 15; if [ $countElapsed -eq 18 ] ; then export app_exit_status=1; export isStarted=200; fi; done - - if [ ${app_exit_status} -ne 0 ]; then echo "Failed to start app..."; docker stack rm ${RETRY_SERVICE_NAME}; exit ${app_exit_status}; fi - - cd ../../.. - - sleep 60 - - time mocha --timeout 30000 test/async-service-tests/async-service-integration-test.js - after_script: - - sh /removeStacks.sh + - npm run grunt-cover tags: - - CEP_RUNNER + - CEP_RUNNER_EE -ldap-auth: - stage: ldap-auth - script: - - export ROUTER="cep_router" - #- export HAPROXY=10.73.96.214 - - export APP_IMAGE_NAME=${APP_IMAGE_NAME} - - export STACK_NAME=${APP_IMAGE_NAME}-ldap - - export NETWORK_NAME=${STACK_NAME} - - echo "docker stack rm ${STACK_NAME}" > /removeStack.sh - - docker stack rm ${STACK_NAME} - - sleep 10 - - time npm install --no-optional - - sed 's/\$NETWORK_NAME/'"$NETWORK_NAME"'/g' test/ldap-auth-tests/oe-cloud.yml > test/ldap-auth-tests/oe-cloud2.yml - - mv -f test/ldap-auth-tests/oe-cloud2.yml test/ldap-auth-tests/oe-cloud.yml - - docker stack deploy -c test/ldap-auth-tests/oe-cloud.yml ${STACK_NAME} - - docker service logs -f ${STACK_NAME}_web & - - echo "${HAPROXY} ${APP_IMAGE_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/) - - export countElapsed=0 - - export app_exit_status=0 - - while [ $isStarted -ne 200 ]; do let countElapsed=countElapsed+1; echo ""; echo -n "Waiting till the URL is up..."; export isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/); echo $isStarted; sleep 15; if [ $countElapsed -eq 18 ] ; then export app_exit_status=1; export isStarted=200; fi; done - - if [ ${app_exit_status} -ne 0 ]; then echo "Failed to start app..."; docker stack rm ${APP_IMAGE_NAME}; exit ${app_exit_status}; fi - - time mocha --timeout 30000 test/ldap-auth-tests/ldap-authentication-test.js - after_script: - - sh /removeStack.sh - tags: - - CEP_RUNNER -job-scheduler-service: - stage: job-scheduler-service - script: - - export ROUTER="cep_router" - #- export HAPROXY=10.73.96.214 - - export ORIG_APP_IMAGE_NAME=${APP_IMAGE_NAME} - - export APP_IMAGE_NAME=${APP_IMAGE_NAME}-s - - export STACK_NAME=${APP_IMAGE_NAME} - - export NETWORK_NAME=${STACK_NAME} - - export SCHEDULER_NAME=${APP_IMAGE_NAME}-scheduler-service - - echo "docker stack rm ${STACK_NAME}" > /removeStack.sh - - docker stack rm ${STACK_NAME} - - sleep 10 - - time npm install --no-optional - - node test/job-scheduler-service-tests/prepare-test.js - - sed 's/\$REGISTRY/'"$REGISTRY"'/g' Dockerfile > Dockerfile2 - - mv -f Dockerfile2 Dockerfile - - time docker build -t ${REGISTRY}/${ORIG_APP_IMAGE_NAME}:oe-scheduler-test . - - time docker push ${REGISTRY}/${ORIG_APP_IMAGE_NAME}:oe-scheduler-test - - sed 's/\$NETWORK_NAME/'"$NETWORK_NAME"'/g' test/job-scheduler-service-tests/oe-cloud.yml > test/job-scheduler-service-tests/oe-cloud2.yml - - mv -f test/job-scheduler-service-tests/oe-cloud2.yml test/job-scheduler-service-tests/oe-cloud.yml - - docker stack deploy -c test/job-scheduler-service-tests/oe-cloud.yml ${STACK_NAME} - - docker service logs -f ${STACK_NAME}_web & - - docker service logs -f ${STACK_NAME}_scheduler & - - echo "${HAPROXY} ${APP_IMAGE_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - echo "${HAPROXY} ${SCHEDULER_NAME}.${DOMAIN_NAME}" >> /etc/hosts - - isSchedulerStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${SCHEDULER_NAME}.${DOMAIN_NAME}/) - - export countElapsedScheduler=0 - - export app_exit_status_scheduler=0 - - while [ $isSchedulerStarted -ne 200 ]; do let countElapsedScheduler=countElapsedScheduler+1; echo ""; echo -n "Waiting till the scheduler URL is up..."; export isSchedulerStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${SCHEDULER_NAME}.${DOMAIN_NAME}/); echo $isSchedulerStarted; sleep 15; if [ $countElapsedScheduler -eq 18 ] ; then export app_exit_status_scheduler=1; export isSchedulerStarted=200; fi; done - - if [ ${app_exit_status_scheduler} -ne 0 ]; then echo "Failed to start scheduler..."; docker stack rm ${APP_IMAGE_NAME}; exit ${app_exit_status_scheduler}; fi; - - isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/) - - export countElapsed=0 - - export app_exit_status=0 - - while [ $isStarted -ne 200 ]; do let countElapsed=countElapsed+1; echo ""; echo -n "Waiting till the URL is up..."; export isStarted=$(curl -k --write-out %{http_code} --output curl.out --silent https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/); echo $isStarted; sleep 15; if [ $countElapsed -eq 18 ] ; then export app_exit_status=1; export isStarted=200; fi; done - - if [ ${app_exit_status} -ne 0 ]; then echo "Failed to start app..."; docker stack rm ${APP_IMAGE_NAME}; exit ${app_exit_status}; fi - - time mocha --timeout 60000 test/job-scheduler-service-tests/scheduler-test.js - after_script: - - sh /removeStack.sh - tags: - - CEP_RUNNER - -rule-cluster-tests: - stage: rule-engine-cluster-tests - script: - - export STACK_NAME=${APP_IMAGE_NAME} - - export NETWORK_NAME=${APP_IMAGE_NAME} - - npm install mocha - - echo "docker stack rm $STACK_NAME" > ./remove.sh - - /bin/sh ./remove.sh - - sleep 30 - - sed 's/\$NETWORK_NAME/'"$NETWORK_NAME"'/g' ./test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml > ./test/rule-engine-cluster-tests/rule-engine-cluster-setup2.yml - - mv -f ./test/rule-engine-cluster-tests/rule-engine-cluster-setup2.yml test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml - - docker stack deploy -c test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml $STACK_NAME - - sleep 10 - - /bin/sh ./test/rule-engine-cluster-tests/wait-all.sh "https://test.node1.oecloud.local/" "https://test.node2.oecloud.local/" - # - /bin/sh ./test/rule-engine-cluster-tests/wait-for-up.sh "https://test.node1.oecloud.local/" - - export NODE_TLS_REJECT_UNAUTHORIZED=0 - - ./node_modules/.bin/mocha --bail --timeout 10000 ./test/rule-engine-cluster-tests/cluster-tests.js - after_script: - # - echo "done..." - - /bin/sh ./remove.sh - tags: - - CEP_RUNNER diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md deleted file mode 100644 index 19359bf..0000000 --- a/CONTRIBUTION.md +++ /dev/null @@ -1,28 +0,0 @@ -# Contributing - -Thank you for your interest in oeCloud.io, an open source project administered by EdgeVerve. - - -## Raising issues - -Please raise any bug reports on the relevant project's issue tracker. Be sure to -search the list to see if your issue has already been raised. - -A good bug report is one that make it easy for us to understand what you were -trying to do and what went wrong. - - -### Contributor License Agreement - -You must sign a Contributor License Agreement (CLA) before submitting your pull request. To complete the CLA, please email a signed .pdf file of this Agreement to IPC@EdgeVerve.com. You need to complete the CLA only once to cover all EdgeVerve projects. - -You can download the CLAs here: - - [Individual Contributer License Agreement](./INDIVIDUAL_CLA.md) - - [Corporate Contributer License Agreement](./CORPORATE_CLA.md) - -If you are an Infosys employee, please contact us directly as the contribution process is -slightly different. - -### Coding standards - -Please ensure you follow the coding standards used through-out the existing code base. diff --git a/CORPORATE_CLA.md b/CORPORATE_CLA.md deleted file mode 100644 index bda1b02..0000000 --- a/CORPORATE_CLA.md +++ /dev/null @@ -1,56 +0,0 @@ -# Corporate Contributor License Agreement - -By signing this Corporate Contributor License Agreement (“Agreement”), and making a Contribution (as defined below) to EdgeVerve Systems Limited, located in Electronics City, Hosur Road, Bangalore 560 100 (“EdgeVerve”). - -This version of the Agreement allows an entity (the "Corporation") to submit Contributions to EdgeVerve, to authorize Contributions submitted by its designated employees to EdgeVerve, and to grant copyright and patent licenses thereto. - -You (as defined below) accept and agree to the following terms and conditions for Your present and future Contributions submitted to EdgeVerve. Except for the license granted in this Agreement to EdgeVerve and recipients of software distributed by EdgeVerve, You reserve all right, title, and interest in and to Your Contributions. Please read this document carefully before signing and keep a copy for your records. Please email a signed .pdf file of this Agreement to IPC@edgeverve.com. -1. Definitions: -“You” or “Your” shall mean the copyright owner or the legal entity authorized by the copyright owner that is entering into this Agreement with EdgeVerve. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. -"Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the EdgeVerve for inclusion in, or documentation of, any of the products owned or managed by the EdgeVerve ("Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the EdgeVerve or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, EdgeVerve for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -2. Grant of Copyright License: -Subject to the terms and conditions of this Agreement, You hereby grant EdgeVerve and recipients of software distributed by EdgeVerve, a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works under any license and without any restrictions. -3. Grant of Patent License: -Subject to the terms and conditions of this Agreement, You hereby grant to EdgeVerve and to recipients of software distributed by EdgeVerve a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this Section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work under any license and without any restrictions. The patent license You grant to EdgeVerve and the recipients of the software under this Section applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions(s) alone or by combination of Your Contributions(s) with the Work to which such Contribution(s) was submitted. If any entity institutes a patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. -4. Grant of License: -You represent that You are legally entitled to grant the licenses under this Agreement. -You represent further that every employee of the Corporation mentioned in Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation -5. Original Work: -You represent that each of Your Contributions are Your original works of authorship (see Section 7 - Submission on behalf of others) -6. Support: -You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. EdgeVerve acknowledges that unless required by applicable law or agreed to in writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -7. Submission on behalf of others: -If You wish to submit work that is not Your original creation, You may submit it to EdgeVerve separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as “Submitted on Behalf of a Third-Party: [named here]”. -8. Change of circumstances: -It is your responsibility to notify EdgeVerve when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with EdgeVerve. You agree to notify EdgeVerve of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. Email us at IPC@edgeverve.com. - -
- -[Signature page follows] -
-
-
- -Signature: _____________________________________________________________________ - - -Name: __________________________________________________________________________ - - -Corporation: ___________________________________________________________________ - - -Title: _________________________________________________________________________ - - -Date: __________________________________________________________________________ - - - - - -Schedule A -List of employees - -Schedule B -List of works of authorship \ No newline at end of file diff --git a/Coding_Guidelines.md b/Coding_Guidelines.md deleted file mode 100644 index 404f929..0000000 --- a/Coding_Guidelines.md +++ /dev/null @@ -1,92 +0,0 @@ - -# Development guidelines - -## Naming conventions -### File Naming Convention -1. All code files should be named in kebab-case. That is all lowercase and words separated by hyphen (-) -e.g. base-entity.js, base-entity.json, config.json etc. - - -### Model naming -1. Model names should always be in PascalCase. That is created by concatenating capitalized words. -e.g. BaseEntity, AppUser etc. - -2. Model name should be singular noun. -e.g. AppUsers is not a correct model name. - -3. Model plural should be in camelCase. That is same as PascalCase, but 1st character of 1st word is in lower case. - -4. Model property names should be in camelCase. - -### Variable Naming conventions - -1. All variables should follow camelCasing convention -TODO - -naming for constant variables, model names etc needs to be defined. - -### Property names in config files. -1. Property names should be in camelCase - -## Code formatting guidelines. - -### Formatting guidelines -TODO Guidelines goes here. These should include following - -1. Indentation -..* Use spaces instead of tabs -..* Indentation size = 4 spaces -2. Braces -..1. Starting brace goes on the same line. -3. White spaces -..1. Space after comma in multiple variable declaration -..2. Space before an opening brace -..3. Space after a comma in function parameter -4. Blank lines -5. New lines -6. Control statements -7. Line wrapping -8. Comments - -# Coding guidelines -### Use JavaScript native functions instead of libraries like lodash. -e.g. User map () function to iterate over array instead of using _.forEach () - -### Avoid if-else style of code. Code should be attached, only when it is needed. -E.g. when a mixin is applied to a model, it attaches the code to the model. This code should further not check if mixin is applied or not. - -### Avoid unnecessary 'undefined' checks by initializing with default value. -e.g. -``` -var autoscope = ctx.Model.definition.settings.autoscope ? ctx.Model.definition.settings.autoscope : []; -``` - -In above statement ctx.Model.definition.settings.autoscope check can be avoided by initializing autoscope parameter to an empty array ([]) in the model constructor. - - -e.g. -``` -if (autoscope && autoscope.length) { -} -``` - -since we have initialized autoscope such ```if``` condition is not needed. - -### Error management / guideline -Create and use multiple types of Error objects like EvBusinessError, EvSystemError instead of generic Error. EvBusinessError can further be of type e.g. DataPersonalizationError - -### Logging guidelines - -### Design APIs doing specific operations instead of generic API -e.g. instead /Application API accepting commands like 'create', 'start', 'stop' in a single API, create individual Apis - /Application/id/start, /Application/id/stop etc. - -Individual APIs gives better control over ACLs, Monitoring and metering. - -### Use constants instead of hardcoded string. -e.g. Instead of using 'before save' have a constant variable and use it. - - -### Avoid accessing nested object repetitively. -Instead capture the value of nested object in a variable for further use. - - diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1943c39..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM registry.oecloud.local/alpine-node:8-alpine - -RUN mkdir -p /home/src - -ENV NODE_ENV docker -ENV MONGO_HOST mongo -ENV POSTGRES_HOST postgres - -EXPOSE 3000 - -WORKDIR /home/src - -COPY . /home/src - -CMD node . diff --git a/Dockerfile_Oracle b/Dockerfile_Oracle deleted file mode 100644 index 0eee81f..0000000 --- a/Dockerfile_Oracle +++ /dev/null @@ -1,21 +0,0 @@ -FROM $REGISTRY/debian-node-oracle:latest - -#ARG http_proxy -#ARG https_proxy -#ARG no_proxy - -RUN mkdir -p /home/src - -ENV NODE_ENV oracle - -EXPOSE 3000 - -WORKDIR /home/src - -CMD node . - -COPY . /home/src - -#RUN npm install oracledb --no-optional && \ -# npm install git+http://10.73.97.24/atul/loopback-connector-oracle.git --no-optional && \ -# npm cache clean diff --git a/Gruntfile.js b/Gruntfile.js index 6b45de3..0ef31f4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,100 +1,14 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ +/** + * + * ©2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + module.exports = function GruntConfig(grunt) { // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - - mkdir: { - all: { - options: { - create: ['dist'] - } - } - }, - - copy: { - main: { - files: [ - // includes files within path and its sub-directories - { - expand: true, - src: ['**', '!node_modules/**', '!coverage/**'], - dest: 'dist/' - } - ] - } - }, - - usebanner: { - all: { - options: { - position: 'top', - banner: '/*\n' + - '©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved.\n' + - 'The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. \n' + - 'The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted.\n' + - 'Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law.\n' + - '*/', - linebreak: true - }, - files: { - src: ['**/*.js'] - } - } - }, - - check_copyright: { - options: { - // Task-specific options go here. - copyrights: [/EdgeVerve Systems Limited \(a fully owned Infosys subsidiary\).*Bangalore, India.*All Rights Reserved.*/], - lines: 10 - }, - files: { - // Target-specific file lists and/or options go here. - src: ['**/*.js', '**/*.html', '!node_modules/**', '!bower_components/**', '!client/bower_components/**', '!coverage/**'] - } - }, - - mochaTest: { - test: { - options: { - quiet: false, - clearRequireCache: true, - timeout: 100000 - }, - src: ['test/*.js'] - }, - integrationTest: { - options: { - quiet: false, - clearRequireCache: true, - timeout: 100000 - }, - src: ['test/integration-test/*.js'] - }, - postgres: { - options: { - quiet: false, - clearRequireCache: true, - timeout: 100000 - }, - src: ['test/audit-field-mixin-test.js', 'test/auto-fields-test.js', 'test/basic-api-test.js', 'test/basic-test.js', 'test/batch-job-test.js', 'test/bootstrap.js', 'test/business-rule-mixin-test.js', 'test/caching-test.js', 'test/composite-model-test.js', 'test/concurrency-test.js', 'test/config-merge-test.js', 'test/cr-model-test.js', 'test/crypto-test.js', 'test/data-acl-test.js', 'test/data-hierarchy-test.js', 'test/data-personalization-test.js', 'test/datasource-personalization.js', 'test/decision-table-test.js', 'test/delete-test.js', 'test/designer-boot-test.js', 'test/embedded-many-test.js', 'test/enum-test.js', 'test/failsafe-observer-test.js', 'test/fail-test.js', 'test/gridconfig-test.js', 'test/gridmetadata-test.js', 'test/health-test.js', 'test/history-mixin-test.js', 'test/idempotent-behavior-test.js', 'test/idempotent-mixin-test.js', 'test/import-export-test.js', 'test/integration-test.js', 'test/job-scheduler-test.js', 'test/literal-test.js', 'test/misclaneous-test.js', 'test/model-collection-test.js', 'test/model-definition-ACL-test.js', 'test/model-definition-inheritance-test.js', 'test/model-definition-relation-test.js', 'test/model-definition-test.js', 'test/model-definition-test2.js', 'test/model-definition-validation-test.js', 'test/model-personalization-test.js', 'test/model-transaction-test.js', 'test/model-validation-composite-uniqueness-test.js', 'test/model-validation-embeddedModel-test.js', 'test/model-validation-evValidation-custom-test.js', 'test/model-validation-evValidation-test.js', 'test/model-validation-relation-test.js', 'test/model-validation-test.js', 'test/model-validation-validateWhen.js', 'test/model-validation-xmodelvalidate-test.js', 'test/model-variant-of-test.js', 'test/multi-tenancy-test.js', 'test/node-red-test.js', 'test/property-expressions-test.js', 'test/relation-has-one-test.js', 'test/service-personalization-relation-test.js', 'test/service-personalization-test.js', 'test/soft-delete-mixin-test.js', 'test/switch-data-source-test.js', 'test/uicomponent-test.js', 'test/uimetadata-test.js', 'test/unauthorised-write.js', 'test/update-data-acl-test.js', 'test/version-mixin-test.js', 'test/z-jwt-assertion-test.js', 'test/z-remove-demo-user-test.js', 'test/z-z-ap-state-test.js', 'test/z-z-business-validations-tests.js', 'test/z-z-rest-api-actors-mixin-tests.js', 'test/z-z-z-actor-pattern-activity-check-test.js', 'test/z-z-z-actor-pattern-test.js', 'test/z-z-z-actor-pattern-db-lock-test.js', 'test/z-z-journal-retry-tests.js', 'test/z-z-z-actor-startup-test.js', 'test/z-z-z-logger-config-test.js', 'test/z-z-z-z-mark-as-cache-able-test.js', 'test/instance-caching-test.js'] - }, - oracle: { - options: { - quiet: false, - clearRequireCache: true, - timeout: 100000 - }, - src: ['test/audit-field-mixin-test.js', 'test/auto-fields-test.js', 'test/basic-api-test.js', 'test/basic-crud.js', 'test/basic-test.js', 'test/bootstrap.js', 'test/business-rule-mixin-test.js', 'test/composite-model-test.js', 'test/concurrency-test.js', 'test/config-merge-test.js', 'test/cr-model-test.js', 'test/data-hierarchy-test.js', 'test/instance-caching-test.js'] - } - }, - clean: { coverage: { src: ['coverage/'] @@ -105,16 +19,18 @@ module.exports = function GruntConfig(grunt) { }, mocha_istanbul: { + options: { + mochaOptions: ['--exit'] + }, coverage: { - src: 'test', + src: ['test/test.js'], options: { - excludes: ['lib/expression-language/expression-syntax-parser.js', 'lib/ev-tenant-util.js', 'common/models/framework/cache-manager.js', 'lib/common/broadcaster-client.js', 'server/boot/uws-boot.js', 'lib/uws-client.js', 'lib/proxy-context.js', 'common/models/framework/base-user-identity.js'], timeout: 60000, check: { lines: 75, statements: 75, - branches: 65, - functions: 75 + branches: 60, + functions: 80 }, reportFormats: ['lcov'] } @@ -129,9 +45,6 @@ module.exports = function GruntConfig(grunt) { grunt.loadNpmTasks('grunt-mkdir'); grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-check-copyright'); - grunt.loadNpmTasks('grunt-banner'); grunt.registerTask('test-with-coverage', ['clean:coverage', 'mocha_istanbul']); - grunt.registerTask('addbanner', ['clean:dist', 'mkdir', 'copy', 'usebanner']); }; diff --git a/INDIVIDUAL_CLA.md b/INDIVIDUAL_CLA.md deleted file mode 100644 index b00dec8..0000000 --- a/INDIVIDUAL_CLA.md +++ /dev/null @@ -1,39 +0,0 @@ -# Individual Contributor License Agreement - -By signing this Individual Contributor License Agreement (“Agreement”), and making a Contribution (as defined below) to EdgeVerve Systems Limited, located in Electronics City, Hosur Road, Bangalore 560 100 (“EdgeVerve”), You (as defined below) accept and agree to the following terms and conditions for Your present and future Contributions submitted to EdgeVerve. Except for the license granted in this Agreement to EdgeVerve and recipients of software distributed by EdgeVerve, You reserve all right, title, and interest in and to Your Contributions. Please read this document carefully before signing and keep a copy for Your records. Please email a signed .pdf file of this Agreement to IPC@edgeverve.com. -1. Definitions: -“You” or “Your” shall mean the copyright owner or the individual authorized by the copyright owner that is entering into this Agreement with EdgeVerve. -“Contribution” shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to EdgeVerve for inclusion in, or documentation of, any of the products owned or managed by EdgeVerve (“Work”). For purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to EdgeVerve or its representatives, including but not limited to communication or electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, EdgeVerve for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as “Not a Contribution.” -2. Grant of Copyright License: -Subject to the terms and conditions of this Agreement, You hereby grant EdgeVerve and recipients of software distributed by EdgeVerve, a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works under any license and without any restrictions. -3. Grant of Patent License: -Subject to the terms and conditions of this Agreement, You hereby grant to EdgeVerve and to recipients of software distributed by EdgeVerve a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this Section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work under any license and without any restrictions. The patent license You grant to EdgeVerve and the recipients of the software under this Section applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions(s) alone or by combination of Your Contributions(s) with the Work to which such Contribution(s) was submitted. If any entity institutes a patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. -4. Grant of License: -You represent that You are legally entitled to grant the licenses under this Agreement. -If Your employer(s) has rights to intellectual property that You create, You represent that You have received appropriate permission(s) to make the Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions, or that Your employer has executed a separate Corporate Contributor License Agreement with EdgeVerve. -5. Original Work: -You represent that each of Your Contributions are Your original works of authorship (see Section 7 - Submission on behalf of others). You represent that to Your knowledge, no other person claims, or has the right to claim, any right in any intellectual property right related to Your Contributions. -You also represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this Agreement. -You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are personally aware and which are associated with any part of Your Contributions. -6. Support: -You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. EdgeVerve acknowledges that unless required by applicable law or agreed to in writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -7. Submission on behalf of others: -If You wish to submit work that is not Your original creation, You may submit it to EdgeVerve separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as “Submitted on Behalf of a Third-Party: [named here]”. -8. Change of circumstances: -You agree to notify EdgeVerve of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. Email us at IPC@edgeverve.com. -
-
-
- - -Signature: ____________________________________________________________________ - - -Name: _________________________________________________________________________ - - -Title: _________________________________________________________________________ - - -Date: _________________________________________________________________________ - diff --git a/LICENSE b/LICENSE index 5b6e959..2c402eb 100644 --- a/LICENSE +++ b/LICENSE @@ -5,4 +5,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index f27c018..e83381f 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,554 @@ - -# oeCloud.io (Open Enterprise for Cloud) +# oe-cloud -oeCloud.io is an architectural blueprint for building enterprise systems leveraging open source framework and powered by automation tools. It is based on [loopback framework](https://github.com/strongloop/loopback). -It provides framework enablers and tool-sets for end to end agile development of enterprise applications. +- [Introduction](#introduction) +- [oe-cloud overall modules](#oe-cloud-overall-modules) +- [oe-cloud Features and functionalities](#oe-cloud-features-and-functionalities) + * [oe-cloud - What it will do](#oe-cloud---what-it-will-do) + * [Usage](#usage) + * [oe-cloud-models](#oe-cloud-models) + + [BaseEntity](#baseentity) + + [ModelDefinition](#modeldefinition) + * [oe-cloud - Initialization](#oe-cloud---initialization) + * [oe-cloud - Loading Models](#oe-cloud---loading-models) + * [oe-cloud - Attaching mixins](#oe-cloud---attaching-mixins) + * [oe-cloud - Boot scripts](#oe-cloud---boot-scripts) + * [oe-cloud - Middlewares](#oe-cloud---middlewares) +- [oeCloud API Documentation](#oecloud-api-documentation) + * [Common Utility API](#common-utility-api) + + [IsBaseEntity(Model)](#isbaseentity-model-) + + [mergeObjects(obj1, obj2)](#mergeobjects-obj1--obj2-) + + [checkDependency(app, modules)](#checkdependency-app--modules-) + + [isInstanceQuery(Model, where)](#isinstancequery-model--where-) + + [getIdValue(Model, data)](#getidvalue-model--data-) + + [idName(Model)](#idname-model-) + * [Application API](#application-api) + + [setServer(server)](#setserver-server-) + + [boot(__dirname, cb)](#boot---dirname--cb-) + + [start()](#start--) + + [addContextField(name, property)](#addcontextfield-name--property-) + + [removeForceId](#removeforceid) + + [observers](#observers) + * [Configurations](#configurations) + * [Remote End point (RestAPI)](#remote-end-point--restapi-) + + [aboutMe](#aboutme) + * [Add fields to BaseEntity or ModelDefinition](#add-fields-to-baseentity-or-modeldefinition) +- [oe-cloud - Difference between old and new](#oe-cloud---difference-between-old-and-new) -## Introduction -oeCloud.io is built on open source technologies, within a standards-based framework. oeCloud.io based components are finely-grained micro-services with well-defined APIs that are integrated using key principles of distributed systems such as : +# Introduction -* Model Driven Architecture -* Everything Personalizable -* Eventual consistency -* Event-driven -* Small footprint – Self Boot strapped -* Cloud Readiness +oeCloud framework has been in development for almost two years and several developers for the first time worked on framework development. It has been adopted by many application developers so far and is being popular within Infosys and EdgeVerve. +It has been demonstrated that application development using oeCloud is fast and developer get many levers to play with when he/she developing with oeCloud - due to framework offering and also JavaScript inherent power. -In addition to the Loopback-provided features, this framework supports the following: -* Dynamic Model creation or Runtime authoring of Models -* Dynamic DataSource creation -* Multi-Tenancy and Tenant/Context-aware Models -* Automatic History and Audit handling -* Data level Access Control +However, there is scope of improvement. +* oeCloud itself has got several features and all of these features are bundled into single monolith node module. This causes trouble for application developer as many of features are included even though they are not needed by application developer. +* Maintainability of oeCloud framework is getting difficult because features cannot be developed/enhanced in isolation. +* Development cycle time is increased due to several CI/CD issues. +* some of the node modules of loopback is forked and maintained by oeCloud team. Example is **loopback-datasource-juggler**. This node module is extensively modified and hence there is no way oeCloud can use latest loopback framework. This is tightly coupled with oeCloud. + -## Prerequisite +To address above concerns, oeCloud is being modularized +* oeCloud application will install oeCloud modules based on requirements. For example if data personalization is really required, he/she will install and use *oe-data-personalization* node module. +* node modules will be created based on feature of oeCloud and each feature thus will have its own development life cycle. +* This way, each feature will live independently of each other and will have separate CI / CD. +* As each feature is separately developed - there will be decoupling with loopback, oeCloud can keep pace with loopback development. -* Nodejs (version > v6.9.1) -* MongoDB +*oe-cloud* is base node module for all oeCloud base application development. -## Getting Started +# oe-cloud overall modules +![Modularization](http://evgit/oec-next/oe-cloud/raw/master/oe-modularization.png) + + +# oe-cloud Features and functionalities + +This is most important project of oeCloud. This module needs to be required in application's server.js. +Below are responsibilities of oe-cloud + +## oe-cloud - What it will do + +* Define BaseEntity Model +* load modules described in app-list.json in sequence. +* call init method of each module described in app-list.json +* provide API for other modules to defined observer hook - and this module will call those hooks when application is loaded +* ensure execution of loading and attaching mixin/middleware/boot script in each module of app-list.json while maintaining sequence +* by default, oe-cloud will attach all the mixins defined in node module to BaseEntity unless it is specified otherwise. +* ensure of loading of models defined in app-list module +* expose *loopback like* APIs for application developer (eg app.boot(), app.start() etc) + + +## Usage + +Typically, following code can be written in oeCloud application's server/server.js file -### Install this node module ``` -git clone https://github.com/EdgeVerve/oe-cloud.git -cd oe-cloud -npm install --no-optional +var oecloud = require('oe-cloud'); + +oecloud.boot(__dirname, function(err){ + oecloud.start(); +}) + ``` -### Running +Above code should able to start application. you don't have to do require of loopback or any of it's component. +Typical app-list.json, which would be part of application would look like + +``` +[ + { + "path": "oe-cloud", + "enabled": true + }, + { + "path": "oe-module", + "enabled": true + }, + { + "path": "oe-common-mixins", + "enabled": true + }, + { + "path": "oe-cache", + "enabled": true + }, + { + "path": "oe-personalization", + "enabled": true + }, + { + "path": "oe-validation", + "enabled": true + }, + { + "path": "oe-service-personalization", + "enabled": true + }, + { + "path": "./", + "enabled": true + } +] +``` + +## oe-cloud-models + +### BaseEntity + +BaseEntity model is part of this module. Mixins in app-list.json modules will be attached to BaseEntity model - as described below. Most of the times, application models will be directly or indirectly derived from BaseEntity. +Therefore, all functionalities of BaseEntity model is available to derived model. + +### ModelDefinition + +ModelDefinition model stores metadata of all models in database. It will further opens up REST end point for client to get metadata of models and also can be used for runtime creation of new model. + -Run node on command line. +## oe-cloud - Initialization + +**Note : There is possibility of loading **oe Modules** automatically. But having explicitly specified in app-list.json can be better idea and there is no ambiguity.** + +oe-cloud interacts with node modules defined in app-list.json in very specific, predefined way. Below are specifications + +* if your node module needs to be loaded, you MUST include that in app-list.json file. For example above oe-data-personsalization module will be loaded from /node_modules/oe-data-personalization folder +* oe-cloud will call .init() method - IF DEFINED - after loading the module. Typically, you can create index.js file which will be loaded and init() will be called. +* If init() is not defined, and you are exporting function as below, that function will be called ``` -node . +module.exports = function(app){ + // oe-cloud will call this function when it loads the module and when init() is not defined. + // app object will be passed which has got much of information to manipulate + // you can set observer hook as below + + app.observe('loaded', function(ctx, next){ + //ctx.app will have app handle + return next(); + }); +} ``` -Browse all oeCloud.io models in browser +## oe-cloud - Loading Models + +oe-cloud can load models defined in app-list.json's node_module. + +* you need to define model-config.json in your modules server and have path specified for models as below. +* If you don't specifiy model-config.json, application needs to specify path to models folder in app's model-config.json. +* You don't have to define Models in model-config.json. Only path is enough. However, in that case, application's model-config.json must have model entry otherwise, model will not be attached to datasrouce. +* If in node_module's model-config.json, if you have defined dataSource (as shown below), it must exists otherwise error will be thrown. +* If in node_module's model-config.json, you have defined dataSource or public property, these properties can be overriden in application's model-config.json. So in below example, application can change datasource of **MyModel** by having entry in it's own model-confg.json. ``` -http://localhost:3000/explorer +{ + "_meta": { + "sources": [ + "../common/models" + ], + "mixins": [ + "../common/mixins" + ] + }, + "MyModel" :{ + "public" : true, + "dataSource": "db", + } +} + ``` -## Migration from oe-cloud ^0.9.x to ^1.1.x -* For Issue: Cannot merge values of incompatible types for the option `remoting.context`, Please change all the modules config files(config.\*.js and config.\*.json ) listed in app-list.json, change `remoting.context` to `false`. +In above model-config, MyModel will be created as public model. However, definition of the model (.json) should be located in common/models folder of module. so in case of data personalization node module, my-model.json file should be present in /node_modules/oe-data-personalization/commmon/models folder + +## oe-cloud - Attaching mixins + + +* oeCloud will load mixins of node_modules defined in app-list.json +* as in above case, mixins can be defined in node_modules's common/mixins folder. mixin's .js file will be loaded and attached to BaseEntity model by default. +* thus, when this node module is loaded, all mixins defined in this module will be attached to BaseEntity and all models derived from BaseEntity will have same behavior. +* Application developer can switch off/on mixins selectively by having entry such as following in app-list.json + +```javaScript + "OeSomeModule" : { + "enable" : true, + "mixins" : { + "MixinA" : false, + "MixinB" : true + } + } +``` +* Above example will change default behavior. +* you can also selectively ON/OFF the mixin attachments by calling **addModuleMixinsToBaseEntity** API as below. This can be important if you have to have some mixins from other dependent module. + +``` +var oecloud = require('oe-cloud'); +oecloud.addModuleMixinsToBaseEntity('oe-data-personalization', false); +oecloud.boot(__dirname, function(err){ + oecloud.start(); +}) + +``` +As shownn above, data personalization mixins will not be attached to BaseEntity by default. In this scenario, you will have to explicitly attach mixin with another API **attachMixinsToBaseEntity** + +``` +var oecloud = require('oe-cloud'); +oecloud.addModuleMixinsToBaseEntity('oe-data-personalization', false); +// adding mixin with name of mixin explicitly. Thus not all mixins in data personalization modules are attached. +oecloud.attachMixinsToBaseEntity('DataPersonalizationMixin'); + +oecloud.boot(__dirname, function(err){ + oecloud.start(); +}) + +``` + +* you can also set mixin value in app-list.json to set default behavior of mixin +``` + , + { + "path": "oe-validation", + "ModelValidationMixin" : false, + "enabled": true + }, +``` + +## oe-cloud - Boot scripts + +* app-list.json's node module can have boot scripts defined in server/boot folder +* oeCloud executes boot scripts in order same as modules are defined in app-list.json. +* Thererfore, boot scripts in first module of app-list.json will be executed first. +* However in each module, boot scripts are executed in alphabetical sequence. Therefore, if in module, there are two boot scripts, a.js and b.js, a.js will be executed first. +* You will not interfere the order of boot scripts in other modules. +* Each boot script will take two parameters, app and callback. if you don't use callback, then boot script will be executed in sync way. + + +```javaScript +// boot script with callback +module.exports = function(app, cb){ + + // must call cb() otherwise next boot script will not be executed + // should throw error if needed like + // return cb(new Error("something went wrong"); +} + +// boot script without callback +module.exports = function(app){ + + // next boot script will be executed when function returns +} + +``` + + +## oe-cloud - Middlewares + +oeCloud would merge all middlewares in all modules defined in app-list.json and ensure execution of middleware. Each middleware should have entry in respective module's middleware.json file. + +Middleware should have entry such as below in middleware.json of module's server folder. + +```javaScript +session:before": { + "./middleware/populate-context-from-scope": {}, + "./middleware/populate-context-from-headers": {} + } +``` + +and middleware should have code such as below. This file should reside in server/middleware folder of your node_module. + +```javaScript +module.exports = function (options) { + return function(req, res, next) { + + return next(); // must call next() otherwise next middlware will not be executed and system will hang. + } + +} +``` + + +# oeCloud API Documentation + +## Common Utility API + +There are simple utility functions which can be used in your module + +### IsBaseEntity(Model) + + +This utility function checks if given Model is derived from BaseEntity. This will be useful many times in programming. Below is code snippet. + +```javaScript +const oecloudUtil = require('oecloud/lib/util'); +const loopback = require('loopback'); +var customerModel = loopback.findModel('Customer') +console.log("Customer Model is derived from BaseEntity ", oecloudUtil.isBaseEntity(customerModel)); +``` + +### mergeObjects(obj1, obj2) + +This function merge two objects. Below is usage and examples. Developer can use lodash libray for that also. + +```javaScript +const util = require('oecloud/lib/util'); + +var o1 = { a : "a" }; +var o2 = { b : "b" }; +var o = util.mergeObjects(o1, o2); +// { a: "a", b : "b" } + +var o1 = { a : "a" , c : { d : { e : "e" } } }; +var o2 = { b : "b" }; +util.mergeObjects(o1, o2); +// { a : "a" , b: "b", c : { d : { e : "e" } } }; - Ex: older config - - ``` json - "context": { - "enableHttpContext": true - }, - ``` - new config +var o1 = { a : "a" , c : { d : { e : "e" } } }; +var o2 = { a : "b" }; +util.mergeObjects(o1, o2); +// { a : "b" , c : { d : { e : "e" } } }; - ``` json - "context": false, - ``` -Or if you face a similar config mismatch issue get the value from node_modules/oe-cloud/config.json and try to have the same value in all modules listed in app-list.json +var o1 = { a : [1,2,3] , c : { d : { e : "e" } } }; +var o2 = { a : [2,4,5] }; +util.mergeObjects(o1, o2); +// { a : [1,2,3,4,5] , c : { d : { e : "e" } } }; -## More information +``` + +### checkDependency(app, modules) + +This function checks if all modules in app-list satisfies depenency. + + +### isInstanceQuery(Model, where) + +This function checks if this is instance based query where primary key of Model is part of where clause. For nesting, it looks only and clause. + +```javaScript +const utils = require('oecloud/lib/util'); +utils.isInstanceQuery(newCustomerModel, { where: { name: 'x' } }); // false +utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { id: 1 }] } }); // true +utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { age: 1 }, { and: [{ id: 1 }, {age : 10}]}] } }); // true +utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { age: 1 }, { or: [{ id: 1 }, {age : 10}]}] } }); // false +``` + + +### getIdValue(Model, data) + +This function gives you id value in your data. + +```javaScript +const utils = require('oecloud/lib/util'); +var id = utils.getIdValue(CustomerModel, { id: 10, name: "A" }); // id = 10 +var id = utils.getIdValue(CustomerModel, { name: "A" }); // id = undefined +``` + +### idName(Model) + +Returns id field name of the model. By default "id" + +```javaScript +const utils = require('oecloud/lib/util'); +var idName = idName(CustomerModel); // returns "id" +``` + +## Application API + +These are the APIs are made available on application object of oeCloud. These APIs mostly should be called before boot. + +### setServer(server) + +This function will allow application to set http or https server. This way, creating of server can be controlled by application. By default oeCloud will create http server. + + +```javaScript +const oecloud = require('oe-cloud'); + +var server = require('http').createServer(oecloud); +oecloud.setServer(server); +``` + +### boot(__dirname, cb) + +This function will boot the application. It will do following typical things, but not limited to that. + +* Load all models, mixins, middleware, boot scripts etc from modules in app-list.json +* Merge configuratino like model-config.json. +* Create ModelDefinition entries for all the models of system. Also loads and creates model based on ModelDefinition. +* executes boot scripts in sequence on app-list. Scripts in given module are executed by order of name alphabatically. Therefore, a.js will be executed before b.js. + + +```javaScript +const oecloud = require('oe-cloud'); +oecloud.boot(__dirname, function(err){ + orcloud.start(); +}) +``` + + +### start() + +This function will start web server and starts listening on PORT. Default port is 3000, or it can be set by PORT environment variable. This function will emit application start event 'started' that application can listen on. + + +```javaScript +const oecloud = require('oe-cloud'); +oecloud.start() +oecloud.once('started', function(){ + // do something. +}); +``` -Please visit [oeCloud.io](https://www.oecloud.io) -## License -The project is licensed under MIT License, See [LICENSE](./LICENSE) for more details. +### addContextField(name, property) + +This function adds context parameter to AccessToken. That way, if this field is set during creation of access token, it will be made avaialble throughout the application context. + +```javaScript +const oecloud = require('oe-cloud'); +oecloud.addContextField('tenantId', { + type: "string" +}); + +accessToken.evObserve("before save", function (ctx, next) { + ctx.instance.tenantId = findTenantIdForUser(ctx.instance.userId); + return next(); +}); + + +// after above code is done during start up of application, "tenantId" is available throughout context + +customerModel.evObserve("access", function(ctx, next){ + var context = ctx.options.ctx; + + assert (context.tenantId) +}) + +customerModel.beforeRemote("*", function(req, res.next){ + var context = req.callContext; + + assert (context.tenantId) +}) + +``` + +### removeForceId + +This function on app object can be used to remove ForceId of models which are not derived from BaseEntity. **User, Role and RoleMapping** are typical models, which are not derived from **BaseEntity** and you want to remove ForceId setting. + +```javascript +var app = require('oe-cloud'); + app.removeForceId('User'); + app.removeForceId('Role'); + app.removeForceId('RoleMapping'); +``` +However, please note that, **by default** above code is executed as part of boot script. Meaning, by default, ForceId is deisabled for User, Role and RoleMapping models. Therefore, you can create User/Role/Rolemapping data by passing id field explicitly. +If you want to disable this, you can use **disableForceIdForUserModels** setting to true in config.json. + +**About ForceId** : In loopback 3, ForceId setting is done on model which is **true** by default. In this case, user/programmer cannot create a record on model by passing his/her own id. Id is always generated by loopback. To disable this setting, you can use removeForceId call. + + + +### observers + +As a programmer, you can implement oeCloud observer hooks. The most important hook is 'loaded'. you can use code similar to following to create the observer hook. 'loaded' observer is executed when all modules in app-list.json is loaded. you can change some configuration, add or remove mixins before boot gets executed. + +```javaScript + app.observe('loaded', function(ctx, next){ + //ctx.app will have app handle + return next(); + }); +``` + +## Configurations + +| Name | Default | comment | +| ------ | ------ | ------- | +| disableAboutMe | false | AboutMe API is exposed and any authenticated user can call /api/users/aboutme to know about logged in user | +| disableDefaultAuth | false | Authentication mechanism is enabled by default. you can set this value to true to disable it | +| enableAuthCookie | false | You can enable Auth cookie to be sent when user login using /api/users/login API. By default it is disabled | +| enableForceIdForUserModels | false | ForceId is disabled for Users/Role/RoleMapping models. You can enable it to keep default loopback behavior | + + +## Remote End point (RestAPI) + +### aboutMe + +Logged in user can know about him/herself by calling this API. API signature is shown below. If auth cookie is enabled, you don't have to pass access_token if you are using browser. + +``` +GET +http://localhost:3000/api/users/aboutme?access_token= +``` + + +## Add fields to BaseEntity or ModelDefinition +* If you want to add (change) schema of BaseEntity or ModelDefinition, you can do so by provided API as shown below. +* Typically, you will write observer hook and call method provided by app. For example, below, **_version** property is added to ModelDefinition model. +* Also you can see BaseEntity will start having autoscope field with 'tenantId' as value. + +``` +var app = require('oe-cloud'); +app.observe('loaded', function(ctx, next){ + app.addSettingsToModelDefinition({properties : {_versioning : {type : "boolean", default : false}}}); + app.addSettingsToModelDefinition({properties : {HistoryMixin : {type : "boolean", default: false}}}); + + app.addSettingsToBaseEntity({autoscope:["tenantId"]}); + + return next(); +}) +``` -## Contributing -We welcome contributions. Some of the best ways to contribute are to try things out, file bugs, and join in design conversations. +# oe-cloud - Difference between old and new -### [How to contribute](./CONTRIBUTION.md) +| Feature | Exisiting | Proposed | +| ------ | ------ | ------- | +| Custom Types Register-Email & timestamp | Data Source juggler Change | Model Builder Wrapper [oe-cloud](http://evgit/oec-next/oe-cloud) | +| **after access** observer notification | DAO Change | Data Source juggler Wrapper [oe-cloud](http://evgit/oec-next/oe-cloud) | +| app-list.json handling | server.js | [oe-cloud](http://evgit/oec-next/oe-cloud) | +| EvObserver | Mixin in file | [oe-cloud](http://evgit/oec-next/oe-cloud) | +| Audit Field | Mixin in file | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | +| Versioning | Mixin in file | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | +| History | Mixin | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins)| +| Idempotency | Mixin+DAO | Not done | +| Soft Delete | Mixin + DAO | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | +| Validations | Mixin | [oe-validation](http://evgit/oec-next/oe-validation) | +| Expression Support | Mixin | [oe-expression](http://evgit/oec-next/oe-expression) | +| Model Composite - Implicit and Explicit | DAO Change | DAO Wrapper [oe-model-composite](http://evgit/oec-next/oe-model-composite) | +| Data Personalization | Mixin | [oe-personalization](http://evgit/oec-next/oe-personalization) | +| Service Personalization | Mixin+Boot | Boot [oe-service=personalization](http://evgit/oec-next/oe-service-personalization) | +| Cachinge | Mixin+DAO | DAO Wrapper [oe-cache](http://evgit/oec-next/oe-cache) | -### [Report an issue](https://github.com/EdgeVerve/oe-cloud/issues) diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md deleted file mode 100644 index edd9f61..0000000 --- a/STYLEGUIDE.md +++ /dev/null @@ -1,1671 +0,0 @@ - - -# oeCloud.io JavaScript Style Guide - -## Table of Contents - - 1. [Types](#types) - 1. [Objects](#objects) - 1. [Arrays](#arrays) - 1. [Strings](#strings) - 1. [Functions](#functions) - 1. [Properties](#properties) - 1. [Variables](#variables) - 1. [Hoisting](#hoisting) - 1. [Comparison Operators & Equality](#comparison-operators--equality) - 1. [Blocks](#blocks) - 1. [Comments](#comments) - 1. [Whitespace](#whitespace) - 1. [Commas](#commas) - 1. [Semicolons](#semicolons) - 1. [Type Casting & Coercion](#type-casting--coercion) - 1. [Naming Conventions](#naming-conventions) - 1. [Constructors](#constructors) - 1. [Events](#events) - 1. [Requires](#requires) - 1. [Callbacks](#callbacks) - 1. [Try Catch](#try-catch) - 1. [Asynchronous Code](#asynchronous-code) - 1. [Resources](#resources) - 1. [License](#license) - -## Types - - - **Primitives**: When you access a primitive type you work directly on its value. - - + `string` - + `number` - + `boolean` - + `null` - + `undefined` - - ```javascript - var foo = 1; - var bar = foo; - - bar = 9; - - console.log(foo, bar); // => 1, 9 - ``` - - **Complex**: When you access a complex type you work on a reference to its value. - - + `object` - + `array` - + `function` - - ```javascript - var foo = [1, 2]; - var bar = foo; - - bar[0] = 9; - - console.log(foo[0], bar[0]); // => 9, 9 - ``` - -**[⬆ back to top](#table-of-contents)** - -## Objects - - - Use the literal syntax for object creation. - - ```javascript - // bad - var item = new Object(); - - // good - var item = {}; - ``` - - - Use readable synonyms in place of reserved words. - - ```javascript - // bad - var superman = { - class: 'alien' - }; - - // bad - var superman = { - klass: 'alien' - }; - - // good - var superman = { - type: 'alien' - }; - ``` - -**[⬆ back to top](#table-of-contents)** - -## Arrays - - - Use the literal syntax for array creation. - - ```javascript - // bad if the array length is not specified - var items = new Array(); - - // good - var items = []; - ``` - Performance Benchmark : [new Array vs Literal](https://jsperf.com/new-array-vs-literal/15) - - - Use Array#push instead of direct assignment to add items to an array. - - ```javascript - var someStack = []; - - - // bad - someStack[someStack.length] = 'abracadabra'; - - // good - someStack.push('abracadabra'); - ``` - - - When you need to copy an array use Array#slice. [jsPerf](http://jsperf.com/converting-arguments-to-an-array/7) - - ```javascript - var len = items.length; - var itemsCopy = []; - var i; - - // bad - for (i = 0; i < len; i++) { - itemsCopy[i] = items[i]; - } - - // good - itemsCopy = items.slice(); - ``` - - - To convert an array-like object to an array, use Array#slice. - - ```javascript - function trigger() { - var args = Array.prototype.slice.call(arguments); - ... - } - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Strings - - - Use single quotes `''` for strings. - - ```javascript - // bad - var name = "Bob Parr"; - - // good - var name = 'Bob Parr'; - - // bad - var fullName = "Bob " + this.lastName; - - // good - var fullName = 'Bob ' + this.lastName; - ``` - - - Strings longer than 100 characters should be written across multiple lines using string concatenation. - - Note: If overused, long strings with concatenation could impact performance. [jsPerf](http://jsperf.com/ya-string-concat). - - ```javascript - // bad - var errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; - - // bad - var errorMessage = 'This is a super long error that was thrown because \ - of Batman. When you stop to think about how Batman had anything to do \ - with this, you would get nowhere \ - fast.'; - - // good - var errorMessage = 'This is a super long error that was thrown because ' + - 'of Batman. When you stop to think about how Batman had anything to do ' + - 'with this, you would get nowhere fast.'; - ``` - - - When programmatically building up a string, use Array#join instead of string concatenation. [jsPerf](http://jsperf.com/string-vs-array-concat/2) - - ```javascript - var items; - var messages; - var length; - var i; - - messages = [{ - state: 'success', - message: 'This one worked.' - }, { - state: 'success', - message: 'This one worked as well.' - }, { - state: 'error', - message: 'This one did not work.' - }]; - - length = messages.length; - - // bad - function inbox(messages) { - items = '
    '; - - for (i = 0; i < length; i++) { - items += '
  • ' + messages[i].message + '
  • '; - } - - return items + '
'; - } - - // good - function inbox(messages) { - items = []; - - for (i = 0; i < length; i++) { - // use direct assignment in this case because we're micro-optimizing. - items[i] = '
  • ' + messages[i].message + '
  • '; - } - - return '
      ' + items.join('') + '
    '; - } - ``` -- Use [] instead of charAt for index access in strings - - ```javascript - - // bad - const str = "Foo"; - const char = str.charAt(1); - - // good - const str = "Foo"; - const char = str[1]; - - ``` - Performance Benchmark [[] vs charAt] (https://esbench.com/bench/579609a0db965b9a00965b9e) - -**[⬆ back to top](#table-of-contents)** - - -## Functions - - - Function expressions: - - ```javascript - // anonymous function expression - var anonymous = function () { - return true; - }; - - // named function expression - var named = function named() { - return true; - }; - - // immediately-invoked function expression (IIFE) - (function () { - console.log('Welcome to the Internet. Please follow me.'); - }()); - ``` - - - Never declare a function in a non-function block (if, while, etc). Use function expression instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears. - - **Note:* - ECMA-262 defines a `block` as a list of statements. A function declaration is not a statement. [Read ECMA-262's note on this issue](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=97). - - ```javascript - // bad - if (currentUser) { - function test() { - console.log('Nope.'); - } - } - - // good - var test; - if (currentUser) { - test = function test() { - console.log('Yup.'); - }; - } - ``` - - - Never name a parameter `arguments`. This will take precedence over the `arguments` object that is given to every function scope. - - ```javascript - // bad - function nope(name, options, arguments) { - // ...stuff... - } - - // good - function yup(name, options, args) { - // ...stuff... - } - ``` - -**[⬆ back to top](#table-of-contents)** - - - -## Properties - - - Use dot notation when accessing properties. - - ```javascript - var luke = { - jedi: true, - age: 28 - }; - - // bad - var isJedi = luke['jedi']; - - // good - var isJedi = luke.jedi; - ``` - - - Use subscript notation `[]` when accessing properties with a variable. - - ```javascript - var luke = { - jedi: true, - age: 28 - }; - - function getProp(prop) { - return luke[prop]; - } - - var isJedi = getProp('jedi'); - ``` - - Performance Benchmark : [dot-vs-square-brackets](https://jsperf.com/dot-vs-square-brackets) - -**[⬆ back to top](#table-of-contents)** - - -## Variables - - - Always use `var` to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that. - - ```javascript - // bad - superPower = new SuperPower(); - - // good - var superPower = new SuperPower(); - ``` - - - Use one `var` declaration per variable. - It's easier to add new variable declarations this way, and you never have - to worry about swapping out a `;` for a `,` or introducing punctuation-only - diffs. - - ```javascript - // bad - var items = getItems(), - goSportsTeam = true, - dragonball = 'z'; - - // bad - // (compare to above, and try to spot the mistake) - var items = getItems(), - goSportsTeam = true; - dragonball = 'z'; - - // good - var items = getItems(); - var goSportsTeam = true; - var dragonball = 'z'; - ``` - - - Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables. - - ```javascript - // bad - var i, len, dragonball, - items = getItems(), - goSportsTeam = true; - - // bad - var i; - var items = getItems(); - var dragonball; - var goSportsTeam = true; - var len; - - // good - var items = getItems(); - var goSportsTeam = true; - var dragonball; - var length; - var i; - ``` - - - Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues. - - ```javascript - // bad - function () { - test(); - console.log('doing stuff..'); - - //..other stuff.. - - var name = getName(); - - if (name === 'test') { - return false; - } - - return name; - } - - // good - function () { - var name = getName(); - - test(); - console.log('doing stuff..'); - - //..other stuff.. - - if (name === 'test') { - return false; - } - - return name; - } - - // bad - unnecessary function call - function () { - var name = getName(); - - if (!arguments.length) { - return false; - } - - this.setFirstName(name); - - return true; - } - - // good - function () { - var name; - - if (!arguments.length) { - return false; - } - - name = getName(); - this.setFirstName(name); - - return true; - } - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Hoisting - - - Variable declarations get hoisted to the top of their scope, but their assignment does not. - - ```javascript - // we know this wouldn't work (assuming there - // is no notDefined global variable) - function example() { - console.log(notDefined); // => throws a ReferenceError - } - - // creating a variable declaration after you - // reference the variable will work due to - // variable hoisting. Note: the assignment - // value of `true` is not hoisted. - function example() { - console.log(declaredButNotAssigned); // => undefined - var declaredButNotAssigned = true; - } - - // The interpreter is hoisting the variable - // declaration to the top of the scope, - // which means our example could be rewritten as: - function example() { - var declaredButNotAssigned; - console.log(declaredButNotAssigned); // => undefined - declaredButNotAssigned = true; - } - ``` - - - Anonymous function expressions hoist their variable name, but not the function assignment. - - ```javascript - function example() { - console.log(anonymous); // => undefined - - anonymous(); // => TypeError anonymous is not a function - - var anonymous = function () { - console.log('anonymous function expression'); - }; - } - ``` - - - Named function expressions hoist the variable name, not the function name or the function body. - - ```javascript - function example() { - console.log(named); // => undefined - - named(); // => TypeError named is not a function - - superPower(); // => ReferenceError superPower is not defined - - var named = function superPower() { - console.log('Flying'); - }; - } - - // the same is true when the function name - // is the same as the variable name. - function example() { - console.log(named); // => undefined - - named(); // => TypeError named is not a function - - var named = function named() { - console.log('named'); - } - } - ``` - - - Function declarations hoist their name and the function body. - - ```javascript - function example() { - superPower(); // => Flying - - function superPower() { - console.log('Flying'); - } - } - ``` - - - For more information refer to [JavaScript Scoping & Hoisting](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting) by [Ben Cherry](http://www.adequatelygood.com/). - -**[⬆ back to top](#table-of-contents)** - - - -## Comparison Operators & Equality - - - Use `===` and `!==` over `==` and `!=`. - - Conditional statements such as the `if` statement evaluate their expression using coercion with the `ToBoolean` abstract method and always follow these simple rules: - - + **Objects** - evaluate to **true** - + **Undefined** - evaluates to **false** - + **Null** - evaluates to **false** - + **Booleans** - evaluate to **the value of the boolean** - + **Numbers** - evaluate to **false** - if **+0, -0, or NaN**, otherwise **true** - + **Strings** - evaluate to **false** - if an empty string `''`, otherwise **true** - - ```javascript - if ([0]) { - // true - // An array is an object, objects evaluate to true - } - ``` - - - Use shortcuts. - - ```javascript - // bad - if (name !== '') { - // ...stuff... - } - - // good - if (name) { - // ...stuff... - } - - // bad - if (collection.length > 0) { - // ...stuff... - } - - // good - if (collection.length) { - // ...stuff... - } - ``` - - - For more information see [Truth Equality and JavaScript](http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108) by Angus Croll. - -**[⬆ back to top](#table-of-contents)** - - -## Blocks - - - Use braces with all multi-line blocks. - - ```javascript - // bad - if (test) - return false; - - // good - if (test) return false; - - // good - if (test) { - return false; - } - - // bad - function () { return false; } - - // good - function () { - return false; - } - ``` - - - If you're using multi-line blocks with `if` and `else`, put `else` on the same line as your - `if` block's closing brace. - - ```javascript - // bad - if (test) { - thing1(); - thing2(); - } - else { - thing3(); - } - - // good - if (test) { - thing1(); - thing2(); - } else { - thing3(); - } - ``` - -**[⬆ back to top](#table-of-contents)** - -## Comments - - - Use `/* - ... */` for multi-line comments. Include a description, specify types and values for all parameters and return values. - - ```javascript - // bad - // make() returns a new element - // based on the passed in tag name - // - // @param {String} tag - // @return {Element} element - function make(tag) { - - // ...stuff... - - return element; - } - - // good - /** - - make() returns a new element - - based on the passed in tag name - * - - @param {String} tag - - @return {Element} element - */ - function make(tag) { - - // ...stuff... - - return element; - } - ``` - - - Use `//` for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment. - - ```javascript - // bad - var active = true; // is current tab - - // good - // is current tab - var active = true; - - // bad - function getType() { - console.log('fetching type...'); - // set the default type to 'no type' - var type = this.type || 'no type'; - - return type; - } - - // good - function getType() { - console.log('fetching type...'); - - // set the default type to 'no type' - var type = this.type || 'no type'; - - return type; - } - ``` - - - Prefixing your comments with `FIXME` or `TODO` helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions are `FIXME -- need to figure this out` or `TODO -- need to implement`. - - - Use `// FIXME:` to annotate problems. - - ```javascript - function Calculator() { - - // FIXME: shouldn't use a global here - total = 0; - - return this; - } - ``` - - - Use `// TODO:` to annotate solutions to problems. - - ```javascript - function Calculator() { - - // TODO: total should be configurable by an options param - this.total = 0; - - return this; - } - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Whitespace - - - Use soft tabs set to 2 spaces. - - ```javascript - // bad - function () { - ∙∙∙∙var name; - } - - // bad - function () { - ∙var name; - } - - // good - function () { - ∙∙var name; - } - ``` - - - Place 1 space before the leading brace. - - ```javascript - // bad - function test(){ - console.log('test'); - } - - // good - function test() { - console.log('test'); - } - - // bad - dog.set('attr',{ - age: '1 year', - breed: 'Bernese Mountain Dog' - }); - - // good - dog.set('attr', { - age: '1 year', - breed: 'Bernese Mountain Dog' - }); - ``` - - - Place 1 space before the opening parenthesis in control statements (`if`, `while` etc.). Place no space before the argument list in function calls and declarations. - - ```javascript - // bad - if(isJedi) { - fight (); - } - - // good - if (isJedi) { - fight(); - } - - // bad - function fight () { - console.log ('Swooosh!'); - } - - // good - function fight() { - console.log('Swooosh!'); - } - ``` - - - Set off operators with spaces. - - ```javascript - // bad - var x=y+5; - - // good - var x = y + 5; - ``` - - - End files with a single newline character. - - ```javascript - // bad - (function (global) { - // ...stuff... - })(this); - ``` - - ```javascript - // bad - (function (global) { - // ...stuff... - })(this);↵ - ↵ - ``` - - ```javascript - // good - (function (global) { - // ...stuff... - })(this);↵ - ``` - - - Use indentation when making long method chains. Use a leading dot, which emphasizes that the line is a method call, not a new statement. If you wish to use multiple methods in the same line, please group it logically rather than breaking it arbitrarily. - - ```javascript - // bad - $('#items').find('.selected').highlight().end().find('.open').updateCount(); - - // bad - $('#items'). - find('.selected'). - highlight(). - end(). - find('.open'). - updateCount(); - - // good - $('#items') - .find('.selected') - .highlight() - .end() - .find('.open') - .updateCount(); - - // bad - var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) - .attr('width', (radius + margin) - 2).append('svg:g') - .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') - .call(tron.led); - - // good - var leds = stage.selectAll('.led') - .data(data) - .enter().append('svg:svg') - .classed('led', true) - .attr('width', (radius + margin) - 2) - .append('svg:g') - .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') - .call(tron.led); - ``` - - - Leave a blank line after blocks and before the next statement - - ```javascript - // bad - if (foo) { - return bar; - } - return baz; - - // good - if (foo) { - return bar; - } - - return baz; - - // bad - var obj = { - foo: function () { - }, - bar: function () { - } - }; - return obj; - - // good - var obj = { - foo: function () { - }, - - bar: function () { - } - }; - - return obj; - ``` - - -**[⬆ back to top](#table-of-contents)** - -## Commas - - - Leading commas: **Nope.** - - ```javascript - // bad - var story = [ - once - , upon - , aTime - ]; - - // good - var story = [ - once, - upon, - aTime - ]; - - // bad - var hero = { - firstName: 'Bob' - , lastName: 'Parr' - , heroName: 'Mr. Incredible' - , superPower: 'strength' - }; - - // good - var hero = { - firstName: 'Bob', - lastName: 'Parr', - heroName: 'Mr. Incredible', - superPower: 'strength' - }; - ``` - - - Additional trailing comma: **Nope.** - - ```javascript - // bad - var hero = { - firstName: 'Kevin', - lastName: 'Flynn', - }; - - var heroes = [ - 'Batman', - 'Superman', - ]; - - // good - var hero = { - firstName: 'Kevin', - lastName: 'Flynn' - }; - - var heroes = [ - 'Batman', - 'Superman' - ]; - ``` - - > Edition 5 clarifies the fact that a trailing comma at the end of an ArrayInitialiser does not add to the length of the array. This is not a semantic change from Edition 3 but some implementations may have previously misinterpreted this. - - -**[⬆ back to top](#table-of-contents)** - - -## Semicolons - - - **Yup.** - - ```javascript - // bad - (function () { - var name = 'Skywalker' - return name - })() - - // good - (function () { - var name = 'Skywalker'; - return name; - })(); - - // good (guards against the function becoming an argument when two files with IIFEs are concatenated) - ;(function () { - var name = 'Skywalker'; - return name; - })(); - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Type Casting & Coercion - - - Use toString instead of coercion or String constructor. Performance Benchmark : [string-coercion-vs-tostring](https://jsperf.com/string-coercion-vs-tostring/2) - - If performing type coercion, do it at the beginning of the statement. - - Strings: - - ```javascript - // => this.reviewScore = 9; - - // bad - var totalScore = this.reviewScore + ''; - - // good - var totalScore = '' + this.reviewScore; - - // bad - var totalScore = '' + this.reviewScore + ' total score'; - - // good - var totalScore = this.reviewScore + ' total score'; - ``` - - - Use toString instead of coercion - - - Use `parseInt` for Numbers and always with a radix for type casting. - - ```javascript - var inputValue = '4'; - - // bad - var val = new Number(inputValue); - - // bad - var val = +inputValue; - - // bad - var val = inputValue >> 0; - - // bad - var val = parseInt(inputValue); - - // good - var val = Number(inputValue); - - // good - var val = parseInt(inputValue, 10); - ``` - - - If for whatever reason you are doing something wild and `parseInt` is your bottleneck and need to use Bitshift for [performance reasons](http://jsperf.com/coercion-vs-casting/3), leave a comment explaining why and what you're doing. - - ```javascript - // good - /** - - parseInt was the reason my code was slow. - - Bitshifting the String to coerce it to a - - Number made it a lot faster. - */ - var val = inputValue >> 0; - ``` - - - **Note:* - Be careful when using bitshift operations. Numbers are represented as [64-bit values](http://es5.github.io/#x4.3.19), but Bitshift operations always return a 32-bit integer ([source](http://es5.github.io/#x11.7)). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Largest signed 32-bit Int is 2,147,483,647: - - ```javascript - 2147483647 >> 0 //=> 2147483647 - 2147483648 >> 0 //=> -2147483648 - 2147483649 >> 0 //=> -2147483647 - ``` - - - Booleans: - - ```javascript - var age = 0; - - // bad - var hasAge = new Boolean(age); - - // good - var hasAge = Boolean(age); - - // good - var hasAge = !!age; - ``` - -**[⬆ back to top](#table-of-contents)** - -## Naming Conventions - - - Avoid single letter names. Be descriptive with your naming. - - ```javascript - // bad - function q() { - // ...stuff... - } - - // good - function query() { - // ..stuff.. - } - ``` - - - Use camelCase when naming objects, functions, and instances. - - ```javascript - // bad - var OBJEcttsssss = {}; - var this_is_my_object = {}; - var o = {}; - function c() {} - - // good - var thisIsMyObject = {}; - function thisIsMyFunction() {} - ``` - - - Use PascalCase when naming constructors or classes. - - ```javascript - // bad - function user(options) { - this.name = options.name; - } - - var bad = new user({ - name: 'nope' - }); - - // good - function User(options) { - this.name = options.name; - } - - var good = new User({ - name: 'yup' - }); - ``` - - - Do not use trailing or leading underscores. - - ```javascript - // bad - this.__firstName__ = 'Panda'; - this.firstName_ = 'Panda'; - this._firstName = 'Panda'; - - // good - this.firstName = 'Panda'; - ``` - - > Why? JavaScript does not have the concept of privacy in terms of properties or methods. Although a leading underscore is a common convention to mean “private”, in fact, these properties are fully public, and as such, are part of your public API contract. This convention might lead developers to wrongly think that a change won't count as breaking, or that tests aren't needed. tl;dr: if you want something to be “private”, it must not be observably present. - - - - Don't save references to this. Use Function#bind. - - ```javascript - // bad - function () { - var self = this; - return function () { - console.log(self); - }; - } - - // bad - function () { - var that = this; - return function () { - console.log(that); - }; - } - - // bad - function () { - var _this = this; - return function () { - console.log(_this); - }; - } - - // good - function () { - return function () { - console.log(this); - }.bind(this); - } - ``` - - - Name your functions. This is helpful for stack traces. - - ```javascript - // bad - var log = function (msg) { - console.log(msg); - }; - - // good - var log = function log(msg) { - console.log(msg); - }; - ``` - - - If your file exports a single class, your filename should be exactly the name of the class. - ```javascript - // file contents - function CheckBox { - // ... - } - module.exports = CheckBox; - - // in some other file - // bad - var CheckBox = require('./checkBox'); - - // bad - var CheckBox = require('./check_box'); - - // good - var CheckBox = require('./CheckBox'); - ``` - -**[⬆ back to top](#table-of-contents)** - -## Constructors - - - Assign methods to the prototype object, instead of overwriting the prototype with a new object. Overwriting the prototype makes inheritance impossible: by resetting the prototype you'll overwrite the base! - - ```javascript - function Jedi() { - console.log('new jedi'); - } - - // bad - Jedi.prototype = { - fight: function fight() { - console.log('fighting'); - }, - - block: function block() { - console.log('blocking'); - } - }; - - // good - Jedi.prototype.fight = function fight() { - console.log('fighting'); - }; - - Jedi.prototype.block = function block() { - console.log('blocking'); - }; - ``` - - - Methods can return `this` to help with method chaining. - - ```javascript - // bad - Jedi.prototype.jump = function jump() { - this.jumping = true; - return true; - }; - - Jedi.prototype.setHeight = function setHeight(height) { - this.height = height; - }; - - var luke = new Jedi(); - luke.jump(); // => true - luke.setHeight(20); // => undefined - - // good - Jedi.prototype.jump = function jump() { - this.jumping = true; - return this; - }; - - Jedi.prototype.setHeight = function setHeight(height) { - this.height = height; - return this; - }; - - var luke = new Jedi(); - - luke.jump() - .setHeight(20); - ``` - - - - It's okay to write a custom toString() method, just make sure it works successfully and causes no side effects. - - ```javascript - function Jedi(options) { - options || (options = {}); - this.name = options.name || 'no name'; - } - - Jedi.prototype.getName = function getName() { - return this.name; - }; - - Jedi.prototype.toString = function toString() { - return 'Jedi - ' + this.getName(); - }; - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Events - - - When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of: - - ```js - // bad - $(this).trigger('listingUpdated', listing.id); - - ... - - $(this).on('listingUpdated', function (e, listingId) { - // do something with listingId - }); - ``` - - prefer: - - ```js - // good - $(this).trigger('listingUpdated', { listingId : listing.id }); - - ... - - $(this).on('listingUpdated', function (e, data) { - // do something with data.listingId - }); - ``` - - **[⬆ back to top](#table-of-contents)** - -## Requires - - - Organize your node requires in the following order: - - core modules (in-built with node e.g. http, fs, etc. ) - - npm modules (part of package dependencies e.g. async, bunyan, etc.) - - others (local file requires) - - ```javascript - // bad - var Car = require('./models/Car'); - var async = require('async'); - var http = require('http'); - - // good - var http = require('http'); - var fs = require('fs'); - - var async = require('async'); - var mongoose = require('mongoose'); - - var Car = require('./models/Car'); - ``` - - - Do not use the `.js` when requiring modules - - ```javascript - // bad - var Batmobil = require('./models/Car.js'); - - // good - var Batmobil = require('./models/Car'); - ``` - - -**[⬆ back to top](#table-of-contents)** - -## Callbacks - - - Always check for errors in callbacks - - ```javascript - //bad - database.get('pokemons', function(err, pokemons) { - console.log(pokemons); - }); - - //good - database.get('drabonballs', function(err, drabonballs) { - if (err) { - // handle the error somehow, maybe return with a callback - return console.log(err); - } - console.log(drabonballs); - }); - ``` - - - Return on callbacks - - ```javascript - //bad - database.get('drabonballs', function(err, drabonballs) { - if (err) { - // if not return here - console.log(err); - } - // this line will be executed as well - console.log(drabonballs); - }); - - //good - database.get('drabonballs', function(err, drabonballs) { - if (err) { - // handle the error somehow, maybe return with a callback - return console.log(err); - } - console.log(drabonballs); - }); - ``` - - - Use descriptive arguments in your callback when it is an "interface" for others. It makes your code readable. - - ```javascript - // bad - function getAnimals(done) { - Animal.get(done); - } - - // good - function getAnimals(done) { - Animal.get(function(err, animals) { - if(err) { - return done(err); - } - - return done(null, { - dogs: animals.dogs, - cats: animals.cats - }) - }); - } - ``` - -**[⬆ back to top](#table-of-contents)** - - -## Try catch - -- Only throw in synchronous functions - - Try-catch blocks cannot be used to wrap async code. They will bubble up to the top, and bring - down the entire process. - - ```javascript - //bad - function readPackageJson (callback) { - fs.readFile('package.json', function(err, file) { - if (err) { - throw err; - } - ... - }); - } - //good - function readPackageJson (callback) { - fs.readFile('package.json', function(err, file) { - if (err) { - return callback(err); - } - ... - }); - } - ``` - -- Catch errors in sync calls - - ```javascript - //bad - var data = JSON.parse(jsonAsAString); - - //good - var data; - try { - data = JSON.parse(jsonAsAString); - } catch (e) { - //handle error - hopefully not with a console.log ;) - console.log(e); - } - ``` - -**[⬆ back to top](#table-of-contents)** - -## Asynchronous Code - -- As a rule of thumb, prefer async over sync API, because using a non-blocking approach gives superior performance over the synchronous scenario. - -- Never use nested callback approach to handle asynchronous operations - - ```javascript - //bad - //validateParams, dbQuery, serviceCall are higher-order functions - function handler (done) { - validateParams((err) => { - if (err) return done(err) - dbQuery((err, dbResults) => { - if (err) return done(err) - serviceCall((err, serviceResults) => { - done(err, { dbResults, serviceResults }) - }) - }) - }) - } - ``` -- Avoiding Callback Hell with Control Flow Managers like; - - - Async library - - ```javascript - //good - //validateParams, dbQuery, serviceCall are higher-order functions - //Using async library - function handler (done) { - async.waterfall([validateParams, dbQuery, serviceCall], done) - } - ``` - - - Promises - - ```javascript - //good - //validateParams, dbQuery, serviceCall are thunks - function handler () { - return validateParams() - .then(dbQuery) - .then(serviceCall) - .then((result) => { - console.log(result) - return result - }) - } - ``` - - - Generators - - ```javascript - //good - // validateParams, dbQuery, serviceCall are thunks - const handler = function*() { - yield validateParams() - const dbResults = yield dbQuery() - const serviceResults = yield serviceCall() - return { dbResults, serviceResults } - } - ``` - -- Always use the best fitting flow control or a mix of them in order reduce the time spent waiting for I/O to complete. - -**[⬆ back to top](#table-of-contents)** - -## Resources - -**Editors** - - - Sublime Text 3: - - [SublimeLinter-eslint](https://github.com/roadhump/SublimeLinter-eslint) - - [Build Next](https://github.com/albertosantini/sublimetext-buildnext) - - [Vim](https://github.com/scrooloose/syntastic/tree/master/syntax_checkers/javascript) - - Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. - - Eclipse Orion: ESLint is the [default linter](http://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) - - Eclipse IDE with [Tern ESLint linter](https://github.com/angelozerr/tern.java/wiki/Tern-Linter-ESLint) - - [TextMate 2](https://github.com/natesilva/javascript-eslint.tmbundle) - - Atom: [linter-eslint](https://atom.io/packages/linter-eslint) - - [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm, AppCode, Android Studio, 0xDBE](http://plugins.jetbrains.com/plugin/7494) - - [Visual Studio Code](https://code.visualstudio.com) with the [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - -**Build Systems** - - - Grunt: [grunt-eslint](https://npmjs.org/package/grunt-eslint) - - Gulp: [gulp-eslint](https://npmjs.org/package/gulp-eslint) - - Mimosa: [mimosa-eslint](https://npmjs.org/package/mimosa-eslint) - - Broccoli: [broccoli-eslint](https://www.npmjs.org/package/broccoli-eslint) - - Browserify: [eslintify](https://www.npmjs.com/package/eslintify) - - Webpack: [eslint-loader](https://www.npmjs.org/package/eslint-loader) - - Rollup: [rollup-plugin-eslint](https://www.npmjs.org/package/rollup-plugin-eslint) - - Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint) - - Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint) - - Start: [start-eslint](https://www.npmjs.com/package/start-eslint) - - Brunch: [eslint-brunch](https://www.npmjs.com/package/eslint-brunch) - -**Command Line Tools** - - - [Eslint Watch](https://www.npmjs.com/package/eslint-watch) - - [Code Climate CLI](https://github.com/codeclimate/codeclimate) - - [ESLint Nibble](https://github.com/IanVS/eslint-nibble) - -**Other Style Guides** - - - [Google JavaScript Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) - - [Airbnb JavaScript Style Guide ES5](https://github.com/airbnb/javascript) - - [Airbnb JavaScript Style Guide ES6+ ES2015+](https://github.com/airbnb/javascript#ecmascript-6-es-2015-styles) - - [JavaScript Standard Style](https://github.com/feross/standard) - -**Further Reading** - - - [JavaScript:The Good Parts](http://javascript.crockford.com/) - - [JavaScript Design Patterns](https://addyosmani.com/resources/essentialjsdesignpatterns/book/) - - [Perfection Kills](http://perfectionkills.com/) - - [You-Dont-Know-JS](https://github.com/getify/You-Dont-Know-JS) - - [Understanding ECMAScript 6](https://leanpub.com/understandinges6/read) - - [Prototype Composition Delegation](http://www.datchley.name/understanding-prototypes-delegation-composition/) - - [Mixins Forwarding Delegation](http://raganwald.com/2014/04/10/mixins-forwarding-delegation.html) - - [V8 closures](http://mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html) - -**[⬆ back to top](#table-of-contents)** - -## License - -(The MIT License) - -Copyright (c) 2017 oeCloud.io - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -**[⬆ back to top](#table-of-contents)** \ No newline at end of file diff --git a/client/explorer/css/oeCloud.css b/client/explorer/css/oeCloud.css deleted file mode 100644 index 0871733..0000000 --- a/client/explorer/css/oeCloud.css +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* oecloud customizations */ - -body #header { - background-color: #1724BA !important; -} - -body #header form#api_selector .input a#explore { - background-color: white !important; - color: #1724BA !important; -} - -body #header form#api_selector .input a#explore:hover { - background-color: white !important; - cursor: pointer; -} -body #header form#api_selector .input a#explore:active { - background-color: #eeeeee !important; -} - -body #header #logo{ - background-size: 36px !important; -} \ No newline at end of file diff --git a/client/explorer/favicon.ico b/client/explorer/favicon.ico deleted file mode 100644 index fb5e39b4cb905fb828eb40eb8ca731119ebc1aca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 diff --git a/client/explorer/images/logo_small.png b/client/explorer/images/logo_small.png deleted file mode 100644 index e423b6993867d90792b47eafff86af5d02bb2674..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6528 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 mO0ZeroysLirulp=E&mU2ch>u=3NBy(0000 - - - - - - oeCloud.io API Explorer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - diff --git a/client/favicon.ico b/client/favicon.ico deleted file mode 100644 index fb5e39b4cb905fb828eb40eb8ca731119ebc1aca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 diff --git a/client/index.html b/client/index.html deleted file mode 100644 index 156a065..0000000 --- a/client/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - oeCloud.io - - - - - - - - -

    oeCloud.io

    -

    You may want to install bower components for polymer UI

    - - - -
    - - - - - diff --git a/common/mixins/audit-fields-mixin.js b/common/mixins/audit-fields-mixin.js deleted file mode 100644 index f063c73..0000000 --- a/common/mixins/audit-fields-mixin.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin adds audit properties such as _type, _createdBy, _modifiedBy, - * _createdOn, _modifiedOn to model and populate them before they are saved into - * database.
    - *
    - * - * Description about the properties:
    - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    NameTypeDescription
    _typestringmodelName.
    _createdBystringremote user who has created the record.
    _createdOnDatetime stamp of creation.
    _modifiedBystringremote user who has modified the record.
    _modifiedOnDatetime stamp of modification.
    - * - * @mixin Audit Mixin - * @author Sivankar Jain - */ - -module.exports = function AuditFieldsMixin(Model) { - if (Model.definition.name === 'BaseEntity') { - return; - } - - Model.defineProperty('_type', { - type: String, - length: 50 - }); - Model.defineProperty('_createdBy', { - type: String, - length: 50 - }); - Model.defineProperty('_modifiedBy', { - type: String, - length: 50 - }); - - Model.defineProperty('_createdOn', { - type: 'timestamp' - }); - - Model.defineProperty('_modifiedOn', { - type: 'timestamp' - }); - - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.AuditFieldsMixin) || !Model.settings.mixins.AuditFieldsMixin) { - Model.evRemoveObserver('before save', injectAuditFields); - } else { - Model.evObserve('before save', injectAuditFields); - } -}; - -/** - * This is an before save observer hook to auto inject Audit properties to the - * Posted data.

    - * - * It checks if posted data is a new instance or an update. In case of new - * Instance it populates all the audit fields, where as in case of update it - * modifies _modifiedBy and _modifiedOn with the appropriate user and time stamp respectively. - * - * @param {object} ctx - ctx object, which is populated by DAO. - * @param {function} next - move to the next function in the queue - * @return {function} next - move to the next function in the queue - * @memberof Audit Mixin - */ -function injectAuditFields(ctx, next) { - var context = ctx.options; - var cctx = context.ctx || {}; - - var remoteUser = cctx.remoteUser || 'system'; - - var currentDateTime = new Date(); - - var protectedFields = ['_type', '_createdBy', '_modifiedBy', '_createdOn', '_modifiedOn']; - var postData = ctx.instance || ctx.data; - // var currentInstance = ctx.currentInstance; - // if user provide data for any protectedField those data are removed, and - // auto set. - var isInstance = ctx.instance; - protectedFields.forEach(function AuditFieldsMixinProtectedFieldsForEachCb(field) { - if (isInstance) { - postData.unsetAttribute(field); - } else { - delete postData[field]; - } - }); - if (isInstance) { - // full save. - if (ctx.isNewInstance) { - // Auto-populate entity type - postData._type = ctx.Model.definition.name; - - // Auto-populate created by user id and timestamp - postData._createdBy = remoteUser; - postData._createdOn = currentDateTime; - } - } - postData._modifiedBy = remoteUser; - postData._modifiedOn = currentDateTime; - return next(); -} diff --git a/common/mixins/auto-fields-mixin.js b/common/mixins/auto-fields-mixin.js deleted file mode 100644 index 60306fd..0000000 --- a/common/mixins/auto-fields-mixin.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This mixin is for auto populate scope overriding manual scope - -*/ -/* @mixin Property expression mixin*/ -/* @author dipayan_aich*/ -var logger = require('oe-logger'); -var log = logger('auto-fields-mixin'); -var autofields = require('../../lib/auto-fields'); -var async = require('async'); - -module.exports = function AutoFieldsMixin(Model) { - if (Model.modelName === 'BaseEntity') { - return; - } - - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.AutoFieldsMixin) || !Model.settings.mixins.AutoFieldsMixin) { - Model.evRemoveObserver('before save', autoFieldMixinBeforeSave); - } else { - Model.observe('before save', autoFieldMixinBeforeSave); - } -}; - -/** - * This 'before save' hook is used to intercept data being - * POSTed using the Loopback API and automatically set properties - * which are marked with a "setval" : ) before saving - * the data to database. This feature is - * applicable to Models that derive from BaseEntity only. - * The pattern-string should be a dot ('.') separated set of - * string values. The first value determines the "source object" - * from where a value needs to be picked up and set to the specified property. - * This first value (source) can be one of BODY, QUERY, HEADER, - * COOKIE, REQUEST, CTX, CALLCONTEXT, ACCESSTOKEN, USER or USERPROFILE. - * - * A property declared to be auto-populated using this feature will always - * override the value, if sent from the client. i.e., the system-generated - * value will overwrite the value supplied by the client in the API (POST, for e.g.,) - * - * - * @memberof Auto Field Mixin - * @param {Object} ctx - call context - * @param {function} next - callback function - * @function - */ -function autoFieldMixinBeforeSave(ctx, next) { - var data = ctx.instance || ctx.currentInstance || ctx.data; - var props = ctx.Model.definition.properties; - log.debug(ctx.options, 'BaseEntity before save called for auto-population: ModelName =', ctx.Model.modelName); - - async.forEachOf(props, function baseEntityObserveBeforeSaveSetValAsyncForEachPropsCb(value, key, callback) { - var propprops = ctx.Model.definition.properties[key]; - if (propprops.setval) { - log.debug(ctx.options, 'To be set:', key, propprops.setval); - autofields({ - 'pattern': propprops.setval - }, ctx.options, function baseEntityObserveBeforeSaveSetValAsyncForEachPropsAutoFieldsCb(val) { - data[key] = val; - callback(); - }); - } else { - callback(); - } - }, function baseEntityObserveBeforeSaveSetValAsyncForEachCb(err) { - if (err) { - log.error(ctx.options, err.message); - } - next(); - }); -} diff --git a/common/mixins/business-rule-mixin.js b/common/mixins/business-rule-mixin.js deleted file mode 100644 index e0c27a7..0000000 --- a/common/mixins/business-rule-mixin.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This file takes certain business rules, evaluate it and sends an array of error codes - * corresponding to the business rules that are violated. - * - * @author Sambit Kumar Patra - * @mixin Business Rule Mixin - */ -var q = require('oe-promise'); -var exprLang = require('../../lib/expression-language/expression-language.js'); -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('model-validations'); - -module.exports = function BusinessRuleMixin(BusinessRuleModel) { - /** - * This function evaluate the business rules and sends an array of error codes - * corresponding to the business rules that are violated. - * - * @memberof Business Rule Mixin - * @param {Array.} rules - array of expressions - * @param {Object} inst - data object - * @param {function} options - options - * @param {function} cb - callback function - * @function - */ - BusinessRuleModel.prototype.processBusinessRules = function processBusinessRules(rules, inst, options, cb) { - var Model = loopback.getModel('BusinessRule'); - var ast = Model._ast; - var businessRulePromises = []; - var errCode = []; - // convert the business rules into promises to be resolved by expression language - if (rules && rules.length > 0) { - log.trace(options, 'Creating business rule promises array'); - rules.forEach(function businessRuleMixinRulesForEach(bRule) { - businessRulePromises.push(exprLang.traverseAST(ast[bRule.expression], inst, options)); - }); - } - // when all promises are resolved filter out those which contains error code and pass it to the callback - q.allSettled(businessRulePromises).then(function businessRuleMixinPromiseResolved(results) { - log.trace(options, 'All business rule promises settled'); - results.map(function businessRuleMixinMap(d) { - return d.value; - }).forEach(function businessRuleMixinAllSettledForEach(d, i) { - if (typeof d !== 'undefined' && !d) { - log.warn(options, 'Business rule ', rules[i], 'violated'); - errCode.push(rules[i].code); - } - }); - return cb(errCode); - }); - }; -}; diff --git a/common/mixins/cache-mixin.js b/common/mixins/cache-mixin.js deleted file mode 100644 index c98113a..0000000 --- a/common/mixins/cache-mixin.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin is attached to BaseEntity so that it applies to all Models used in - * the oeCloud.io framework. The purpose of this mixin is as follows: - * At creation time of (each) model, check if its definition contains a property - * called 'cacheable', and if its value is anything other than 'false'. - * If so, mark the model as Cacheable by adding a property with this model's name - * to the global 'evcacheables' object. - * The global 'evcacheables' object is used to check whether a given model is cacheable - * or not in the modified loopback-datasource-juggler - * - * @mixin Cache Mixin - * @author Ajith Vasudevan - */ - -var logger = require('oe-logger'); -var log = logger('cache-mixin'); -var process = require('process'); - -module.exports = function CacheMixin(Model) { - // Add an 'After Save' observer for this Model to evict the cache - // corresponding to this Model's data whenever this Model's data - // is updated. - - Model.evObserve('after save', clearCacheOnSave); - Model.evObserve('after delete', clearCacheOnDelete); - - // create the global evDisableInstanceCache object if not present - if (!global.evDisableInstanceCache) { - global.evDisableInstanceCache = {}; - } - - // Add an 'After Delete' observer for this Model to evict the cache - // corresponding to this Model's data whenever this Model's data - // is deleted. - // check if this model is defined/declared to be cacheable - if (Model.definition && Model.definition.settings && Model.definition.settings.cacheable) { - log.info(log.defaultContext(), 'EV_CACHE', 'Marking as Cacheable model:', Model.modelName); - - // create the global evcacheables object if not present - if (!global.evcacheables) { global.evcacheables = {}; } - - // Mark the model as cacheable by adding a property with this model's name - // to the 'evcacheables' object and setting its value to 'true'. - global.evcacheables[Model.modelName] = true; - } - - if ((Model.definition && Model.definition.settings && Model.definition.settings.disableInstanceCache) || process.env.CONSISTENT_HASH !== 'true') { - log.debug(log.defaultContext(), 'EV_CACHE', 'disable instance cache for model:', Model.modelName); - - // Mark the model as not instance cache enabled by adding a property with this model's name - // to the 'evDisableInstanceCache' object and setting its value to 'true'. - global.evDisableInstanceCache[Model.modelName] = true; - } - - if (Model.definition && Model.definition.settings && !Model.definition.settings.cacheable) { - log.debug(log.defaultContext(), 'EV_CACHE', 'Marking as Uncacheable model:', Model.modelName); - } - - if (Model.definition && Model.definition.settings && !Model.definition.settings.disableInstanceCache) { - log.debug(log.defaultContext(), 'EV_CACHE', 'Marking as Uncacheable model:', Model.modelName); - } -}; - -function clearCacheOnSave(ctx, next) { - ctx.Model.clearCacheOnSave(ctx, next); -} - -function clearCacheOnDelete(ctx, next) { - ctx.Model.clearCacheOnDelete(ctx, next); -} diff --git a/common/mixins/crypto-mixin.js b/common/mixins/crypto-mixin.js deleted file mode 100644 index e7791a4..0000000 --- a/common/mixins/crypto-mixin.js +++ /dev/null @@ -1,160 +0,0 @@ -/** -* -* 2016-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -* Bangalore, India. All Rights Reserved. -* -*/ - -var encryption = require('../../lib/encryption'); -var logger = require('oe-logger'); -var config = require('../../server/config.js'); - -var log = logger('crypto-mixin'); - -module.exports = function CryptoMixin(Model) { - if (Model.modelName === 'BaseEntity') { - return; - } - Model.settings.propsToEncrypt = []; - - var props = Model.definition.properties; - for (var key in props) { - if (props.hasOwnProperty(key)) { - var propprops = Model.definition.properties[key]; - if (propprops.encrypt && propprops.type.name.toLowerCase() === 'string') { - Model.settings.propsToEncrypt.push(key); - } - } - } - - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.SoftDeleteMixin) || !Model.settings.mixins.CryptoMixin) { - Model.evRemoveObserver('before save', cryptoMixinBeforeSaveHook); - Model.evRemoveObserver('after accesss', cryptoMixinAfterAccessHook); - } else { - Model.evObserve('before save', cryptoMixinBeforeSaveHook); - Model.evObserve('after accesss', cryptoMixinAfterAccessHook); - } -}; - -/** - * This 'before save' hook is used to intercept data being - * POSTed using the Loopback API and encrypt properties - * (which are marked with an "encrypt" : true ) before saving - * the data to database. Thus the database will only contain - * encrypted values for these properties. This feature is - * applicable to Models that derive from BaseEntity only. - * Ajith - */ - -function cryptoMixinBeforeSaveHook(ctx, next) { - if (ctx.Model.settings.propsToEncrypt && ctx.Model.settings.propsToEncrypt.length === 0) { - return next(); - } - var data = ctx.instance || ctx.data; - log.debug(ctx.options, 'cryptoMixin before save called: ModelName =', ctx.Model.modelName); - var props = ctx.Model.definition.properties; - ctx.Model.settings.propsToEncrypt.forEach(function (key) { - if (props.hasOwnProperty(key)) { - log.debug(ctx.options, 'To be encrypted:', key, data[key]); - data[key] = encrypt(data[key]); - log.debug(ctx.options, 'After encryption:', key, data[key]); - } - }); - next(); -} - -/** - * The function encrypts the specified string argument - * using the algorithm and password specified in - * config.json (priority given to application config) - * and returns the resulting encrypted value as a string. - * @param {string}str - string to encrypt - * @returns {string} - encrypted string - * Ajith - */ -function encrypt(str) { - log.debug(log.defaultContext(), 'INFO: Encryption Algorithm defined in config.json is', '\'', config.encryptionAlgorithm, '\''); - var module = config.encryptionAlgorithm.split('.')[0]; - var algo = config.encryptionAlgorithm.split('.')[1]; - var pwd = config.encryptionPassword; - if (!encryption) { - log.error(log.defaultContext(), 'ERROR: Encryption module is not available'); - var err = new Error('ERROR: Encryption module is not available'); - err.retriable = false; - throw err; - } - if (!encryption[module]) { - log.warn(log.defaultContext(), 'WARNING: Encryption algorithm module', module, 'defined in config.json (e.g., "encryptionAlgorithm": "', config.encryptionAlgorithm, '") is not implemented. Falling back to crypto'); - module = 'crypto'; - } - var encrypter = new encryption[module]({ - 'algorithm': algo, - 'password': pwd - }); - return encrypter.encrypt(str); -} - -/** - * This 'after accesss' hook is used to intercept data - * fetched from the database, before it is sent to the - * client requesting for it. Here, the properties - * (which are marked with an "encrypt" : true ) are - * decrypted and the data is sent to the client. - * - * Ajith - */ - -function cryptoMixinAfterAccessHook(ctx, next) { - if (ctx.Model.settings.propsToEncrypt && ctx.Model.settings.propsToEncrypt.length === 0) { - return next(); - } - var data = ctx.instance || ctx.currentInstance || ctx.data || ctx.accdata; - var props = ctx.Model.definition.properties; - data.forEach(function (item) { - ctx.Model.settings.propsToEncrypt.forEach(function (key) { - if (props.hasOwnProperty(key)) { - log.debug(ctx.options, 'To be decrypted:', key, item[key]); - item[key] = decrypt(item[key]); - log.debug(ctx.options, 'After decryption:', key, item[key]); - } - }); - }); - next(); -} - -/** - * The function decrypts the specified string argument - * using the algorithm and password specified in - * config.json (priority given to application config) - * and returns the resulting decrypted value as a string. - * @param {string}str - string to decrypt - * @returns {string} - decrypted string - * Ajith - */ -function decrypt(str) { - log.debug(log.defaultContext(), 'INFO: Encryption Algorithm defined in config.json is', '\'', config.encryptionAlgorithm, '\''); - var module = config.encryptionAlgorithm.split('.')[0]; - var algo = config.encryptionAlgorithm.split('.')[1]; - var pwd = config.encryptionPassword; - if (!encryption) { - log.error(log.defaultContext(), 'ERROR: Decryption module is not available'); - var err = new Error('ERROR: Decryption module is not available'); - err.retriable = false; - throw err; - } - if (!encryption[module]) { - log.warn(log.defaultContext(), 'WARNING: Decryption algorithm module', module, 'defined in config.json (e.g., "encryptionAlgorithm": "', config.encryptionAlgorithm, '") is not implemented. Falling back to crypto'); - module = 'crypto'; - } - var encrypter = new encryption[module]({ - 'algorithm': algo, - 'password': pwd - }); - var result = '**********'; - try { - result = encrypter.decrypt(str); - } catch (e) { - log.error(log.defaultContext(), e); - } - return result; -} diff --git a/common/mixins/data-hierarchy-mixin.js b/common/mixins/data-hierarchy-mixin.js deleted file mode 100644 index b478a78..0000000 --- a/common/mixins/data-hierarchy-mixin.js +++ /dev/null @@ -1,397 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -const mergeQuery = require('loopback-datasource-juggler/lib/utils').mergeQuery; -const toRegExp = require('loopback-datasource-juggler/lib/utils').toRegExp; -const _ = require('lodash'); -const uuidv4 = require('uuid/v4'); -const async = require('async'); -const ROOT_PATH = ',root,'; -module.exports = function DataHierarchyMixin(Model) { - if (Model.modelName === 'BaseEntity') { - return; - } - Model.defineProperty('_hierarchyScope', { - type: 'object', - index: true, - required: false - }); - - // Making _hierarchyScope as hidden fields. - if (Model.definition.settings.hidden) { - Model.definition.settings.hidden = Model.definition.settings.hidden.concat(['_hierarchyScope']); - } else { - Model.definition.settings.hidden = ['_hierarchyScope']; - } - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.DataHierarchyMixin) || (!Model.settings.mixins.DataHierarchyMixin)) { - Model.evRemoveObserver('before save', hierarchyBeforeSave); - Model.evRemoveObserver('access', hierarchyAccess); - Model.evRemoveObserver('after accesss', hierarchyAfterAccess); - } else { - Model.evObserve('before save', hierarchyBeforeSave); - Model.evObserve('access', hierarchyAccess); - Model.evObserve('after accesss', hierarchyAfterAccess); - } -}; - - -/** - * Observer function DataBeforeSave. - * This function is invoked upon save of data in any model - * to which this mixin is attached. - * It reads HierarchyScope array from model definition settings, - * gets value from context.ctx for each key and sets to data._hierarcyScope.F - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @return {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function hierarchyBeforeSave(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for DataHierarchyMixin is applied or not. - if (modelSettings.mixins.DataHierarchyMixin === false) { - return next(); - } - - // adding hierarchyScope setting. - if (!modelSettings.hierarchyScope || ctx.options.ignoreHierarchy) { - return next(); - } - - const callContext = ctx.options; - - // Clone callContext.ctx so the any changes locally made will not affect callContext.ctx. - let context = Object.assign({}, callContext.ctx); - - // Convert the callcontext to lowercase. - context = convertToLowerCase(context); - - const data = ctx.data || ctx.instance; - const _hierarchyScope = {}; - const hierarchyScope = modelSettings.hierarchyScope; - - async.each(hierarchyScope, (key, cb) => { - if (typeof key === 'string') { - setValueToHierarchyScope(ctx, context, data, _hierarchyScope, key, cb); - } else { - const err = new Error(); - err.name = 'Hierarchy Scope Definition Error'; - err.message = `The Hierarchy scope in model should be of type string for the model ${ctx.Model.modelName} key ${key}`; - err.code = 'DATA_HIERARCHY_ERROR_001'; - err.type = 'Type mismatch in Declaration'; - err.retriable = false; - return next(err); - } - }, (err) => { - if (err) { - return next(err); - } - data._hierarchyScope = _hierarchyScope; - return next(); - }); -} - -/** - * Observer function dataAccess. - * This function is invoked upon access of data in any model. - * It reads the hierarchyScope from model definition settings and - * gets values from context.ctx for each key and forms a query based - * on inputs like upwards and depth. - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @return {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function hierarchyAccess(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for DataHierarchyMixin is applied or not - if (modelSettings.mixins.DataHierarchyMixin === false) { - return next(); - } - - // adding hierarchyScope setting. - if (!modelSettings.hierarchyScope || ctx.options.ignoreHierarchy) { - return next(); - } - - const callContext = ctx.options; - - // Clone callContext.ctx so the any changes locally made will not affect callContext.ctx. - let context = Object.assign({}, callContext.ctx); - - // Convert the callcontext to lowercase. - context = convertToLowerCase(context); - - const hierarchyScope = modelSettings.hierarchyScope; - - hierarchyScope.forEach((key) => { - if (typeof key === 'string') { - if (context && context[key]) { - createQuery(ctx, context, key); - } - } else { - const err = new Error(); - err.name = 'Hierarchy Scope Definition Error'; - err.message = `The Hierarchy scope in model should be of type string for the model ${ctx.Model.modelName} key ${key}`; - err.code = 'DATA_HIERARCHY_ERROR_001'; - err.type = 'Type mismatch in Declaration'; - err.retriable = false; - return next(err); - } - }); - return next(); -} - -/** - * Observer function to handle - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @returns {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function hierarchyAfterAccess(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for HierarchyMixin is applied or not. - if (modelSettings.mixins.DataHierarchyMixin === false) { - return next(); - } - - // adding hierarchyScope setting. - if (!modelSettings.hierarchyScope || ctx.options.ignoreHierarchy) { - return next(); - } - - const upward = modelSettings.upward || false; - - let resultData = []; - const result = ctx.accdata; - - if (result.length && upward) { - let uniq = []; - const modelProp = ctx.Model.definition.properties; - - result.forEach((obj) => { - let weight = 0; - Object.keys(obj._hierarchyScope).forEach((item) => { - const value = obj._hierarchyScope[item]; - weight += value.split(',').length; - }); - obj.weight = weight; - resultData.push(obj); - }); - - // Reads each property for unique and populates uniq array. - Object.keys(modelProp).forEach((key) => { - const prop = modelProp[key]; - if (prop.unique) { - if (typeof prop.unique === 'boolean' || typeof prop.unique === 'string') { - uniq.push(key); - } else if (typeof prop.unique === 'object') { - prop.unique.scopedTo ? uniq = uniq.concat(prop.unique.scopedTo) : null; - uniq.push(key); - } - } - }); - - // var sortFields = uniq.concat(['weights']); - // var sortOrders = _.fill(Array(sortFields.length), 'desc'); - // Lodash v3.10.1 - resultData = _.sortByOrder(resultData, 'weight', 'desc'); - - // Filter out the redundent records from result by applying unique validation. - if (uniq.length > 0) { - resultData = _.uniq(resultData, value => uniq.map(u => value[u]).join('-')); - // resultData = _.intersection.apply(this, _.chain(uniq).map(function (v) { return _.uniq(resultData, v) }).value()); - } - ctx.accdata = resultData; - } - next(); -} - -/** - * This function is used to convert any input(array or object) to lowercase - * In case of arrays its elements will be converted to lowercase. - * In case of object its values will be converted to lowercase. - * - * @param {array|object} input - any array or object - * @returns {array|object} - array or object according to input. - * @function - */ -var convertToLowerCase = function convertToLowerCase(input) { - // Check for type of input and branch accordingly. - if (Array.isArray(input)) { - const resArr = []; - input.forEach((value) => { - resArr.push(value.toLowerCase()); - }); - return resArr; - } else if (input && typeof input === 'object') { - const resObj = {}; - Object.keys(input).forEach((key) => { - const value = input[key]; - if (typeof value === 'string') { - resObj[key] = value.toLowerCase(); - } else if (typeof value === 'object') { - resObj[key] = convertToLowerCase(value); - } else { - resObj[key] = value; - } - }); - return resObj; - } -}; - -/** - * This function is used to form hierarchy string. Simply concatenates - * child to parent. - * - * @param {string} parentPath - Parent path - * @param {string} childPath - Child path - * @returns {string} - "Parentpath+childpath" - * @function - */ -function createPath(parentPath, childPath) { - parentPath = parentPath.substring(0, parentPath.length - 1); - return parentPath.concat(childPath); -} - -/** - * This function is used to form the query based on upward and - * depth parameters - * - * @param {object} ctx - hook ctx - * @param {object} context - current context - * @param {string} hierarchy - current hierarchy - * @function - */ -function createQuery(ctx, context, hierarchy) { - const upward = ctx.Model.definition.settings.upward || false; - let depth = ctx.query && ctx.query.depth ? ctx.query.depth : '0'; - let query = {}; - const key = `_hierarchyScope.${hierarchy}`; - const regexString = context[hierarchy]; - const orParms = []; - let modifiedRegex; - - if (!upward) { - if (depth === '*') { - const regexObj = toRegExp(regexString); - - query[key] = regexObj; - mergeQuery(ctx.query, { - where: query - }); - } else { - for (let i = 0; i <= depth; i++) { - query = {}; - if (i === 0) { - modifiedRegex = `${regexString}$`; - } else { - modifiedRegex = `${modifiedRegex.substr(0, modifiedRegex.length - 1)}[[:alnum:]]*,$`; - } - query[key] = toRegExp(modifiedRegex); - orParms.push(query); - } - mergeQuery(ctx.query, { - where: { - or: orParms - } - }); - } - } else { - if (depth === '*') { - depth = regexString.split(',').length - 2; - } - for (let j = 0; j <= depth; j++) { - query = {}; - if (j === 0) { - modifiedRegex = `${regexString}$`; - } else { - const hierarchyArray = modifiedRegex.split(','); - hierarchyArray.splice(hierarchyArray.length - 2, 1); - modifiedRegex = hierarchyArray.join(); - } - if (modifiedRegex === ',$' || modifiedRegex === '$') { - break; - } - query[key] = toRegExp(modifiedRegex); - orParms.push(query); - } - mergeQuery(ctx.query, { - where: { - or: orParms - } - }); - } -} - -/** - * This function is used to form get value for each key in Hierarchy scope. - * - * @param {object} ctx - hook ctx - * @param {object} context - current context - * @param {object} data - instance - * @param {object} _hierarchyScope - hierarchy scope - * @param {string} key - hierarchy key - * @param {function} cb - callback - * @return {function} cb - callback - * @function - */ -function setValueToHierarchyScope(ctx, context, data, _hierarchyScope, key, cb) { - var modelName = ctx.Model.clientModelName || ctx.Model.modelName; - if (modelName.toLowerCase().concat('Hierarchy') === key) { - if (ctx.isNewInstance && data.id !== 'root') { - if (!data.id) { - data.id = uuidv4(); - } - if (data.parentId) { - ctx.Model.findById(data.parentId, ctx.options, (err, parent) => { - if (err) { - return cb(err); - } - if (parent && parent._hierarchyScope && parent._hierarchyScope[key]) { - _hierarchyScope[key] = createPath(parent._hierarchyScope[key], `,${data.id},`); - cb(); - } else { - const err1 = new Error(); - err1.name = 'Parent Not Found'; - err1.message = `Parent Not Found for the given parentid ${data.parentId}`; - err1.code = 'DATA_HIERARCHY_ERROR_003'; - err1.type = 'ParentNotFound'; - err1.retriable = false; - return cb(err1); - } - }); - } else { - _hierarchyScope[key] = createPath(ROOT_PATH, `,${data.id},`); - cb(); - } - } else if (ctx.isNewInstance && data.id === 'root') { - _hierarchyScope[key] = ROOT_PATH; - cb(); - } else { - cb(); - } - } else if (context && context[key]) { - _hierarchyScope[key] = context[key]; - cb(); - } else { - const err1 = new Error(); - err1.name = 'Hierarchy Personalization error'; - err1.message = `insufficient data! HierachyScope values not found for the model ${ctx.Model.modelName} key ${key}`; - err1.code = 'DATA_HIERARCHY_ERROR_002'; - err1.type = 'Insufficient data'; - err1.retriable = false; - return cb(err1); - } -} diff --git a/common/mixins/data-personalization-mixin.js b/common/mixins/data-personalization-mixin.js deleted file mode 100644 index 91738e2..0000000 --- a/common/mixins/data-personalization-mixin.js +++ /dev/null @@ -1,834 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin is attached to BaseEntity so that it applies to all Models used in - * the oeCloud.io framework.This is used to add data with autoscope and manual - * scope values. While POSTing the data manual scope values are sent by the user adding - * a json attribute named scope : {} and autoscoped values are read from the model - * settings and final scope will be calculated based on values of manual scope and auto scope - * and stored in record along with calculated _scope. While GETting the data, records are - * filtered based on existence of all the auto scoped values in record and any of the matched - * manual scoped values and weightages taken from the context and applied to the results to - * calculate score and sorted in descending order of the score.
    - * - *
    - * scope : While POSTing the data user can specify a scope manually by adding a json attribute
    - *         named scope : {}
    - *
    - * autoscope : A setting in model.json where developer can force model to be automatically scoped
    - *             at certain parameter.Specified as an array of strings in model definition.
    - *
    - * _scope : A final calculated scope which will be stored in the database.
    - *          The final _scope will be a combination of autoscope and Manual Scope.
    - *         _scope will be stored as an array of Integer bit positions reserved on first
    - *         come first serve basis.
    - *
    - * _autoScope : A variable which contains the autoscoped values.
    - *
    - * Weightages : Weightages specified on request may be as an additional header.
    - *              These weightages will be used while calculating the score while retrieving
    - *              the records.
    - *
    - * score : The final value calculated based on weightages of the matched records.
    - *         The record with highest score will be given priority over the next highest score
    - *         and so on.
    - * 
    - * - * @mixin Data personalization mixin - * @author Ramesh Choudhary. - */ - -const mergeQuery = require('loopback-datasource-juggler/lib/utils').mergeQuery; -const _ = require('lodash'); -const log = require('oe-logger')('data-personalization-mixin'); - -module.exports = Model => { - if (Model.modelName === 'BaseEntity') { - return; - } - // Defining a new _score, scope, _autoScope property - Model.defineProperty('_scope', { - type: ['string'], - index: true, - required: false - }); - Model.defineProperty('_autoScope', { - type: 'object', - required: false - }); - if (!Model.definition.properties.scope) { - Model.defineProperty('scope', { - type: 'object', - required: false - }); - } - - // Making _autoScope and _scope as hidden fields. - if (Model.definition.settings.hidden) { - Model.definition.settings.hidden = Model.definition.settings.hidden.concat(['_autoscope', '_scope']); - } else { - Model.definition.settings.hidden = ['_autoScope', '_scope']; - } - - // adding autoscope setting. - if (!Model.definition.settings.autoscope) { - Model.definition.settings.autoscope = []; - } - - // Initializing mixin field in model settings so that we need not check for that field while performing operations - if (!Model.definition.settings.mixins) { - Model.definition.settings.mixins = {}; - } - - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.DataPersonalizationMixin) || !Model.definition.settings.mixins.DataPersonalizationMixin) { - Model.evRemoveObserver('before save', dataPersonalizationBeforeSave); - Model.evRemoveObserver('access', dataPersonalizationAccess); - if (!Model.definition.settings.disableManualPersonalization) { - Model.evRemoveObserver('after accesss', dataPersonalizationAfterAccess); - } - } else { - Model.evObserve('before save', dataPersonalizationBeforeSave); - Model.evObserve('access', dataPersonalizationAccess); - if (!Model.definition.settings.disableManualPersonalization) { - Model.evObserve('after accesss', dataPersonalizationAfterAccess); - } - } -}; - -/** - * This function is used to convert scope to _scope. - * - * @param {object}scope - scope property of a record. - * @returns {array} - array of strings of the form "key.value" - * @function - */ -const convertToKeyValueString = function convertToKeyValueString(scope) { - const _scope = []; - - // Loop through each key value pair and form an array of strings - // each string in array will be of form "key.value" - Object.keys(scope).forEach((key, index) => { - let value = scope[key]; - let keyValuestring; - value = value || ''; - // If array then the string will only have the "key" else it will have "key.value" - if (Array.isArray(value)) { - value.forEach((item) => { - keyValuestring = `${key}:${item}`; - _scope.push(keyValuestring); - }); - } else { - keyValuestring = `${key}:${value.toString()}`; - _scope.push(keyValuestring); - } - }); - return _scope; -}; - -/** - * This function is used to convert any input(array or object) to lowercase - * In case of arrays its elements will be converted to lowercase. - * In case of object its values will be converted to lowercase. - * - * @param {array|object} input - any array or object - * @returns {array|object} - array or object according to input. - * @function - */ -const convertToLowerCase = function convertToLowerCase(input) { - // Check for type of input and branch accordingly. - if (Array.isArray(input)) { - const resArr = []; - input.forEach((value) => { - resArr.push(value.toLowerCase()); - }); - return resArr; - } else if (input && typeof input === 'object') { - const resObj = {}; - Object.keys(input).forEach((key) => { - const value = input[key]; - if (typeof value === 'string') { - resObj[key] = value.toLowerCase(); - } else if (typeof value === 'object') { - resObj[key] = convertToLowerCase(value); - } else { - resObj[key] = value; - } - }); - return resObj; - } -}; - -/** - * This function is used to filter autoscope values from contextContributor. - * - * @param {object}ctx - execution context scope - * @param {array}autoscope - autoscope - * @returns {Object} - returns new execution scope. - * @function - */ -const filterAutoscopeFromCtx = function filterAutoscopeFromCtx(ctx, autoscope) { - const newCtx = {}; - _.forEach(ctx, (value, key) => { - if (!(_.contains(autoscope, key))) { - newCtx[key] = value; - } - }); - return newCtx; -}; - -/** - * Observer function DataBeforeSave. - * This function is invoked upon save of data in any model. - * It reads autoscope array from model definition settings - * and reads the scope from the ctx.instance and modifies the scope - * by adding autoscope values in the scope variable before saving. - * - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @returns {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function dataPersonalizationBeforeSave(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for DataPersonalizationMixin is applied or not. - // If mixin is applied to current model then only data wil be scoped. - if (modelSettings.mixins.DataPersonalizationMixin === false) { - return next(); - } - - const callContext = ctx.options; - - // Clone callContext.ctx so the any changes locally made will not affect callContext.ctx. - let context = Object.assign({}, callContext.ctx); - - // Convert the callcontext to lowercase. - context = convertToLowerCase(context); - - // Reading the autoscope values from the model definition settings. - const autoscope = modelSettings.autoscope; - - const data = ctx.instance || ctx.data; - // log.debug('Raw data with manual scope - ' , JSON.stringify(data)); - let scope; - if (modelSettings.disableManualPersonalization) { - scope = {}; - } else { - scope = (data.scope && data.scope.__data) || data.scope || {}; - } - - // Converting scope to lowercase - scope = convertToLowerCase(scope); - const _autoScope = {}; - - let currentAutoScope; - if (!ctx.IsNewInstance && ctx.currentInstance) { - currentAutoScope = ctx.currentInstance._autoScope; - } - - // get default autoscope value from config files - const defaultValue = ctx.Model.app.get('defaultAutoScope') || ''; - - if (callContext.ignoreAutoScope) { - if (!callContext.useScopeAsIs) { - autoscope.forEach((key) => { - _autoScope[key] = defaultValue; - }); - } else { - return next(); - } - } else { - // Loop through each value in autoscope. - autoscope.forEach((key) => { - if (currentAutoScope) { - const f1 = context[key] || ''; - const f2 = currentAutoScope[key] || ''; - if (f1 !== f2) { - const error = new Error(`could not find a model with id ${ctx.currentInstance.id}`); - error.statusCode = 404; - error.code = 'MODEL_NOT_FOUND'; - error.retriable = false; - return next(error); - } - } - - if (scope[key]) { - // If autoscoped values are passed in scope then data would not be saved. - const err = new Error(); - err.name = 'Data Personalization error'; - err.message = 'Cannot pass autoscoped values in payload'; - err.code = 'DATA_PERSONALIZATION_ERROR_028'; - err.type = 'InvalidData'; - err.retriable = false; - return next(err); - } - if (context[key]) { - // adding autoscope values to scope. - // scope[key] = context[key]; - _autoScope[key] = context[key]; - } else { - // throws an Error when model is autoscope on some contributor - // but contributor values are not provided. - const err1 = new Error(); - err1.name = 'Data Personalization error'; - err1.message = `insufficient data! Autoscoped values not found for the model${ctx.Model.modelName} key ${key}`; - err1.code = 'DATA_PERSONALIZATION_ERROR_029'; - err1.type = 'AutoScopeValuesNotFound'; - err1.retriable = false; - return next(err1); - } - }); - } - - // Adding _autoscope and _scope to object formed. - data._autoScope = _autoScope; - data._scope = convertToKeyValueString(scope).concat(convertToKeyValueString(_autoScope)); - - if (!modelSettings.disableManualPersonalization) { - ctx.hookState.scopeVars = filterAutoscopeFromCtx(callContext.ctx, autoscope); - } - // log.debug(ctx.options, 'Data with scope and _scope ' , JSON.stringify(data)); - next(); -} - -/** - * Observer function dataAccess. - * This function is invoked upon access of data in any model. - * It reads the autoscope and manual scopes from various contributors and - * forms query based on the values. - * If any additional parameters like ignore list or defaults are provided then - * they will be filtered from the list and query is formed accordingly. - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @returns {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function dataPersonalizationAccess(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for DataPersonalizationMixin is applied or not. - // If mixin is applied to current model then only data wil be scoped. - if (modelSettings.mixins.DataPersonalizationMixin === false) { - return next(); - } - - if (ctx.options && ctx.options.fetchAllScopes) { - return next(); - } - - ctx.query = ctx.query || {}; - const callContext = ctx.options; - - // Reading the autoscope values from the model definition settings. - const autoscope = modelSettings.autoscope; - - // Clone callContext.ctxso the any changes locally made will not affect callContext.ctx. - let context; - if (ctx.query.scope) { - context = Object.assign({}, ctx.query.scope); - if (autoscope.length) { - autoscope.forEach(function dataAccessForEach(key) { - context[key] = callContext.ctx[key]; - }); - } - } else { - context = Object.assign({}, callContext.ctx); - } - - // Convert contextContributors to lowercase. - context = convertToLowerCase(context); - - // Filter out autoscope from contextContributors. - var scopeVars; - if (modelSettings.disableManualPersonalization) { - scopeVars = {}; - } else { - scopeVars = filterAutoscopeFromCtx(context, autoscope); - } - - // adding manual scope to ctx for use in cache - ctx.hookState.scopeVars = Object.assign({}, scopeVars); - - const andParams = []; - - // Getting the ignore list from the callContext - let ignoreList = callContext.ignoreContextList || []; - - // Convert the ignore list to lowercase so that it will be easy to compare. - ignoreList = convertToLowerCase(ignoreList); - - // This is a temporary solution to data load at boot time for no tenantId case. - // Get new list of autoscope by removing ignoreList values from autoscope. - // var autoscope = _.difference(autoscope, ignoreList); - - // get default autoscope value from config files - const defaultValue = ctx.Model.app.get('defaultAutoScope') || ''; - - if (autoscope.length) { - var err; - var len = autoscope.length; - for (var i = 0; i < len; i++) { - var key = autoscope[i]; - if (!context[key] && !callContext.ignoreAutoScope) { - err = new Error(); - err.name = 'Data Personalization error'; - err.message = `insufficient data! Autoscoped values not found for the model${ctx.Model.modelName} key ${key}`; - err.code = 'DATA_PERSONALIZATION_ERROR_029'; - err.type = 'AutoScopeValuesNotFound'; - err.retriable = false; - break; - } - } - if (err) { - return next(err); - } - } - - // pushing the query parameters into ignorelist.so tht manually passed query will not conflict with context inferred - if (ctx.query && ctx.query.where) { - let arr = []; - const igList = []; - getKeys(ctx.query.where, arr, igList); - arr = _.unique(arr); - ignoreList = ignoreList.concat(arr); - callContext[`whereKeys${ctx.Model.modelName}`] = _.unique(igList); - } - - // This forms the second part of the 'and' condition in the query. - // Check for the callContext.defaults - // If callContext.defaults is false then query is formwed with manual scope parameters. - // If callContext.defaults is true then query will be not be formed with manual scope parameters. - let finalQuery = {}; - const dataSourceName = ctx.Model.dataSource.connector.name; - const dataSourceTypes = ['mongodb', 'postgresql', 'oracle']; - if (dataSourceTypes.indexOf(dataSourceName) !== -1) { - let exeContextArray = convertToKeyValueString(scopeVars); - let autoscopeArray = []; - autoscope.forEach((element) => { - if (context && Array.isArray(context[element])) { - const valueArray = []; - context[element].forEach((item) => { - valueArray.push(`${element}:${item}`); - }); - autoscopeArray = autoscopeArray.concat(valueArray); - exeContextArray.push(`${element}:${defaultValue}`); - exeContextArray = exeContextArray.concat(valueArray); - } else { - exeContextArray.push(`${element}:${defaultValue}`); - if (context) { - exeContextArray.push(`${element}:${context[element]}`); - autoscopeArray.push(`${element}:${context[element]}`); - } - } - }); - ctx.hookState.autoscopeArray = autoscopeArray; - if (callContext['whereKeys' + ctx.Model.modelName]) { - exeContextArray = exeContextArray.concat(callContext['whereKeys' + ctx.Model.modelName]); - } - if (ctx.Model.dataSource.connector.name === 'mongodb') { - if (ctx.query.scope && Object.keys(ctx.query.scope).length === 0) { - let autoscopeCondition = []; - autoscope.forEach(item => { - if (Array.isArray(context[item])) { - let itemsArr; - item.forEach(elem => itemsArr.push(`${item}:${elem}`)); - itemsArr.push(`${item}:${defaultValue}`); - autoscopeCondition.push({ '_scope': { elemMatch: { $in: itemsArr } } }); - } else { - autoscopeCondition.push({ '_scope': { elemMatch: { $in: [`${item}:${context[item]}`, `${item}:${defaultValue}`] } } }); - } - }); - finalQuery = { - 'where': { 'and': autoscopeCondition } - }; - } else { - finalQuery = { - 'where': { '_scope': { 'not': { '$elemMatch': { '$nin': exeContextArray } } } } - }; - } - } else { - finalQuery = { - where: { _scope: { contains: exeContextArray } } - }; - } - } else { - if (autoscope.length) { - const autoAnd = []; - let autoscopeArray = []; - // This forms the first part of the 'and' condition in the query. - // loops through each value in autoscope and forms an 'and' condition between each value. - const asvals = {}; - autoscope.forEach((key) => { - if (callContext.ignoreAutoScope) { - // When ignoreAutoScope is true then only query with autoscope deafult - // is formed and only default records are sent. - asvals._scope = { - inq: [`${key}:${defaultValue}`] - }; - } else { - const value = context[key]; - if (Array.isArray(value)) { - const valueArray = []; - value.forEach((item) => { - valueArray.push(`${key}:${item}`); - }); - autoscopeArray = autoscopeArray.concat(valueArray); - valueArray.push(`${key}:${defaultValue}`); - asvals._scope = { - inq: valueArray - }; - } else { - autoscopeArray.push(`${key}:${value}`); - asvals._scope = { - inq: [`${key}:${value}`, `${key}:${defaultValue}`] - }; - } - } - autoAnd.push(asvals); - }); - ctx.hookState.autoscopeArray = autoscopeArray; - - // Push and condition formed with autoscopes into andParams array. - andParams.push({ - and: autoAnd - }); - } - - - if (scopeVars && !(_.isEmpty(scopeVars))) { - const manualAnd = []; - // loops through each value in scopeVars and forms an 'and' condition between each value in scopeVars. - Object.keys(scopeVars).forEach((key) => { - const msVals = {}; - const msRegExpVal = {}; - const msOrParams = []; - const value = scopeVars[key]; - // Filter for removing ignorelist values from scopeVars values. - if (!(_.contains(ignoreList, key))) { - let regEx; - if (Array.isArray(value)) { - if (value.length) { - const valueArray = []; - value.forEach((item) => { - valueArray.push(`${key}:${item}`); - }); - msVals._scope = { - inq: valueArray - }; - regEx = new RegExp(`^${key}:`); - msRegExpVal._scope = { - nin: [regEx] - }; - msOrParams.push(msVals); - msOrParams.push(msRegExpVal); - manualAnd.push({ - or: msOrParams - }); - } - } else { - msVals._scope = { - inq: [`${key}:${value}`] - }; - regEx = new RegExp(`^${key}:`); - msRegExpVal._scope = { - nin: [regEx] - }; - msOrParams.push(msVals); - msOrParams.push(msRegExpVal); - manualAnd.push({ - or: msOrParams - }); - } - } - }); - - andParams.push({ - and: manualAnd - }); - } - finalQuery = { - where: { - and: andParams - } - }; - } - - // Merging the query formed with the existing query if any. - mergeQuery(ctx.query, finalQuery); - log.debug(ctx.options, 'Final formed query', ctx.query); - next(); -} - -/** - * Observer function to handle score calculation and orderBy . - * This function is invoked after access of data in any model. - * It reads the scopes and their corresponding weights from various - * contextContributors and and calculates score based on the sum of the - * weights and then orders it in descending order based on score calculated. - * - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @returns {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @function - */ -function dataPersonalizationAfterAccess(ctx, next) { - const modelSettings = ctx.Model.definition.settings; - - // Checking for DataPersonalizationMixin is applied or not. - // If mixin is applied to current model then only data will be scoped. - if (modelSettings.mixins.DataPersonalizationMixin === false) { - return next(); - } - - if (ctx.options && ctx.options.fetchAllScopes) { - return next(); - } - - // Reads the data which we get based the query fromed in dataAccess function. - const result = ctx.accdata; - // if (variantData && variantData.length) { - // if (!ctx.accdata) { - // ctx.accdata = []; - // } - // for (var i = 0; i < variantData.length; ++i) { - // ctx.accdata.push(variantData[i]); - // } - // } - - if (result && result.length) { - // Get the loopback current context and reads the callContext from the context. - const callContext = {}; - const callCtx = ctx.options; - - // Clone callContext.ctx so the any changes locally made will not affect callContext.ctx. - callContext.ctx = Object.assign({}, callCtx.ctx); - - // Convert the callcontext.ctx to lowercase. - callContext.ctx = convertToLowerCase(callContext.ctx); - - // Clone callContext.ctxWeights so the any changes locally made will not affect callContext.ctx. - callContext.ctxWeights = Object.assign({}, callCtx.ctxWeights); - - // Convert the callcontext.ctxWeights to lowercase. - callContext.ctxWeights = convertToLowerCase(callContext.ctxWeights); - - // Reading the autoscope values from the model definition settings. - const autoscope = modelSettings.autoscope; - - // Reading the autoscope values from the model definition settings. - // var scoreScheme = modelSettings.scoreScheme ? modelSettings.scoreScheme : 'sum'; - - let resultData = []; - const weights = {}; - - - // get default autoscope value from config files - const defaultValue = ctx.Model.app.get('defaultAutoScope') || ''; - - if (callContext.ctx) { - // Loops through each value in callContext.ctx to calculate scope and - // weights ignoring for the values in ignore list. - if (callContext.ctxWeights) { - Object.keys(callContext.ctx).forEach((key) => { - const value = callContext.ctx[key]; - if (Array.isArray(value)) { - value.forEach((item, index) => { - weights[`${key}:${item}`] = (callContext.ctxWeights[key] && callContext.ctxWeights[key][index]) || 1; - }); - } else { - weights[`${key}:${value}`] = callContext.ctxWeights[key] || 1; - } - }); - } - - autoscope.forEach((key) => { - const weight = (callContext.ctxWeights && callContext.ctxWeights[key]) ? callContext.ctxWeights[key] - 1 : -1; - weights[`${key}:${defaultValue}`] = weight.toString(); - }); - } - const dataSourceName = ctx.Model.dataSource.connector.name; - const dataSourceTypes = ['mongodb', 'postgresql', 'oracle']; - if (dataSourceTypes.indexOf(dataSourceName) !== -1) { - resultData = calculateScoreMongo(result, weights); - } else { - let scope = {}; - // Get the manually applied filter keys - const whereKeys = JSON.parse(JSON.stringify(callCtx[`whereKeys${ctx.Model.modelName}`] || [])); - - // Reads the ignore list from the callContext. - let ignoreList = JSON.parse(JSON.stringify(callCtx.ignoreContextList || [])); - // Converts ignore list to lowercase - ignoreList = convertToLowerCase(ignoreList); - - Object.keys(callContext.ctx).forEach((key) => { - const value = callContext.ctx[key]; - if (!(_.contains(ignoreList, key))) { - scope[key] = value; - } - }); - - // Convert scope obj to array of strings. - scope = convertToKeyValueString(scope); - - // Adding all autoscope.default values to scope. - autoscope.forEach((element) => { - scope.push(`${element}:${defaultValue}`); - }); - scope = scope.concat(whereKeys); - - // Loops through each record in result and calculate score based on subset - result.forEach((obj) => { - let score = 0; - let weight = 0; - // read _scope from record - const _scope = obj._scope || []; - if (!(_.difference(_scope, scope).length)) { - // Find out the intersection part of _scope and our own calculated scope. - // var intersection = _.intersection(_scope, scope); - _scope.forEach((element) => { - score = Math.max(score, parseInt(weights[element] || '1', 10)); - weight += parseInt(weights[element] || '1', 10); - }); - obj._score = score; - obj._weight = weight; - resultData.push(obj); - } - }); - } - - // Sort in descending order based on score . - // resultData =_.orderBy(resultData, ['score', 'weight'], ['desc', 'desc']); //Lodash v4.6.1 - // Lodash v3.10.1 - resultData = _.sortByOrder(resultData, ['_score', '_weight'], ['desc', 'desc']); - resultData.forEach((obj) => { - delete obj._score; - delete obj._weight; - }); - if (ctx.query.scope && Object.keys(ctx.query.scope).length !== 0) { - ctx.accdata = resultData; - } else { - ctx.accdata = calculateUnique(ctx.Model.definition.properties, resultData); - } - } - next(); - // function isSameCollection(settings1, settings2) { - // if (!settings1.mongodb || !settings2.mongodb) { - // return false; - // } - // var collection1 = settings1.mongodb.collection; - // if (collection1) { collection1 = collection1.toLowerCase(); } - // var collection2 = settings2.mongodb.collection; - // if (collection2) { collection2 = collection2.toLowerCase(); } - // if (collection1 === collection2) { return true; } - // return false; - // } - - // var loopback = require('loopback'); - // if (modelSettings.variantOf) { - // var variantModel = loopback.findModel(modelSettings.variantOf); - // if (variantModel) { - // if (isSameCollection(variantModel.definition.settings, modelSettings)) { - // return prepareResponse(); - // } - // variantModel.find(ctx.query, ctx.options, function (err, variantData) { - // if (err) { - // return next(new Error(err)); - // } - // return prepareResponse(variantData); - // }); - // return; - // } - // } - - // return prepareResponse(); -} - -/** - * Function to get "scope" keys and keyValue pairs from the query. - * @param {object} data - query from which we need to gets keys. - * @param {array} arr - Array to hold keys context keys from query. - * @param {array} igList - Array to hold context keysValue pair formatted keys from the query. - * @function - */ -var getKeys = function dataAccessGetKeys(data, arr, igList) { - _.forEach(data, (value, key) => { - if ((typeof key === 'string') && (key !== 'and' || key !== 'or')) { - if (key.indexOf('scope.') > -1) { - Array.prototype.splice.apply(arr, [0, 0].concat(key.split('.'))); - if (typeof value !== 'object') { - Array.prototype.splice.apply(igList, [0, 0].concat(`${key.split('.')[key.split('.').length - 1]}:${value}`)); - } - } - } - if (typeof value === 'object') { - getKeys(value, arr, igList); - } - }); -}; - -/** - * Function to calculate score and weightage for the result and sort it in - * descending order based on score and weightages. - * @param {array} result - actual unsorted result. - * @param {object} weights - 'Key:value' keys with respective weightages . - * @returns {array} result - The final sorted resultant array. - * @function - */ -var calculateScoreMongo = function calcScoreMongo(result, weights) { - // Loops through each record in result and calculate score based on subset - result.forEach((obj) => { - let score = 0; - let weight = 0; - // read _scope from record - const _scope = obj._scope || []; - - // Find out the intersection part of _scope and our own calculated scope. - // var intersection = _.intersection(_scope, scope); - _scope.forEach((element) => { - score = Math.max(score, parseInt(weights[element] || '1', 10)); - weight += parseInt(weights[element] || '1', 10); - }); - obj._score = score; - obj._weight = weight; - }); - return result; -}; - -/** - * Function to get unique properties from the definition and filter out - * the result based on the unique properties defined on the model. - * @param {object} modelProp - The current model properties. - * @param {array} resultData - actual sorted result data. - * @returns {array} resultData - The final filtered resultant array. - * @function - */ -var calculateUnique = function calcUniqFn(modelProp, resultData) { - let uniq = []; - - // Reads each property for unique and populates uniq array. - Object.keys(modelProp).forEach((key) => { - const prop = modelProp[key]; - if (prop.unique) { - if (typeof prop.unique === 'boolean' || typeof prop.unique === 'string') { - uniq.push(key); - } else if (typeof prop.unique === 'object') { - prop.unique.scopedTo ? uniq = uniq.concat(prop.unique.scopedTo) : null; - uniq.push(key); - } - } - }); - - // Filter out the redundent records from result by applying unique validation. - if (uniq.length > 0) { - resultData = _.uniq(resultData, value => uniq.map(u => value[u]).join('-')); - // resultData = _.intersection.apply(this, _.chain(uniq).map(function (v) { return _.uniq(resultData, v) }).value()); - } - - return resultData; -}; diff --git a/common/mixins/expression-ast-populator-mixin.js b/common/mixins/expression-ast-populator-mixin.js deleted file mode 100644 index f6d7461..0000000 --- a/common/mixins/expression-ast-populator-mixin.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin is for expression language, where we collect all the grammar rules attached to the model - * and create their ASTs.All the ASTs are attached to the model in the object "_ast". - * "_ast" takes the expression as key and its AST as the value. - * @mixin Expression ast Populator Mixin - * @author Sambit Kumar Patra - */ - -var logger = require('oe-logger'); -var log = logger('expression-ast-populator-mixin'); -var exprLang = require('../../lib/expression-language/expression-language.js'); - -module.exports = function ExpressionAstPopulator(Model) { - Model._ast = {}; - log.debug(log.defaultContext(), 'building AST for ', Model.modelName); - var properties = Model.definition.properties; - // process all the validateWhen grammar rules at property level and attach their ASTs to the model - Object.keys(properties).forEach(function propertiesForEachCb(propertyName) { - Object.keys(properties[propertyName]).forEach(function propertyNameForEachCb(key) { - if (properties[propertyName].validateWhen && properties[propertyName].validateWhen[key]) { - // pick the validateWhen condition if present for the validation rule - var validateWhenRule = properties[propertyName].validateWhen[key]; - Model._ast[validateWhenRule] = exprLang.createAST(validateWhenRule); - log.debug(log.defaultContext(), 'validateWhen ast building for ', key, ' rule of ', Model.modelName, '->', propertyName); - } - // this is for property expressions which will be evaluated and assigned to property - if (properties[propertyName].propExpression) { - log.debug(log.defaultContext(), 'applying expression to property name ', propertyName, ' for model ', Model.modelName); - var propExpression = properties[propertyName].propExpression; - Model._ast[propExpression] = exprLang.createAST(propExpression); - } - }); - }); - - var oeValidations = Model.definition.settings.oeValidations || {}; - - // process all the grammar rules present in oeValidations and and attach their ASTs to the model - Object.keys(oeValidations).forEach(function validationsForEachCb(validationName) { - var validationRule = oeValidations[validationName]; - // if oeValidation has a validateWhen condition then pick it up and create AST for it - if (validationRule.validateWhen) { - // validateWhen takes a string in case of ev validations - if (typeof validationRule.validateWhen === 'string') { - // pick the validateWhen condition and attach its AST to the model - var validateWhenRule = validationRule.validateWhen; - Model._ast[validateWhenRule] = exprLang.createAST(validateWhenRule); - log.debug(log.defaultContext(), 'validateWhen ast building for oeValidation rule ', Model.modelName, '->', validationName); - } - } - // if the oeValidation is of 'custom' type then pick its expression which a grammar rule and create AST for that expression - if (validationRule.type === 'custom') { - var expression = validationRule.expression; - // pick the expression for custom type oeValidation and attach its AST to the model - Model._ast[expression] = exprLang.createAST(expression); - log.debug(log.defaultContext(), 'ast building for oeValidation custom rule ', Model.modelName, '->', validationName); - } - }); - - // var otpEnabledMethods = Model.definition.settings.enableOTP || []; - // otpEnabledMethods.forEach(function otpMethodIterate(otpConfig) { - // var expression = otpConfig.authWhen; - // if (expression) { - // Model._ast[expression] = exprLang.createAST(expression); - // } - // }); -}; diff --git a/common/mixins/failsafe-observer-mixin.js b/common/mixins/failsafe-observer-mixin.js deleted file mode 100644 index d376ca8..0000000 --- a/common/mixins/failsafe-observer-mixin.js +++ /dev/null @@ -1,289 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var logger = require('oe-logger'); -var log = logger('failsafe-observer-mixin'); -var async = require('async'); -var uuidv4 = require('uuid/v4'); -var os = require('os'); -var process = require('process'); -var currHostName = process.env.HOSTNAME || os.hostname(); -var eventHistroyManager; -var disableEventHistoryManager = process.env.DISABLE_EVENT_HISTORY; -var observerTypes = ['after save', 'after delete']; -var loopback = require('loopback'); - -module.exports = function failsafeObserverMixin(Model) { - if (disableEventHistoryManager && disableEventHistoryManager === 'true') { - return; - } - eventHistroyManager = require('./../../lib/event-history-manager.js'); - if (Model.modelName === 'BaseEntity') { - return; - } - - // register createEventHistory method as a remote method on the model - Model.remoteMethod('createEventHistory', { - http: { - path: '/createEventHistory', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'context', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - - Model.prototype.createEventHistory = function (context, options, cb) { - var modelName = this._type; - var version = this._version; - var trigger = context.trigger; - - var ctx = {}; - ctx.instance = this; - ctx.Model = loopback.getModel(modelName); - ctx.fromRecovery = context.fromRecovery || false; - ctx.hasFailedObserverLog = context.hasFailedObserverLog || false; - if (this._fsCtx) { - try { - var fsCtx = JSON.parse(this._fsCtx); - Object.assign(ctx, fsCtx); - } catch (e) { - log.debug(log.defaultContext(), 'failed to parse _fsCtx: ', e); - ctx.options = { fetchAllScopes: true, ctx: { tenantId: 'default' } }; - } - } - - eventHistroyManager.create(modelName, version, trigger, ctx); - return cb(); - }; - - var FailSafeObserver = function (fn) { - function generateId(fn) { - if (fn.getId && typeof fn.getId === 'function') { - return fn.getId(); - } - return uuidv4(); - } - var _fn = fn; - var _id = generateId(_fn); - var _name = fn.name; - - this.execute = function (modelName, version, operation, ctx, next) { - _fn(ctx, function (error) { - if (!error) { - if (version) { - eventHistroyManager.update(modelName, version, operation, _id); - } - next(); - } else { - log.debug(ctx.options, 'failSafe observer: ', _fn.name, ' failed with error: ', error.message); - next(error); - } - }); - }; - - this.getId = function () { - return _id; - }; - - this.getName = function () { - return _name; - }; - }; - - Model._fsObservers = {}; - - Model.defineProperty('_hostName', { - type: String, - default: currHostName - }); - - Model.defineProperty('_fsCtx', { - type: 'string', - oracle: { - 'dataType': 'CLOB' - } - }); - - if (Model.definition.settings.hidden) { - Model.definition.settings.hidden = Model.definition.settings.hidden.concat(['_fsCtx', '_hostName']); - } else { - Model.definition.settings.hidden = ['_fsCtx', '_hostName']; - } - - Model.definition.options = Model.definition.options || {}; - Model.definition.options.proxyEnabled = true; - Model.definition.options.proxyMethods = Model.definition.options.proxyMethods || []; - Model.definition.options.proxyMethods.push({ name: 'createEventHistory' }); - - Model.failSafeObserve = function (eventName, observer) { - if (!(observer instanceof FailSafeObserver)) { - var err = new Error('observer should be an instanceof FailSafeObserver'); - err.retriable = false; - throw err; - } - - if (!this._fsObservers[eventName]) { - this._fsObservers[eventName] = { 'observers': [], 'observerIds': [] }; - } - - this._fsObservers[eventName].observers.push(observer); - this._fsObservers[eventName].observerIds.push(observer.getId()); - }; - - function converteObservers(type) { - if (typeof Model._observers[type] !== 'undefined') { - Model._observers[type].forEach(function (observer) { - var failSafeObserver = new FailSafeObserver(observer); - Model.failSafeObserve(type, failSafeObserver); - }); - Model._observers[type] = []; - } - } - - function changeObserve() { - var _observe = Model.observe; - Model.observe = function (operation, method) { - if (observerTypes.find(op => op === operation) && !method.name.startsWith('_failsafeObserver')) { - var failSafeObserver = new FailSafeObserver(method); - Model.failSafeObserve(operation, failSafeObserver); - } else { - _observe.call(this, operation, method); - } - }; - } - - Model.evObserve('before save', failsafeObserverBeforeSave); - Model.evObserve('before save', updateHostName); - Model.evObserve('before delete', failsafeObserverBeforeDelete); - - observerTypes.forEach(type => converteObservers(type)); - changeObserve(); - - Model.evObserve('after save', _failsafeObserverAfterSave); - Model.evObserve('after delete', _failsafeObserverAfterDelete); -}; - -function updateHostName(ctx, next) { - if (ctx.currentInstance && currHostName !== ctx.currentInstance._hostName) { - ctx.data._hostName = currHostName; - } - return next(); -} - -function failsafeObserverBeforeDelete(ctx, next) { - var version; - var fsCtx = {}; - fsCtx.options = ctx.options; - fsCtx.isNewInstance = ctx.isNewInstance ? ctx.isNewInstance : false; - if (typeof ctx.instance !== 'undefined') { - version = ctx.instance._version; - ctx.instance._fsCtx = JSON.stringify(fsCtx); - } else if (typeof ctx.data !== 'undefined') { - version = ctx.data._version; - if (ctx.where) fsCtx.where = ctx.where; - ctx.data._fsCtx = JSON.stringify(fsCtx); - } - if (!version) { - return next(); - } - eventHistroyManager.create(ctx.Model.modelName, version, 'after delete', ctx); - next(); -} - -function failsafeObserverBeforeSave(ctx, next) { - var version; - var fsCtx = {}; - fsCtx.options = ctx.options; - fsCtx.isNewInstance = ctx.isNewInstance ? ctx.isNewInstance : false; - if (typeof ctx.instance !== 'undefined') { - version = ctx.instance._version; - ctx.instance._fsCtx = JSON.stringify(fsCtx); - } else if (typeof ctx.data !== 'undefined') { - version = ctx.data._version; - if (ctx.where) fsCtx.where = ctx.where; - ctx.data._fsCtx = JSON.stringify(fsCtx); - } - if (!version) { - return next(); - } - - eventHistroyManager.create(ctx.Model.modelName, version, 'after save', ctx); - next(); -} - -function _failsafeObserverAfterSave(ctx, next) { - invokeFailsafeObserver(ctx, 'after save', next); -} - -function _failsafeObserverAfterDelete(ctx, next) { - invokeFailsafeObserver(ctx, 'after delete', next); -} - -function invokeFailsafeObserver(ctx, operation, next) { - if (ctx.Model.definition.settings.mixins.FailsafeObserverMixin) { - notifyFailsafeObservers(ctx.Model, ctx.Model, operation, ctx, function (err) { - var version = (ctx.instance && ctx.instance._version) || (ctx.data && ctx.data._version); - if (!err) { - if (version) { - eventHistroyManager.remove(ctx.Model.modelName, version, operation); - } - return next(); - } - if (err.retriable === false) { - if (version) { - eventHistroyManager.remove(ctx.Model.modelName, version, operation, 'error: ' + err.message + ' is not retriable.'); - } - return next(err); - } - eventHistroyManager.updateRanOnce(ctx.Model.modelName, version, operation); - return next(); - }); - } else { - return next(); - } -} - -function notifyFailsafeObservers(childModel, model, operation, ctx, cb) { - notifyBaseFailsafeObservers(childModel, model, operation, ctx, function (error) { - if (error) { - return cb(error); - } - var fsObservers = (model._fsObservers[operation] && model._fsObservers[operation].observers) || []; - var version = (ctx.instance && ctx.instance._version) || (ctx.data && ctx.data._version); - async.eachSeries(fsObservers, function notifySingleObserver(fn, callback) { - var retval = fn.execute(childModel.modelName, version, operation, ctx, callback); - - if (retval && typeof retval.then === 'function') { - retval.then(function () { - callback(); - }, callback); - } - }, function (err) { - if (err) { - cb(err); - } else { - cb(); - } - }); - }); -} - -function notifyBaseFailsafeObservers(childModel, model, operation, ctx, cb) { - if (model.base && model.base._fsObservers) { - notifyFailsafeObservers(childModel, model.base, operation, ctx, cb); - } else { - cb(); - } -} diff --git a/common/mixins/history-mixin.js b/common/mixins/history-mixin.js deleted file mode 100644 index 4b346d7..0000000 --- a/common/mixins/history-mixin.js +++ /dev/null @@ -1,295 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/** - * This mixin is to provide History management support for any update and delete - * operation on an model instance.
    - * - * It creates a new model for history management, with appended 'History' to - * actual model name. So for example if there is a Product model, it creates - * ProductHistory model in the same datasource as of actual model.
    - * The properties of History model are same as the actual model, but without any - * validations as history will be created from the last record in database which - * would have already passed validations.
    - * - * It added a new property _modelId to history model, to keep track of actual id - * of the record.
    - * - * It also adds a new remote method /history to actual model which can be used - * to retrieve history. remote method also support filters similar to find - * filter. - * - * @mixin History Mixin - * @author Sivankar jain - */ -var loopback = require('loopback'); -// var _ = require('lodash'); - -var logger = require('oe-logger'); -var log = logger('history-mixin'); - -module.exports = function HistoryMixin(Model) { - // Skip this mixin where ever not applicable. - if (skipThisMixinIfNotApplicable(Model)) { - return; - } - - // Add a remote method to retrive history. - addHistoryMethodAndExposeAsRemote(Model); - - // Disable updateAll as we will not allow bulk updates. Only updates on id - // is allowed. - Model.disableRemoteMethod('updateAll', true); - - // Create history model when the model is added to the loopback application. - Model.on('attached', function historyMixinAttached() { - createHistoryModel(Model); - }); - - addObservers(Model); -}; - -/** - * Checks if mixin needs to be skipped or not. Mixin is skipped for BaseEntity, - * CacheManager. - * - * @param {object}Model - Model Constructor - * @returns {Boolean} - true is model is BaseEntity else false - * @memberof History Mixin - */ -function skipThisMixinIfNotApplicable(Model) { - if (Model.definition.name === 'BaseEntity' || Model.definition.name === 'CacheManager') { - Model.definition.settings.HistoryMixin = true; - log.debug(log.defaultContext(), 'skipping mixin for - ', Model.definition.name); - return true; - } - return false; -} - -/** - * Adds a remote method to model and exposes /history to retrieve model history. - * - * @param {ModelConst} Model - Model constructor on which remote method is added. - * @memberof History Mixin - */ -function addHistoryMethodAndExposeAsRemote(Model) { - Model.history = function historyMixinHistory(filter, options, cb) { - if (!(cb && typeof cb === 'function')) { - if (options && typeof options === 'function') { - cb = options; - options = {}; - } else { - var err = new Error(' callBack function is not defined'); - err.retriable = false; - throw err; - } - } - // get name of the History model - var HistoryModel = loopback.findModel(Model.modelName + 'History'); - if (!HistoryModel) { - var err1 = new Error('No History model found for ' + Model.modelName); - err1.retriable = false; - throw err1; - } - HistoryModel.find(filter, options, cb); - }; - - // Register a REST end-point /history for the above method - Model.remoteMethod('history', { - http: { - path: '/history', - verb: 'get' - }, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); -} -/** - * It creates a new model, with appended 'History' to actual model name. So for - * example if there is a Product model, it creates ProductHistory model in the - * same datasource as of actual model.
    - * The properties of History model are copied from actual model, but without any - * validations.
    - * - * It added a new property _modelId to history model, to keep track of actual id - * of the record.
    - * - * @param {ModelConst} model - Model constructor on which remote method is added. - * @memberof History Mixin - */ -function createHistoryModel(model) { - var auditModelName = model.modelName + 'History'; - log.debug(log.defaultContext(), 'creating history model for with additional property _modelId - ', model.modelName); - - var properties = {}; - var idName = model.definition.idName(); - // Retain all properties from original model. - // Also add a new property - _modelId to hold the original request id. - // Note that original model should not have a modelId property - - properties._modelId = { - type: 'string', - index: true - }; - - // to remove any validation or so in properties for history table. - var prop = model.definition.properties; - for (var key in prop) { - if (prop.hasOwnProperty(key)) { - if (key === idName) { - properties._modelId.type = model.definition.properties[idName].type; - continue; - } - for (var key1 in model.definition.properties[key]) { - if (key1 === 'type') { - properties[key] = {}; - properties[key][key1] = model.definition.properties[key][key1]; - } - } - } - } - - var dataSourceGroup = model.definition.settings.dataSourceGroup; - - // Create a new Model in loopback. - var newModel = loopback.createModel(auditModelName, properties, { - base: 'PersistedModel', - mixins: { - 'SwitchDatasourceMixin': true - }, - dataSourceGroup: dataSourceGroup - }); - - model._historyModel = newModel; - // Attach it to datasource to which model is attached, - // skipping Models that do not have a datasource attached - - if (model.dataSource) { - model.dataSource.attach(newModel); - } - - log.debug(log.defaultContext(), 'Created History Model ', auditModelName, ' for model ', model.modelName); -} - -/** - * Function adds 'before save' and 'before delete' observers hooks which is used - * to create history data, which is created in after save. - * - * @param {ModelConst} Model - Model constructor on which remote method is added. - * @memberof History Mixin - */ -function addObservers(Model) { - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.HistoryMixin) || !Model.settings.mixins.HistoryMixin) { - Model.evRemoveObserver('before save', createHistoryData); - Model.evRemoveObserver('before delete', createHistoryDataForDelete); - Model.evRemoveObserver('after save', insertIntoHistory); - Model.evRemoveObserver('after delete', insertIntoHistoryForDelete); - } else { - Model.evObserve('before save', createHistoryData); - Model.evObserve('before delete', createHistoryDataForDelete); - Model.evObserve('after save', insertIntoHistory); - Model.evObserve('after delete', insertIntoHistoryForDelete); - } -} - -function createHistoryData(ctx, next) { - if (!ctx.Model.definition.settings.mixins.HistoryMixin) { - return next(); - } - if (ctx.isNewInstance) { - return next(); - } - var historyModel = ctx.Model._historyModel; - if (!historyModel) { - return next(); - } - if (ctx.currentInstance) { - ctx.hookState.historyData = [ctx.currentInstance.toObject()]; - return next(); - } else if (ctx.where) { - // Earlier history mixin was doing findOne - // which is wrong as updateAll is being done... - // and that may have multiple records.. - // TODO decide whether we need to support history for - // bulk operations - ctx.Model.find({ - 'where': ctx.where - }, ctx.options, function historyMixinFindOneCb(err, recs) { - if (err) { - return next(err); - } - ctx.hookState.historyData = []; - recs.forEach(function recsForEach(e) { - ctx.hookState.historyData.push(e.toObject()); - }); - return next(); - }); - } else { - return next(); - } -} - -/** - * This function is used to create history. So when ever there is a update or - * delete request, first it fetch the record from database and move it to - * history and then update the record in Database. - * - * @param {object} ctx - ctx object, which is populated by DAO. - * @param {function} next - move to the next function in the queue - * @returns {function} next - move to the next function in the queue - * @memberof History Mixin - */ -function insertIntoHistory(ctx, next) { - if (!ctx.hookState) { - return next(); - } - - var historyModel = ctx.Model._historyModel; - if (!historyModel) { - return next(); - } - - var recs = ctx.hookState.historyData || []; - recs.forEach(function recsForEachcb(hist) { - var idName = ctx.Model.definition.idName() || 'id'; - var idValue = hist[idName]; - hist._modelId = idValue; - delete hist[idName]; - hist._isDeleted = false; - }); - - if (recs.length) { - historyModel.create(recs, ctx.options, function historyModelCreatecb(err, res) { - if (err) { - log.error(ctx.options, 'error on insert into history model ', err); - return next(err); - } else if (res) { - log.debug(ctx.options, 'history instance created '); - return next(); - } - }); - } else { - return next(); - } -} - -function createHistoryDataForDelete(ctx, next) { - return createHistoryData(ctx, next); -} - -function insertIntoHistoryForDelete(ctx, next) { - return insertIntoHistory(ctx, next); -} - - diff --git a/common/mixins/idempotent-mixin.js b/common/mixins/idempotent-mixin.js deleted file mode 100644 index 0650528..0000000 --- a/common/mixins/idempotent-mixin.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/** - * This mixin is to support eventual consistency. This mixin support patterns of - * Idempotent behavior or replay behavior, which will enable framework to perform actions - * multiple times and preserve the same outcome. The system maintains this - * behavior in CRUD operations. - * - * Idempotent behavior – The ability to perform the same operation multiple time - * and always receive the same results - * - * @mixin Idempotent mixin - * @author Praveen - */ - -var mergeQuery = require('loopback-datasource-juggler/lib/utils').mergeQuery; - -module.exports = function IdempotencyMixin(Model) { - Model.checkIdempotency = function modelCheckIdempotency(ctx, cb) { - var data = ctx.data || ctx.instance; - if (ctx.currentInstance && data) { - if (ctx.currentInstance._version === data._newVersion) { - return cb(null, ctx.currentInstance); - } - } - - if (ctx.isNewInstance) { - data._newVersion = data._newVersion || data._version; - } - - if (data._newVersion && ctx.isNewInstance) { - // create case - return Model.findInHistory(ctx, cb); - } else if (data._newVersion) { - // update case by id - if (ctx.currentInstance && ctx.currentInstance._version === data._newVersion) { - return cb(null, ctx.currentInstance); - } - return Model.findInHistory(ctx, cb); - } - return cb(); - }; - - Model.findInHistory = function findInHistory(ctx, cb) { - var data = ctx.data || ctx.instance; - if (!Model._historyModel) { - return cb(); - } - var whereClause = { - '_version': data._newVersion - }; - Model._historyModel.find({ - where: whereClause - }, ctx.options, function historyModelFindcb(err, result) { - if (err) { - return cb(err); - } - if (result && result.length) { - if (ctx.currentInstant) { - return cb(null, ctx.currentInstant); - } - var hinst = result[0]; - Model.findById(hinst._modelId, ctx.options, function modelFindByIdcb(err, latestInst) { - return cb(err, latestInst); - }); - } else { - return cb(); - } - }); - }; - - Model.checkIdempotencyAfter = function modelCheckIdempotencyAfter(err, ctx, cb) { - var data = ctx.data || ctx.instance; - if (ctx.isNewInstance && err) { - var whereClause = { - '_version': data._version - }; - Model.find({ - where: whereClause - }, ctx.options, function modelFindcb(error, result) { - if (error) { - return cb(err); - } else if (result && result.length) { - return cb(null, result[0]); - } - return cb(err); - }); - } else if (!ctx.isNewInstance) { - Model.findInHistory(ctx, function (err, res) { - if (res === null) { - return cb(err); - } - return cb(null, res); - }); - } else { - return cb(err); - } - }; - Model.checkIdempotencyForDelete = function modelCheckIdempotencyForDeletecb(context, cb) { - var filter; - if (context.id) { - filter = { where: context.where, fetchDeleted: true }; - Model.findOne(filter, context.options, function modelFindOnecb(err, res) { - if (err) { - return cb(err); - } - if (res) { - return cb(err, { count: 1 }); - } - return cb(); - }); - } else if (context.options && context.options.requestId) { - filter = { where: context.where, fetchDeleted: true }; - mergeQuery(filter, { - where: { - _requestId: context.options.requestId - } - }); - Model.find(filter, context.options, function modelFindcb(err, res) { - if (err) { - return cb(err); - } - if (res.length > 0) { - return cb(err, { count: res.length }); - } - return cb(); - }); - } else { - return cb(); - } - }; -}; diff --git a/common/mixins/model-validations.js b/common/mixins/model-validations.js deleted file mode 100644 index 533e1bf..0000000 --- a/common/mixins/model-validations.js +++ /dev/null @@ -1,550 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/* eslint-disable no-eval */ -/** - * This mixin is for validations, where we override the isValid function of loopback. - * All the validations defined on the model will be aggregated and attached to the model, - * which will be parallely executed whenever any data is posted for the model. - * - * @mixin Model Validations - * @author Sambit Kumar Patra - */ - -var logger = require('oe-logger'); -var log = logger('model-validations'); -var async = require('async'); -var q = require('oe-promise'); -var validationBuilder = require('../../lib/common/validation-builder.js'); -var exprLang = require('../../lib/expression-language/expression-language.js'); -var getError = require('../../lib/common/error-utils').attachValidationError; -var loopback = require('loopback'); -var util = require('../../lib/common/util.js'); -// design-break this is experimental, may break functionality of expression validation -function isSimpleValidation(expression) { - var regex = new RegExp('(@m)|( where )|(if[ ]{0,1}\\()|(while[ ]{0,1}\\()|(switch[ ]{0,1}\\()|(void )|(delete )|(typeof )|(alert[ ]{0,1}\\()|(eval[ ]{0,1}\\()'); - return !regex.test(expression); -} - -// design-break this is experimental, may break functionality of expression validation -function generateSimpleValidation(expression) { - var regex = new RegExp('@i.', 'g'); - return expression.replace(regex, 'data.'); -} - -// design-break this is experimental, may break functionality of expression validation -function evalSimpleValidation(value, data) { - // this eval value is validated with possible validation expression and it returns a boolean value which is not inserted in DB - var res = eval(value); - if (typeof (res) === typeof (true) ) {return res;} - return false; -} -module.exports = function ModelValidations(Model) { - if (Model.modelName === 'BaseEntity') { - // No need to apply the "isValid" change at BaseEntity level - // Let the actual model decide if it wants to enable modelvalidation mixin - return; - } - - var validationRules = []; - // aggregate all the validations defined for the model and attach it to the model - validationRules = validationBuilder.buildValidations(Model); - - Model.validationRules = validationRules; - - /** - * - * This function overrides the isValid method of loopback, it will be called whenever `obj.isValid()` method is called - * - * @memberof Model Validations - * @param {function} done - function to be called once validation is over - * @param {Object} options - options object - * @param {Object} data - posted data - * @param {String} path - modelName of the present model for which isValid is called with its parent model's name appended incase of embedded model - * @param {Object} inst - instance of the data posted - * @param {function} callback - function to be called incase of embedded model validation - * @returns {Boolean} true or false depending upon the validity of the data - * @function - * @name modelValidationsIsValidFn - */ - - Model.prototype.isValid = function modelValidationsIsValidFn(done, context, data, path, inst, callback) { - var options; - if (!context) { - context = { options: {} }; - } - options = context.options; - // check if validations are to be executed, if not simply return the done callback. - if (options && options.skipValidations) { - return process.nextTick(function skipValidationCb() { - done(true); - }); - } - - var valid = true; - var fnArr = []; - var validateWhenPromises = []; - - if (!inst) { - inst = this; - } - - // To Do : modelData to be used for validation hooks - var modelData = data; - log.debug(options, 'modelData validation : ', modelData); - - data = inst.toObject(true); - - // path will give the exact level and property for which validation is being executed - if (!path) { - path = inst.constructor.modelName; - } - log.debug(options, 'isValid called for : ', path); - var self = inst; - var ast = self.constructor._ast; - var args = {}; - args.inst = inst; - args.data = data; - args.path = path; - args.options = options; - // construct an array of promises for validateWhen and wait for expression language to resolve all the promises - inst.constructor.validationRules.forEach(function modelValidationsRulesForEachFn(obj) { - if (obj.args.validateWhen) { - // design-break this is experimental implementation, may break functionality of expression validation - // check self.constructor._simpleValidation[obj.args.validateWhen] true - // if true, eval(inst.constructor._simpleValidation[obj.args.validateWhen]); - // check if simple expression true, - // if true, replace "@i.property" with "data.property" - // eval the result and self.constructor._simpleValidation[obj.args.validateWhen] = result - if (self.constructor._simpleValidation && self.constructor._simpleValidation[obj.args.validateWhen]) { - // console.log('already there -----', obj.args.validateWhen) - validateWhenPromises.push((function validateWhenPromisesCb() { - return q.fcall(function fCallCb() { - return evalSimpleValidation(self.constructor._simpleValidation[obj.args.validateWhen], data); - }); - })()); - } else if (isSimpleValidation(obj.args.validateWhen)) { - // console.log('simple -----', obj.args.validateWhen) - var res = generateSimpleValidation(obj.args.validateWhen); - self.constructor._simpleValidation = {}; - self.constructor._simpleValidation[obj.args.validateWhen] = res; - validateWhenPromises.push((function validateWhenPromisesCb() { - return q.fcall(function fCallCb() { - return evalSimpleValidation(res, data); - }); - })()); - } else { - // not a simple validation need to traverse the AST - // console.log('not simple *****', obj.args.validateWhen) - validateWhenPromises.push(exprLang.traverseAST(ast[obj.args.validateWhen], data, options)); - } - } else { - validateWhenPromises.push((function validateWhenPromisesCb() { - return q.fcall(function fCallCb() { - return true; - }); - })()); - } - }); - // when all promises are resolved check for the resolved value to know which validation rules are to be skipped - // based on the validateWhen clause - q.allSettled(validateWhenPromises).then(function modelValidationsValidateWhenPromisesCb(results) { - log.trace(options, 'all promises settled in isValid'); - results.map(function modelValidationsValidateWhenPromisesMapCb(d) { - return d.value; - }).forEach(function modelValidationsValidateWhenPromisesMapForEachCb(d, i) { - log.trace(options, 'preparing async function array for validation rules'); - if (d) { - // this wrapper prepares an array of functions containg all the validations attached to the model - var obj = inst.constructor.validationRules[i]; - fnArr.push(async.apply(obj.expression, obj.args, args)); - } - }); - /* prepare an array of functions which are nothing but the isValid method of the - properties which are of Model type*/ - var recursionFns = getRecursiveModelFns(context, inst, data, path); - if (inst.constructor.settings._isModelRuleExists) { - fnArr.push(async.apply(executeDTValidationRulesFn, inst.constructor, inst.__data || inst, options)); - } - // execute all the validation functions of the model parallely - async.parallel(fnArr, function modelValidationsAsyncParallelCb(err, results) { - if (err) { - results.push(err); - } - results = [].concat.apply([], results); - // execute all the isValid functions of the properties which are of Model type - if (recursionFns && recursionFns.length > 0) { - async.parallel(recursionFns, function modelValidationRecursionAsyncParallelCb(err, recurResults) { - if (err) { - recurResults.push(err); - } - results = results.concat([].concat.apply([], recurResults)); - var errArr = results.filter(function modelValidationAsyncParalllelErrCb(d) { - return d !== null && typeof d !== 'undefined'; - }); - // inst.errors will have custom errors if any - if (errArr.length > 0 || inst.errors) { - valid = false; - } - callback && callback(null, errArr); - if (done) { - log.trace(options, 'all validation rules executed'); - if (errArr && errArr.length > 0) { - log.debug(options, 'Data posted is not valid'); - // Add error to the response object - getError(self, errArr); - // done(valid); - } - // running custom validations of model(written in model js file) if any - if (Model.customValidations || inst.__data.customValidations) { - log.trace(options, 'executing custom validations for model'); - var customValArr = []; - var custValidations = inst.__data.customValidations || []; - delete inst.__data.customValidations; - custValidations = custValidations.concat(Model.customValidations || []); - custValidations.forEach(function customValidationForEachCb(customValidation) { - customValArr.push(async.apply(customValidation, inst.__data, context)); - }); - async.parallel(customValArr, function customModelValidationsAsyncParallelElseCb(err, customResults) { - if (err) { - customResults.push(err); - } - // Add error to the response object - customResults = [].concat.apply([], customResults); - var custErrArr = customResults.filter(function modelValidationAsyncParalllelCustomErrCb(d) { - return d !== null && typeof d !== 'undefined'; - }); - // inst.errors will have custom errors if any - if ((custErrArr && custErrArr.length > 0) || inst.errors) { - valid = false; - getError(self, custErrArr); - } - done(valid); - }); - } else { - done(valid); - } - } else { - return valid; - } - }); - } else { - var errArr = results.filter(function modelValidationAsyncParallelElseErrFilterFn(d) { - return d !== null && typeof d !== 'undefined'; - }); - // inst.errors will have custom errors if any - if (errArr.length > 0 || inst.errors) { - valid = false; - } - callback && callback(null, errArr); - if (done) { - log.trace(options, 'all validation rules executed'); - if (errArr && errArr.length > 0) { - log.debug(options, 'Data posted is not valid'); - // Add error to the response object - getError(self, errArr); - // done(valid); - } - // running custom validations of model(written in model js file) if any - if (Model.customValidations || inst.__data.customValidations) { - log.trace(options, 'executing custom validations for model'); - var customValArr = []; - var custValidations = inst.__data.customValidations || []; - delete inst.__data.customValidations; - custValidations = custValidations.concat(Model.customValidations || []); - custValidations.forEach(function customValidationForEachCb(customValidation) { - customValArr.push(async.apply(customValidation, inst.__data, context)); - }); - async.parallel(customValArr, function customModelValidationsAsyncParallelElseCb(err, customResults) { - if (err) { - customResults.push(err); - } - // Add error to the response object - customResults = [].concat.apply([], customResults); - var custErrArr = customResults.filter(function modelValidationAsyncParalllelCustomErrCb(d) { - return d !== null && typeof d !== 'undefined'; - }); - // inst.errors will have custom errors if any - if ((custErrArr && custErrArr.length > 0) || inst.errors) { - valid = false; - getError(self, custErrArr); - } - done(valid); - }); - } else { - done(valid); - } - } else { - return valid; - } - } - }); - }, function modelValidationsAsyncParallelReasonFn(reason) { - log.warn(options, 'Warning - Unable to resolve validateWhen promises with reason -', reason); - }).catch(function validatwWhenPromiseErrorCb(err) { - log.error(options, 'Error - Unable to resolve validateWhen promises with error -', err); - }); - }; - - function executeDTValidationRulesFn(model, inst, options, callback) { - var desicionTableModel = loopback.findModel('DecisionTable'); - var desicionServiceModel = loopback.findModel('DecisionService'); - var modelRule = loopback.findModel('ModelRule'); - - // begin - new change - // Traverse up the inheritance tree - var chain = []; - - util.traverseInheritanceTree(model, options, base => { - chain.push(base); - }); - - Promise.resolve(chain).then(chain => { - // Step 1. Getting all the base model names - // var results = []; - // var { tenantId } = options.ctx; - - var modelNames = chain.map(baseModel => baseModel.modelName); - - // adding the original model to the start of array - // modelNames.unshift(fnExtractUserFriendlyModelName(model.modelName)); - modelNames.unshift(model.modelName); - - return modelNames; - }) - .then(modelArray => { - // Step 2. Getting all validation rules - var results = []; - // begin - routine to fetch rules on model - var fetchTasks = modelArray.map(m => cb => { - var filter = { - where: { - modelName: m, - disabled: false - } - }; - // console.log('Fetching model rule: ', filter); - modelRule.findOne(filter, options, (err, rule) => { - if (err) { - cb(err); - } else { - if (rule) { - results = results.concat(rule.validationRules.map(r => - ( - { - model: m, - rule: r, - isService: rule.isService - } - ) - )); - } - cb(); - } - }); - }); - // end - routine to fetch rules on model - - return new Promise((resolve, reject) => { - async.seq(...fetchTasks)(err => { - if (err) { - reject(err); - } else { - resolve(results); - } - }); - }); - }) - .then(validationRules => { - // Step 3. execute the validation rules in no particular order - var data = inst; - data.options = options; - return new Promise(resolve => { - async.concat(validationRules, (ruleObj, cb) => { - // begin - rule invocation - data.options.modelName = ruleObj.model; - if (ruleObj.isService) { - // begin - decision service invoation - desicionServiceModel.invoke(ruleObj.rule, data, options, (err, postValidationResults) => { - if (err) { - cb(err); - } else { - var results = Object.values(postValidationResults).reduce((arr, item) => arr.concat(item), []); - var errorArr = results.map(obj => { - obj.fieldName = 'DecisionService'; - obj.model = ruleObj.model; - return obj; - }); - cb(null, errorArr); - } - }); - // end - decision service invoation - } else { - // begin - decision table invocation - desicionTableModel.exec(ruleObj.rule, data, options, (err, postValidationResults) => { - if (err) { - cb(err); - } else { - var errorArr = postValidationResults.map(function (obj) { - obj.fieldName = 'DecisionTable'; - obj.model = ruleObj.model; - return obj; - }); - cb(null, errorArr); - } - }); - // end - decision table invocation - } - // end - rule invocation - }, (err, results) => { - if (inst && inst.options) { - delete inst.options; - } - if (err) { - results.push(err); - } - // callback(null, results); - resolve(results); - }); - }); - }) - .then(results => { - // Step 5. We are done... - callback(null, results); - }) - .catch(err => { - callback(err); - }); - // end - new change - - // begin - legacy - // var filter = { - // where: { - // modelName: model.modelName, - // disabled: false - // } - // }; - // modelRule.find(filter, options, function modelRule(err, res) { - // if (err) { - // return callback(err); - // } - - - // if (res && res[0] && res[0].validationRules && res[0].validationRules.length > 0) { - // var rules = res[0].validationRules; - // inst.options = options; - // inst.options.modelName = model.modelName; - // async.concat(rules, function (rule, cb) { - // if (!res[0].isService) { - // desicionTableModel.exec(rule, inst, options, function (err, dataAfterValidationRule) { - // if (err) { - // return cb(err); - // } - // var errorArr = dataAfterValidationRule.map(function (obj) { - // obj.fieldName = 'DecisionTable'; - // return obj; - // }); - // cb(null, errorArr); - // }); - // } else { - // desicionServiceModel.invoke(rule, inst, options, function (err, dataAfterValidationRule) { - // if (err) { - // return cb(err); - // } - // var allDataAfterValidationRule = Object.values(dataAfterValidationRule).reduce((arr, item) => arr.concat(item), []); - // var errorArr = allDataAfterValidationRule.map(function (obj) { - // obj.fieldName = 'DecisionService'; - // return obj; - // }); - // cb(null, errorArr); - // }); - // } - // }, function (err, results) { - // if (inst && inst.options) { - // delete inst.options; - // } - // if (err) { - // results.push(err); - // } - // callback(null, results); - // }); - // } else { - // callback(null, null); - // } - // }); - // end - legacy - } - - - /** - * This function prepares an array which contains all the isValid methods, - * incase a property is of type Model its data can be validated by calling its isValid method - * @memberof Model Validations - * @param {Object} options - options object - * @param {Object} modelinstance - model instance of the posted model - * @param {Object} instanceData - posted data - * @param {String} instancePath - modelName of the present model for which isValid is called with its parent model's name appended incase of embedded model - * @returns {function[]} Array of functions. - * @function getRecursiveModelFns - */ - - function getRecursiveModelFns(context, modelinstance, instanceData, instancePath) { - var options = context.options; - var properties = modelinstance.constructor.definition.properties; - var modelfns = []; - var model; - var path; - var data; - var instance; - log.debug(options, 'preparing recursive validation rules for : ', modelinstance.constructor.modelName); - var relations = modelinstance.constructor.relations || {}; - var relationNames = Object.keys(relations); - Object.keys(properties).forEach(function modelValidationsRecursiveModelKeysFn(property) { - // if type of the property is an array which is of Model type then collect the isValid methods for the Model - // for example: if proerty is of type : ['Items'] where Item is a Model - var validateEmbeddedModel = true; - if (properties[property].type instanceof Array && - properties[property].type[0] && - properties[property].type[0].sharedClass && - instanceData[property]) { - relationNames.forEach(function getRelationFn(relationName) { - var rel = relations[relationName]; - if (rel.modelTo.modelName === properties[property].type[0].modelName && rel.type === 'embedsMany' && rel.options && rel.options.validate === false) { - validateEmbeddedModel = rel.options.validate; - } - }); - for (var i = 0; i < instanceData[property].length; i++) { - model = properties[property].type[0]; - path = instancePath + '->' + property + '[' + i + ']'; - data = instanceData[property][i]; - instance = modelinstance.__data[property][i]; - if (validateEmbeddedModel && instance && data && model.settings.mixins && model.settings.mixins.ModelValidations) { - log.debug(options, 'recursive validation rules added for : ', model.modelName); - modelfns.push(async.apply(model.prototype.isValid, null, context, data, path, instance)); - } - } - } else if (properties[property].type instanceof Function && - properties[property].type.sharedClass) { - // if property is of type Model then add its isValid method to the function array - model = properties[property].type; - path = instancePath + '->' + property; - data = instanceData[property]; - instance = modelinstance.__data[property]; - relationNames.forEach(function getRelationFn(relationName) { - var rel = relations[relationName]; - if (rel.modelTo.modelName === model.modelName && rel.type === 'embedsOne' && rel.options && rel.options.validate === false) { - validateEmbeddedModel = rel.options.validate; - } - }); - if (validateEmbeddedModel && instance && data && model.settings.mixins && model.settings.mixins.ModelValidations) { - log.debug(options, 'recursive validation rules added for : ', model.modelName); - modelfns.push(async.apply(model.prototype.isValid, null, context, data, path, instance)); - } - } - }); - return modelfns; - } -}; diff --git a/common/mixins/observer-mixin.js b/common/mixins/observer-mixin.js index 2256b1a..9995808 100644 --- a/common/mixins/observer-mixin.js +++ b/common/mixins/observer-mixin.js @@ -28,7 +28,6 @@ var logger = require('oe-logger'); var log = logger('observer-mixin'); - module.exports = function ObserverMixin(Model) { /** * evObserve: Register observers based on the value of isObserverApplied.
    @@ -41,6 +40,7 @@ module.exports = function ObserverMixin(Model) { * @param {object}listener - The listener function. It will be invoked with this set to the model constructor, e.g. User. * @memberof Observer mixin */ + log.debug(log.defaultContext(), 'observer-mixin is loaded for ', Model.modelName); Model.evObserve = function evObserve(operation, listener) { if (!Model.isObserverApplied(operation, listener)) { log.debug(log.defaultContext(), 'Registering observer for model - ', Model.modelName); @@ -75,15 +75,16 @@ module.exports = function ObserverMixin(Model) { Model.isObserverApplied = function isObserverApplied(operation, listener) { var isApplied = false; var observerList = Model._observers[operation]; - var fsObserverList; + // var fsObserverList; var isSameName = function isSameNameFn(e) { - return e.name === listener.name; + return e === listener; + // return e.name === listener.name; }; - if (Model._fsObservers) { - fsObserverList = Model._fsObservers[operation]; - } + // if (Model._fsObservers) { + // fsObserverList = Model._fsObservers[operation]; + // } if (observerList) { isApplied = observerList.indexOf(listener) !== -1 ? true : false; if (!isApplied) { @@ -93,18 +94,18 @@ module.exports = function ObserverMixin(Model) { isApplied = false; } - if (!isApplied) { - if (fsObserverList) { - for (var x = 0; x < fsObserverList.observers.length; x++) { - var observer = fsObserverList.observers[x]; - isApplied = (observer.getName() === listener.name) ? true : false; - log.debug(log.defaultContext(), 'fs observer ', observer.getName()); - if (isApplied) { - break; - } - } - } - } + // if (!isApplied) { + // if (fsObserverList) { + // for (var x = 0; x < fsObserverList.observers.length; x++) { + // var observer = fsObserverList.observers[x]; + // isApplied = (observer.getName() === listener.name) ? true : false; + // log.debug(log.defaultContext(), 'fs observer ', observer.getName()); + // if (isApplied) { + // break; + // } + // } + // } + // } if (isApplied) { return true; @@ -120,15 +121,15 @@ module.exports = function ObserverMixin(Model) { Model.observerAppliedWhere = function observerAppliedWhere(operation, listener) { var isApplied = false; var observerList = Model._observers[operation]; - var fsObserverList; + // var fsObserverList; var isSameName = function (e, index, array) { return e.name === listener.name; }; - if (Model._fsObservers) { - fsObserverList = Model._fsObservers[operation]; - } + // if (Model._fsObservers) { + // fsObserverList = Model._fsObservers[operation]; + // } if (observerList) { isApplied = observerList.indexOf(listener) !== -1 ? true : false; if (!isApplied) { @@ -138,18 +139,18 @@ module.exports = function ObserverMixin(Model) { isApplied = false; } - if (!isApplied) { - if (fsObserverList) { - for (var x = 0; x < fsObserverList.observers.length; x++) { - var observer = fsObserverList.observers[x]; - isApplied = (observer.getName() === listener.name) ? true : false; - log.debug(log.defaultContext(), 'fs observer ', observer.getName()); - if (isApplied) { - break; - } - } - } - } + // if (!isApplied) { + // if (fsObserverList) { + // for (var x = 0; x < fsObserverList.observers.length; x++) { + // var observer = fsObserverList.observers[x]; + // isApplied = (observer.getName() === listener.name) ? true : false; + // log.debug(log.defaultContext(), 'fs observer ', observer.getName()); + // if (isApplied) { + // break; + // } + // } + // } + // } if (isApplied) { return Model; diff --git a/common/mixins/property-expression-mixin.js b/common/mixins/property-expression-mixin.js deleted file mode 100644 index 76dfaa8..0000000 --- a/common/mixins/property-expression-mixin.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This mixin is for expression evaluations mentioned in properties and attaching the evaluated values to the properties*/ -/* @mixin Property expression mixin*/ -/* @author Rahul_Verma17*/ -var logger = require('oe-logger'); -var log = logger('property-expression-mixin'); -var propertyUtils = require('../../lib/common/property-expression-utils.js'); -var exprLang = require('../../lib/expression-language/expression-language.js'); - - -module.exports = function PropertyExpressionMixin(Model) { - if (Model.modelName === 'BaseEntity') { - // No need to apply the model property expression change at - // BaseEntity level - // Let the actual model decide if it wants to enable property - // expression mixin - return; - } - log.debug(log.defaultContext(), 'PropertyExpressionMixin invoked for ', Model.modelName, 'prop expr ', propertyUtils.propertyExpressions(Model)); - // get property expressions from property-expression-util - Model.propertyUtils = propertyUtils.propertyExpressions(Model); - if (typeof Model.propertyUtils !== 'undefined' && Model.propertyUtils.length > 0) { - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.DataPersonalizationMixin) || !Model.settings.mixins.PropertyExpressionMixin) { - Model.evRemoveObserver('before save', injectPropertyExprVal); - } else if (Model.settings.mixins.PropertyExpressionMixin) { - Model.evObserve('before save', injectPropertyExprVal); - } - } -}; - - -function injectPropertyExprVal(ctx, next) { - if (!ctx.Model.definition.settings.mixins.PropertyExpressionMixin) { - log.debug(ctx.options, 'PropertyExpressionMixin disabled for model - ', ctx.Model.modelName); - return next(); - } - log.debug(ctx.options, 'PropertyExpressionMixin Before save called. Model Name - ', ctx.Model.modelName); - log.debug(ctx.options, 'PropertyExpressionMixin Saving entity - ', ctx.Model.modelName); - if (ctx.instance) { - evaluateExpressions(ctx, function returnBack() { - log.debug(ctx.options, 'returned: ', ctx.instance); - next(); - }); - } else { - log.error(ctx.options, 'instance not defined'); - next(); - } -} - -// function to traverse through sub models and call evaluateExpressions -function getSubModels(inst) { - log.debug(log.defaultContext(), 'in sub models', inst); - var properties = inst.constructor.definition.properties; - log.debug(log.defaultContext(), 'in sub models properties', properties); - var model; - Object.keys(properties).forEach(function propertyExpressions(property) { - // if type of the property is an array which is of Model type then collect the property expressions for the Model properties - if (properties[property].type instanceof Function && - properties[property].type.sharedClass) { - log.debug(log.defaultContext(), 'in sub models property ', properties[property].type); - log.debug(log.defaultContext(), 'shared class ', properties[property].type.sharedClass); - model = properties[property].type; - var instance = inst.__data[property]; - log.debug(log.defaultContext(), 'shareid class instance ', instance); - if (instance && model.settings.mixins.PropertyExpressionMixin) { - log.debug(log.defaultContext(), 'rules added for ', model.modelName); - evaluateExpressions(instance); - } - } else if (properties[property].type instanceof Array && - properties[property].type[0] && - properties[property].type[0].sharedClass && - inst[property]) { - log.debug(log.defaultContext(), 'in array type model'); - for (var i = 0; i < inst[property].length; i++) { - model = properties[property].type[0]; - var instances = inst.__data[property]; - // for array type iterate and evaluate individually - iterateArrayProps(instances, model); - } - } else { - return; - } - }); - return; -} - - -function iterateArrayProps(instances, model) { - instances.forEach(function instacnesForEachFn(instance) { - log.debug(log.defaultContext(), 'instance in type array ', instance); - if (instance && model.settings.mixins.PropertyExpressionMixin) { - log.debug(log.defaultContext(), 'rules added for ', model.modelName); - evaluateExpressions(instance); - } - }); -} - - -function evaluateExpressions(ctx, callback) { - var instance = ctx.instance; - var options = ctx.options; - var self = instance; - var ast = self.constructor._ast; - var propertyExpressionPromises = []; - var propMapper = []; - log.debug(options, 'valid instance found:', ast); - var count = -1; - var data = self.toObject(true); - self.constructor.propertyUtils.forEach(function propExpressionsForEach(obj) { - if (obj.propExpression) { - log.debug(options, 'check---', JSON.stringify(exprLang.traverseAST(ast[obj.propExpression], self.toObject(true), options), '---data', JSON.stringify(data))); - propertyExpressionPromises.push(exprLang.traverseAST(ast[obj.propExpression], self.toObject(true), options)); - count++; - propMapper[count] = obj.name; - } - }); - // all settled promises are accumalated and attached to properties - Promise.all(propertyExpressionPromises).then(function propertiesPromiseFn(results) { - log.debug(options, 'eval result ', results); - for (var i = 0; i < propMapper.length; i++) { - log.debug(options, 'property', propMapper[i]); - if (typeof instance[propMapper[i].toString()] === 'undefined') { - var undefinedProp = propMapper[i].toString(); - instance[undefinedProp] = results[i]; - } else { - log.debug(options, 'length ', instance[propMapper[i].toString()].length); - if (instance[propMapper[i].toString()].length > 0) { continue; } - instance[propMapper[i].toString()] = results[i]; - } - } - log.debug(options, 'post update ', instance); - getSubModels(instance); - ctx.instance = instance; - callback(); - }, function promiseCallbackFn(err) { - log.debug(options, ' error----------------', err); - callback(); - }); -} diff --git a/common/mixins/read-only-mixin.js b/common/mixins/read-only-mixin.js deleted file mode 100644 index f458ce3..0000000 --- a/common/mixins/read-only-mixin.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * - * ©2017-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var logger = require('oe-logger'); -var log = logger('read-only-mixin'); - -module.exports = function ReadOnlyMixin(Model) { - if (Model.definition.name === 'BaseEntity') { - log.debug(log.defaultContext(), 'skipping mixin for - ', Model.definition.name); - return; - } - - Model.disableRemoteMethod('upsert', true); - Model.disableRemoteMethod('updateAll', true); - Model.disableRemoteMethod('updateAttributes', false); - Model.disableRemoteMethod('deleteById', true); - Model.disableRemoteMethod('destroyById', true); - Model.disableRemoteMethod('createChangeStream', true); - Model.disableRemoteMethod('replaceById', true); - Model.disableRemoteMethod('replaceOrCreate', true); - Model.disableRemoteMethod('patchOrCreate', true); - Model.disableRemoteMethod('replaceById', true); - Model.disableRemoteMethod('patchAttributes', true); - - - log.debug(log.defaultContext(), 'Applied Read-Only mixin on ' + Model.moduleName); -}; diff --git a/common/mixins/rest-api-actors-mixin.js b/common/mixins/rest-api-actors-mixin.js deleted file mode 100644 index 1f42b71..0000000 --- a/common/mixins/rest-api-actors-mixin.js +++ /dev/null @@ -1,95 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/** - - * @mixin Change rest api calls to first check in memory pool and if it is not find - find in DB. - * @author Karin Angel - */ -var _ = require('lodash'); - -module.exports = function RestApiActorsMixin(Model) { - // Before remote hook to parse ctx.args.filter string to object - Model.beforeRemote('find', function parseFilter(ctx, modelInstance, next) { - if (ctx.args.filter) { - ctx.args.filter = (typeof ctx.args.filter !== 'object') ? JSON.parse(ctx.args.filter) : ctx.args.filter; - } - next(); - }); - - Model.afterRemote('**', function (ctx, unused, next) { - var query = ctx.args; - - if (!ctx.methodString.includes('find')) { - return next(); - } - - var id = query.id; - - if (typeof id === 'undefined') { - if (typeof query.filter !== 'undefined' && typeof query.filter.where !== 'undefined') { - var idInWhere = findIdInWhere(Model, query.filter.where); - if (idInWhere) { - id = idInWhere.value; - } - } - } - - if (id) { - var actor; - if (Array.isArray(ctx.result)) { - actor = ctx.result[0]; - } else { - actor = ctx.result; - } - if (!actor) { - return next(); - } - var options = ctx.req.callContext; - actor.balanceProcess(options, function (err, res) { - if (err) { - return next(err); - } - if (Array.isArray(ctx.result)) { - ctx.result = [res]; - } else { - ctx.result = res; - } - next(); - }); - } else { - return next(); - } - }); - - function getFields(data, arr) { - _.forEach(data, function dataAccessGetKeysForEach(value, key) { - if ((typeof key === 'string') && (key !== 'and' || key !== 'or')) { - if (key.indexOf('.') > -1) { - Array.prototype.splice.apply(arr, [0, 0].concat(key.split('.'))); - } else { - arr.push({key: key, value: value}); - } - } - if (typeof value === 'object') { - getFields(value, arr); - } - }); - } - - function idName(m) { - return m.definition.idName() || 'id'; - } - - function findIdInWhere(model, where) { - var pk = idName(model); - var whereConds = []; - getFields(where, whereConds); - return whereConds.find(function (cond) { - return cond.key === pk; - }); - } -}; diff --git a/common/mixins/retry-support-mixin.js b/common/mixins/retry-support-mixin.js deleted file mode 100644 index d2f36d2..0000000 --- a/common/mixins/retry-support-mixin.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/** - * - * @mixin Retry-Support Mixin - * @author David Zharnest - */ - - -module.exports = function RetrySupportMixin(Model) { - // Add a remote methods. - exposeAsRemote(Model); -}; - -/** - * Adds remote methods to model and exposes them. - * - * @param {ModelConst} Model - Model constructor on which remote method is added. - * @memberof History Mixin - */ -function exposeAsRemote(Model) { - Model.isRetryable = function modelIsRetryable(filter, options, cb) { - return cb(null, 'true'); - }; - - // Register a REST end-point /isRetryable for the above method - Model.remoteMethod('isRetryable', { - http: { - path: '/isRetryable', - verb: 'get' - }, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - - Model.primaryKeyField = function pKeyField(filter, options, cb) { - var pkField = {}; - pkField.name = Model.dataSource.idName(Model.modelName); - return cb(null, pkField); - }; - - // Register a REST end-point /isRetryable for the above method - Model.remoteMethod('primaryKeyField', { - http: { - path: '/primaryKeyField', - verb: 'get' - }, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); -} diff --git a/common/mixins/soft-delete-mixin.js b/common/mixins/soft-delete-mixin.js deleted file mode 100644 index bda1259..0000000 --- a/common/mixins/soft-delete-mixin.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin is to provide support for soft delete for a given model.
    - * - * This mixin add a new property _isDeleted to the model and overrides - * destroyedById and DestroyAll method to update records and set per filter and - * set _isDelete to true. By default _isDeleted is set to false.
    - * - * It also set an access observer hook to alter the query, it add filter - * _isDelete = false so only records with _isDeleted : false are return. - * - * @mixin Soft Delete - * @author Sivankar Jain - */ - -var mergeQuery = require('loopback-datasource-juggler/lib/utils').mergeQuery; - -module.exports = function SoftDeleteMixin(Model) { - if (Model.modelName === 'BaseEntity') { - return; - } - - Model.settings._softDelete = true; - - Model.defineProperty('_isDeleted', { - type: 'boolean', - default: false - }); - if ((Model.settings.overridingMixins && !Model.settings.overridingMixins.SoftDeleteMixin) || !Model.settings.mixins.SoftDeleteMixin) { - Model.evRemoveObserver('access', addSoftDeleteFilter); - } else { - Model.evObserve('access', addSoftDeleteFilter); - } -}; - -/** - * Adds an access observer hook to add query filter _isDelete: false, so only - * records with _isDeleted : false are return. - * - * @param {object} - * ctx - ctx object, which is populated by DAO. - * @param {function} - * next - move to the next function in the queue - * @returns {function} next - move to the next function in the queue - * @memberof Soft Delete - */ -function addSoftDeleteFilter(ctx, next) { - if (!ctx.Model.settings._softDelete) { - return next(); - } - ctx.query = ctx.query || {}; - if (ctx.query.fetchDeleted) { - mergeQuery(ctx.query, { - where: { - _isDeleted: true - } - }); - } else { - mergeQuery(ctx.query, { - where: { - _isDeleted: false - } - }); - } - next(); -} diff --git a/common/mixins/switch-datasource-mixin.js b/common/mixins/switch-datasource-mixin.js deleted file mode 100644 index 4c8657d..0000000 --- a/common/mixins/switch-datasource-mixin.js +++ /dev/null @@ -1,302 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This Module provides Methods to get Personalized Models. - * - * @mixin Switch Data Source Mixin - * @Author Atul - */ - -var logger = require('oe-logger'); -var log = logger('switch-datasource-mixin'); -var appinstance = require('../../server/server.js').app; -var versionMixin = require('./version-mixin.js'); - -function getScopeMatchedDS(model, list, scope) { - var matchedds; - var priority = -1; - - for (var i = 0; i < list.length; i++) { - var ds = list[i]; - var dsScope = {}; - var match = true; - var count = 0; - - for (var scopevar in ds.settings.scope) { - if (ds.settings.scope.hasOwnProperty(scopevar)) { - dsScope[scopevar] = ds.settings.scope[scopevar]; - } - } - - for (var autoScopeVar in ds.settings._autoScope) { - if (ds.settings._autoScope.hasOwnProperty(autoScopeVar)) { - dsScope[autoScopeVar] = ds.settings._autoScope[autoScopeVar]; - } - } - - dsScope.modelName = model.clientModelName || model.modelName; - - for (var dsScopeVar in dsScope) { - if (!dsScope.hasOwnProperty(dsScopeVar)) { - continue; - } - if (dsScope[dsScopeVar] === 'default') { - match = true; - break; - } - - count = count + 1; - if (!scope[dsScopeVar]) { - match = false; - break; - } else if (dsScope[dsScopeVar] instanceof Array) { - if (dsScope[dsScopeVar].indexOf(scope[dsScopeVar]) === -1) { - match = false; - break; - } - } else if (scope[dsScopeVar] && scope[dsScopeVar] !== dsScope[dsScopeVar]) { - match = false; - break; - } - } - - if (match) { - var temp = (ds.priority && ds.priority > 0) ? ds.priority : count; - - if (temp > priority) { - matchedds = ds; - priority = temp; - } - } - } - - return matchedds; -} - -function getScopeMatchedMapping(model, list, scope) { - var matchedMapping; - var priority = -1; - - for (var i = 0; i < list.length; i++) { - var mapping = list[i]; - var mappingScope = {}; - var match = true; - var count = 0; - - for (var scopevar in mapping.scope) { - if (mapping.scope.hasOwnProperty(scopevar)) { - mappingScope[scopevar] = mapping.scope[scopevar]; - } - } - - for (var autoScopeVar in mapping._autoScope) { - if (mapping._autoScope.hasOwnProperty(autoScopeVar)) { - mappingScope[autoScopeVar] = mapping._autoScope[autoScopeVar]; - } - } - - for (var mappingScopeVar in mappingScope) { - if (!mappingScope.hasOwnProperty(mappingScopeVar)) { - continue; - } - if (mappingScope[mappingScopeVar] === 'default') { - match = true; - break; - } - count = count + 1; - if (!scope[mappingScopeVar]) { - match = false; - break; - } else if (mappingScope[mappingScopeVar] instanceof Array) { - if (mappingScope[mappingScopeVar].indexOf(scope[mappingScopeVar]) === -1) { - match = false; - break; - } - } else if (scope[mappingScopeVar] && scope[mappingScopeVar] !== mappingScope[mappingScopeVar]) { - match = false; - break; - } - } - - if (match) { - var temp = (mapping.priority && mapping.priority > 0) ? mapping.priority : count; - - if (temp > priority) { - matchedMapping = mapping; - priority = temp; - } - } - } - return matchedMapping; -} - -function getDataSourceForName(app, model, dsname, scope) { - var dslist = app.locals.dataSources[dsname]; - if (!dslist) { - app.locals.dataSources[dsname] = []; - dslist = app.locals.dataSources[dsname]; - Object.keys(app.datasources).forEach(function iter(id) { - var dataSource = app.dataSources[id]; - // currently two dbs will get same entry - if (dataSource.settings.name === dsname) { - dslist.push(dataSource); - } - }); - } - - if (dslist.length === 1) { - return dslist[0]; - } - - var ds = getScopeMatchedDS(model, dslist, scope); - if (!ds) { - ds = app.dataSources[dsname]; - } - return ds; -} - -module.exports = function SwitchDatasourceMixin(model) { - var originalDataSource = {}; - - model.getDataSource = function switchDatasource(options) { - var app = model.app; - if (!app) { - // history model is not attached to app - app = appinstance; - } - - var frameworkmodelnames = ['PersonalizationRule', 'ModelDefinition', 'DataSourceMapping', 'DataSourceDefinition', 'Tenant']; - - if (frameworkmodelnames.indexOf(model.modelName) !== -1) { - var ret = app.dataSources.db; - if (ret) { - model.attachTo(ret); - } - return ret; - } - - // if (options) { - // if (typeof options === 'function') { - // console.log('switch ds ', model.modelName, 'no options'); - // console.trace('no options'); - // process.exit(1); - // } else { - // console.log('switch ds options ok ', model.modelName); - // //console.log(options); - // } - // } - - var modelName = model.settings.variantOf || model.clientModelName; - - originalDataSource[modelName] = originalDataSource[modelName] || model.dataSource; - - app.locals.dataSourceMappings = app.locals.dataSourceMappings || {}; - app.locals.dataSources = app.locals.dataSources || {}; - - var maplist = app.locals.dataSourceMappings[modelName]; - var scope = {}; - if (options.ctx) { - var callContext = options; - callContext = callContext || {}; - callContext.ctx = callContext.ctx || {}; - - Object.keys(callContext.ctx).forEach(function iter(key) { - if (callContext.ctx[key] instanceof Array) { - scope[key] = []; - callContext.ctx[key].forEach(function funclowercase(item) { - scope[key].push(item); - }); - } else if (typeof callContext.ctx[key] === 'string') { - scope[key] = callContext.ctx[key]; - } else { - scope[key] = callContext.ctx[key]; - } - }); - } - - scope.modelName = modelName; - if (maplist) { - var mapping = getScopeMatchedMapping(model, maplist, scope); - if (mapping) { - var ds = getDataSourceForName(app, model, mapping.dataSourceName, scope); - if (ds) { - // console.log('switch datasource ', modelName, ds.settings.name); - model.attachTo(ds); - // re-attaching switchVersion method after datasource switching on any model. so that _version will be created properly - // because switchVersion method will be overridden by the dao switchVersion method which simply return cb() when datasource switching happen on any model. - // check if version mixin enabled on model too. - if (model.settings.mixins.VersionMixin) { - model.switchVersion = versionMixin.switchVersion; - } - return ds; - } - } - } else if (originalDataSource[model.clientModelName]) { - var dsName = originalDataSource[model.clientModelName].settings.name; - var ds2 = getDataSourceForName(app, model, dsName, scope); - if (ds2) { - model.attachTo(ds2); - return ds2; - } - } - - if (originalDataSource[model.clientModelName]) { - model.attachTo(originalDataSource[modelName]); - return originalDataSource[model.clientModelName]; - } - - var dsname = model.dataSource.settings.name ? model.dataSource.settings.name : 'db'; - var defaultds = app.dataSources[dsname]; - if (defaultds) { - model.attachTo(defaultds); - log.debug(options, 'switch datasource ', modelName, defaultds.settings.name); - } - return defaultds; - }; -}; - -// removing data source personalisation for now, only model to ds mapping we will support -// function getScopeMatchedDS(model, dslist, scope) { -// -// var matchedds; -// var priority = -1; -// -// for (var i = 0; i < dslist.length; i++) { -// var ds = dslist[i]; -// var dsscope = ds.settings.scope; -// var match = true; -// var count = 0; -// -// for (var scopevar in dsscope) { -// count = count + 1; -// if (!scope[scopevar]) { -// match = false; -// break; -// } else if (dsscope[scopevar] instanceof Array) { -// if (dsscope[scopevar].indexOf(scope[scopevar]) === -1) { -// match = false; -// break; -// } -// } else if (scope[scopevar] && scope[scopevar] !== dsscope[scopevar]) { -// match = false; -// break; -// } -// } -// -// -// if (match) { -// var temp = (ds.settings.priority && ds.settings.priority > 0) ? ds.settings.priority : count; -// -// if (temp > priority) { -// matchedds = ds; -// priority = temp; -// } -// } -// } -// return matchedds; -// -// } diff --git a/common/mixins/version-mixin.js b/common/mixins/version-mixin.js deleted file mode 100644 index b054b30..0000000 --- a/common/mixins/version-mixin.js +++ /dev/null @@ -1,148 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This mixin is to support version control of a record/instance it adds a new - * property called _version and auto populate it with uuidv4() which is a - * unique number, new version for a record is generated, when a new instance is - * created or updated.

    - * - * It also added check for version number on update and delete, of a record so - * when a record/instance needs to be updated or deleted, user must provide the - * current version of the record. - * - * @mixin EV Version Mixin - * @author Sivankar Jain - */ - -var uuidv4 = require('uuid/v4'); - -function versionMixinBeforeSave(ctx, next) { - // if (Model.modelName !== ctx.Model.modelName) { - // return next(); - // } - var data = ctx.data || ctx.instance; - var error; - if (ctx.isNewInstance) { - data._version = data._newVersion || data._version || uuidv4(); - delete data._oldVersion; - delete data._newVersion; - } else if (ctx.currentInstance) { - if (ctx.currentInstance.__remoteInvoked) { - if (!data._version) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'current version must be specified in _version field'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - } - var version = data._version || ctx.currentInstance._version; - if (data._newVersion && data._newVersion === version) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'current version and new version must be different'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - if (version.toString() !== ctx.currentInstance._version.toString()) { - error = new Error(); - error.name = 'Data Error'; - error.message = 'No record with version specified'; - error.code = 'DATA_ERROR_071'; - error.type = 'DataModifiedError'; - error.retriable = false; - error.status = 422; - return next(error); - } - data._oldVersion = version; - data._version = data._newVersion || uuidv4(); - delete data._newVersion; - } - // TODO replaceById will have ctx.instance, and not - // ctx.currentinstance, need to analyze that - next(); -}; - -module.exports = function VersionMixin(Model) { - if (Model.modelName === 'BaseEntity') { - return; - } - - Model.defineProperty('_oldVersion', { - type: String - }); - - Model.defineProperty('_version', { - type: String, - index: { - unique: true - }, - required: true - }); - - Model.defineProperty('_requestId', { - type: String - }); - - Model.defineProperty('_newVersion', { - type: String - }); - - Model.settings._versioning = true; - // Model.settings.updateOnLoad = true; - - Model.evObserve('after save', function afterSaveVersionMixin(ctx, next) { - var data = ctx.data || ctx.instance; - if (data && data.__data) { - delete data.__data._newVersion; - } - next(); - }); - - Model.switchVersion = versionMixinBeforeSave; - // lock current _version - Model.evObserve('persist', function versionMixinPersistsFn(ctx, next) { - delete ctx.data._newVersion; - return next(); - }); - - Model.remoteMethod('deleteWithVersion', { - http: { - path: '/:id/:version', - verb: 'delete' - }, - description: 'Delete a model instance by id and version number, from the data source.', - accepts: [{ - arg: 'id', - type: 'string', - required: true, - http: { - source: 'path' - } - }, { - arg: 'version', - type: 'string', - required: true, - http: { - source: 'path' - } - }], - returns: { - arg: 'response', - type: 'object', - root: true - } - }); -}; - -module.exports.switchVersion = versionMixinBeforeSave; diff --git a/common/models/base-entity.js b/common/models/base-entity.js new file mode 100644 index 0000000..a739797 --- /dev/null +++ b/common/models/base-entity.js @@ -0,0 +1,20 @@ +var oecloud = require('../../'); +var logger = require('oe-logger'); +var log = logger('base-entity'); + +log.debug(log.defaultContext(), 'BaseEntity.js is loaded'); + +module.exports = function (Model) { + log.debug(log.defaultContext(), 'BaseEntity.js is loaded for ', Model.modelName); + var sources = oecloud.options.baseEntitySources; + if (sources) { + sources.forEach(source => { + var f = require(source); + if (typeof f === 'function') { + f(Model); + } + }); + } +}; + + diff --git a/common/models/base-entity.json b/common/models/base-entity.json new file mode 100644 index 0000000..b9a2dc5 --- /dev/null +++ b/common/models/base-entity.json @@ -0,0 +1,29 @@ +{ + "name": "BaseEntity", + "base": "PersistedModel", + "idInjection": true, + "strict": true, + "forceId": false, + "options": { + "validateUpsert": true, + "trackChanges": false, + "updateOnLoad": true, + "isBaseEntity": true, + "strictObjectIDCoercion": true + }, + "mixins": { + "ObserverMixin": true + }, + "validations": [], + "relations": {}, + "acls": [ + { + "accessType": "WRITE", + "principalType": "ROLE", + "principalId": "$unauthenticated", + "permission": "DENY" + } + ], + "methods": {} +} + diff --git a/common/models/data-source-definition.js b/common/models/data-source-definition.js new file mode 100644 index 0000000..1790cc2 --- /dev/null +++ b/common/models/data-source-definition.js @@ -0,0 +1,35 @@ +/** + * + * �2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + +/** + * @classdesc This model is to hold DataSourceDefinition, actual data sources created of application + * This is used to create / delete data source dynamically + * Only admin guys should have access to this model + * @kind class + * @author Praveen/Atul + * @class DataSourceDefinition + */ + +const loopback = require('loopback'); + +module.exports = function (Model) { + /* + * 'after save' - hook is used to create actual data source in loopback + * User posts the data to DataSourceDefinition model and then this hoook is executed + * when data is saved. After that this hook uses utility function to create data source + * + * @param {object} ctx - saved data context which contains actual data saved + * @param {function} next - next: a callback function for continuation + */ + Model.observe('after save', function (ctx, next) { + var app = ctx.Model.app; + var inst = ctx.instance; + var ds = loopback.createDataSource(inst); + app.datasources[inst.id] = ds; + return next(); + }); +}; diff --git a/common/models/data-source-definition.json b/common/models/data-source-definition.json new file mode 100644 index 0000000..a72a519 --- /dev/null +++ b/common/models/data-source-definition.json @@ -0,0 +1,65 @@ +{ + "name": "DataSourceDefinition", + "base": "BaseEntity", + "description": "Application Data Sources configuration", + "idInjection": true, + "options": { + "validateUpsert": true, + "isFrameworkModel": true + }, + "properties": { + "name": { + "type": "string", + "required": true, + "unique": true, + "max": 200 + }, + "connector": { + "type": "string", + "max": 200 + }, + "host": { + "type": "string", + "max": 200 + }, + "port": { + "type": "number" + }, + "url": { + "type": "string", + "max": 500 + }, + "database": { + "type": "string", + "max": 200 + }, + "user": { + "type": "string", + "max": 200 + }, + "password": { + "type": "string", + "max": 200 + }, + "priority": { + "type": "Number", + "default": 0 + }, + "connectionTimeout": { + "type": "Number", + "default": 5000 + }, + "connectTimeoutMS": { + "type": "Number", + "default": 5000 + }, + "socketTimeoutMS": { + "type": "Number", + "default": 5000 + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} \ No newline at end of file diff --git a/common/models/framework/enum-base.js b/common/models/enum-base.js similarity index 100% rename from common/models/framework/enum-base.js rename to common/models/enum-base.js diff --git a/common/models/framework/enum-base.json b/common/models/enum-base.json similarity index 100% rename from common/models/framework/enum-base.json rename to common/models/enum-base.json diff --git a/common/models/error.json b/common/models/error.json new file mode 100644 index 0000000..1d3d724 --- /dev/null +++ b/common/models/error.json @@ -0,0 +1,29 @@ +{ + "name": "Error", + "base": "BaseEntity", + "plural": "errors", + "idInjection": false, + "description": "This Model stores all the Customized error details", + "options": { + "validateUpsert": true, + "isFrameworkModel": true + }, + "properties": { + "errCode": { + "type": "string", + "max": 100 + }, + "errMessage": { + "type": "string", + "max": 250 + }, + "moreInformation": { + "type": "string", + "max": 500 + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/common/models/framework/actor-activity.json b/common/models/framework/actor-activity.json deleted file mode 100644 index 250eb51..0000000 --- a/common/models/framework/actor-activity.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "ActorActivity", - "base": "PersistedModel", - "description": "Represents an activity in a transcation", - "idInjection": true, - "properties": { - "entityId": { - "type": "string" - }, - "payload": { - "type": "object" - }, - "payloadTxt": { - "type": "string" - }, - "atomic": { - "type": "boolean" - }, - "modelName": { - "type": "string" - }, - "instructionType": { - "type": "string" - }, - "seqNum": { - "type": "Number" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {}, - "indexes": { - "modelname_entityid_seqnum": { - "keys": { - "modelName": 1, - "entityId": 1, - "seqNum": 1 - } - } - } -} \ No newline at end of file diff --git a/common/models/framework/admin.js b/common/models/framework/admin.js deleted file mode 100644 index c766e3a..0000000 --- a/common/models/framework/admin.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * - * �2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var logger = require('oe-logger'); -var log = logger('admin'); -var jwt = require('jsonwebtoken'); -var jwtUtil = require('../../../lib/jwt-token-util'); - -module.exports = function AdminFunction(admin) { - admin.checkLicense = function checkLicense(options, cb) { - var licensePublicKey = jwtUtil.sanitizePublicKey(process.env.LICENSE_PUBLICKEY); - var licenseKey = process.env.LICENSE_KEY; - if (licensePublicKey && licenseKey) { - var decoded = jwt.verify(licenseKey, licensePublicKey, { - algorithm: 'RS256' - }); - if (decoded && decoded.endl) { - log.info(options, 'licence info decoded'); - cb(null, { 'expired': (Date.now() > decoded.endl), 'expiryDate': new Date(decoded.endl) }); - } else { - log.info(options, 'licence info not configured'); - cb(null, { 'expired': false, 'expiryDate': 'License not set' }); - } - } - }; - - admin.remoteMethod('checkLicense', { - description: 'check if license expiry', - accessType: 'READ', - accepts: [], - http: { - verb: 'GET', - path: '/checkLicense' - }, - returns: [{ - arg: 'body', - type: 'string', - root: true - }] - }); -}; diff --git a/common/models/framework/admin.json b/common/models/framework/admin.json deleted file mode 100644 index a7c1694..0000000 --- a/common/models/framework/admin.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Admin", - "plural": "admin", - "base": "Model", - "description" : "Used to download or upload metadata - Under Experiment", - "options": { - "validateUpsert": true - }, - "properties": { - }, - "validations": [], - "acls": [], - "methods": {} -} diff --git a/common/models/framework/app-config.json b/common/models/framework/app-config.json deleted file mode 100644 index c74511b..0000000 --- a/common/models/framework/app-config.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "AppConfig", - "base": "BaseEntity", - "description": "This model is used to store application configuration", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "queryCacheSize": 20000, - "queryCacheExpiration": 86400000 - }, - "properties": { - "server": { - "type": "object", - "required": true, - "value": {} - }, - "client": { - "type": "object", - "required": true, - "value": {} - } - }, - "hidden": ["server"], - "validations": [], - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/auth-session.js b/common/models/framework/auth-session.js deleted file mode 100644 index f0e9280..0000000 --- a/common/models/framework/auth-session.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -// @jsonwebtoken is internal dependency of @oe-jwt-generator -var jwt = require('jsonwebtoken'); -const loopback = require('loopback'); -const log = require('oe-logger')('auth-session'); -const uuidv4 = require('uuid/v4'); -var jwtUtil = require('../../../lib/jwt-token-util'); - -const cachedTokens = {}; -module.exports = function AuthSessionFn(AuthSession) { - AuthSession.findForRequest = function authSessionFindForRequestFn(req, options, cb) { - if (typeof cb === 'undefined' && typeof options === 'function') { - cb = options; - options = {}; - } - var id = tokenIdForRequest(req, options); - - var proxyKey = options.model.app.get('evproxyInternalKey') || '97b62fa8-2a77-458b-87dd-ef64ff67f847'; - if (req.headers && proxyKey) { - if (req.headers['x-evproxy-internal-key'] === proxyKey) { - var data = req.callContext.evproxyContext.accessTokenData; - var token = new AuthSession(data, { - applySetters: false, - persisted: true - }); - return cb(null, token); - } - } - - if (id && id !== 'undefined') { - let jwtForAccessToken = process.env.JWT_FOR_ACCESS_TOKEN ? (process.env.JWT_FOR_ACCESS_TOKEN.toString() === 'true') : false; - // json web token contains 3 parts separated by .(dot) - if (jwtForAccessToken && id.split('.').length === 3) { - var jwtConfig = jwtUtil.getJWTConfig(); - var jwtOpts = {}; - jwtOpts.issuer = jwtConfig.issuer; - jwtOpts.audience = jwtConfig.audience; - var secretOrPrivateKey = jwtConfig.secretOrKey; - jwt.verify(id, secretOrPrivateKey, jwtOpts, function (err, parsedJWT) { - if (err) { - err.statusCode = 401; - cb(err); - } else { - var trustedApp = parsedJWT[jwtConfig.keyToVerify]; - var userObj = loopback.getModelByType('BaseUser'); - var username = ''; - if (trustedApp) { - var rolesToAdd = []; - var appObj = loopback.getModelByType('TrustedApp'); - var query = { appId: trustedApp }; - appObj.findOne({ - where: query - }, req.callContext, (err, trusted) => { - if (err) { - log.error(req.callContext, 'Error while Querying TrustedApp', err); - return cb(); - } - if (trusted && req.headers.username && req.headers.email) { - username = req.headers.username; - var email = req.headers.email; - // verify supported Roles - if (req.headers.roles && trusted.supportedRoles) { - JSON.parse(req.headers.roles).forEach(function (element) { - if (trusted.supportedRoles.some(x => x === element)) { - rolesToAdd.push({ 'id': element, 'type': 'ROLE' }); - } - }); - } - if (rolesToAdd && rolesToAdd.length > 0) { - req.callContext.principals = rolesToAdd ? rolesToAdd : req.callContext.principals; - } - userObj.findOne({ where: { username } }, req.callContext, (err, u) => { - if (err) { - log.error(req.callContext, 'Error Querying User Information', err); - return cb(); - } - if (u) { - parsedJWT.id = id; - parsedJWT.userId = u.id; - parsedJWT.username = u.username; - cb(null, new AuthSession(parsedJWT)); - } else { - userObj.create({ username: username, email: email, password: uuidv4() }, req.callContext, (err, newUser) => { - if (err) { - return cb(); - } - if (newUser) { - // Setting "id" which will be retrieved in post-auth-context-populator - // for setting callContext.accessToken - parsedJWT.id = id; - parsedJWT.userId = newUser.id; - parsedJWT.username = newUser.username; - cb(null, new AuthSession(parsedJWT)); - } else { - cb(); - } - }); - } - }); - } else { - // Setting "id" which will be retrieved in post-auth-context-populator - // for setting callContext.accessToken - parsedJWT.id = id; - checkUserExistence(parsedJWT, req, userObj, cb); - } - }); - } else { - // Setting "id" which will be retrieved in post-auth-context-populator - // for setting callContext.accessToken - parsedJWT.id = id; - checkUserExistence(parsedJWT, req, userObj, cb); - } - } - }); - } else { - this.findById(id, req.callContext, function authSessionFindById(err, token) { - if (err) { - cb(err); - } else if (token) { - token.validate(function tokenValidate(err, isValid) { - if (err) { - cb(err); - } else if (isValid) { - cb(null, token); - } else { - var e = new Error('Invalid Access Token'); - e.status = e.statusCode = 401; - e.code = 'INVALID_TOKEN'; - e.retriable = false; - cb(e); - } - }); - } else { - cb(); - } - }); - } - } else { - process.nextTick(function tokenForRequestFn() { - cb(); - }); - } - }; - - function checkUserExistence(parsedJWT, req, userObj, callback) { - var username = parsedJWT.username || parsedJWT.user_name || parsedJWT.email || ''; - - // If token is available in cachedTokens, return from cachedTokens. - if (cachedTokens[username]) { - return callback(null, cachedTokens[username]); - } - - // If parsedJWT contains the userId information, no need to query the DB. - if (parsedJWT.userId) { - cachedTokens[username] = new AuthSession(parsedJWT); - return callback(null, cachedTokens[username]); - } - - userObj.findOne({ - where: { - username - } - }, req.callContext, (err, u) => { - if (err) { - return callback(err); - } - if (u) { - parsedJWT.userId = u.id; - parsedJWT.username = u.username; - cachedTokens[username] = new AuthSession(parsedJWT); - callback(null, cachedTokens[username]); - } else { - log.error(req.callContext, 'User not found!!!'); - const error = new Error('User not found!!'); - error.statusCode = 401; - return callback(error); - } - }); - } - - function tokenIdForRequest(req, options) { - var params = options.params || []; - var headers = options.headers || []; - var cookies = options.cookies || []; - var i = 0; - var length; - var id; - - // https://github.com/strongloop/loopback/issues/1326 - if (options.searchDefaultTokenKeys !== false) { - params = params.concat(['access_token']); - - // Adding 'x-jwt-assertion' to headers for supporting JWT Assertion. - let jwtForAccessToken = process.env.JWT_FOR_ACCESS_TOKEN ? (process.env.JWT_FOR_ACCESS_TOKEN.toString() === 'true') : false; - if (jwtForAccessToken) { - headers = headers.concat(['X-Access-Token', 'x-jwt-assertion', 'authorization']); - } else { - headers = headers.concat(['X-Access-Token', 'authorization']); - } - cookies = cookies.concat(['access_token', 'authorization']); - } - - for (length = params.length; i < length; i++) { - var param = params[i]; - // replacement for deprecated req.param() - id = req.params && typeof req.params[param] !== 'undefined' ? req.params[param] : - req.body && typeof req.body[param] !== 'undefined' ? req.body[param] : - req.query && typeof req.query[param] !== 'undefined' ? req.query[param] : - getFromCookie(req, param); - if (id && typeof id === 'string') { - return id; - } - } - - for (i = 0, length = headers.length; i < length; i++) { - id = req.header(headers[i]); - - if (typeof id === 'string') { - // Add support for oAuth 2.0 bearer token - // http://tools.ietf.org/html/rfc6750 - if (id.indexOf('Bearer ') === 0) { - id = id.substring(7); - // Decode from base64 - var buf = new Buffer(id, 'base64'); - id = buf.toString('utf8'); - } else if (/^Basic /i.test(id)) { - id = id.substring(6); - id = (new Buffer(id, 'base64')).toString('utf8'); - // The spec says the string is user:pass, so if we see both parts - // we will assume the longer of the two is the token, so we will - // extract "a2b2c3" from: - // "a2b2c3" - // "a2b2c3:" (curl http://a2b2c3@localhost:3000/) - // "token:a2b2c3" (curl http://token:a2b2c3@localhost:3000/) - // ":a2b2c3" - var parts = /^([^:]*):(.*)$/.exec(id); - if (parts) { - id = parts[2].length > parts[1].length ? parts[2] : parts[1]; - } - } - return id; - } - } - - if (req.signedCookies) { - for (i = 0, length = cookies.length; i < length; i++) { - id = req.signedCookies[cookies[i]]; - - if (typeof id === 'string') { - return id; - } - } - } - return null; - } -}; - - -function getFromCookie(r, p) { - if (r.headers && r.headers.cookie) { - var allCookies = r.headers.cookie.split(';'); - var result = null; - allCookies.forEach(function (c) { - if (c.split('=')[0].trim() === p) { - result = c.split('=')[1].trim(); - return; - } - }); - return result ? result.substring(4, 68) : null; - } else if (r.cookies && r.cookies[p]) { - return r.cookies[p]; - } -} diff --git a/common/models/framework/auth-session.json b/common/models/framework/auth-session.json deleted file mode 100644 index 17abce6..0000000 --- a/common/models/framework/auth-session.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "AuthSession", - "plural": "AuthSessions", - "base": "AccessToken", - "description": "This model stores authentication data of the user", - "properties": { - "tenantId": { - "type": "string", - "required": false, - "max": 30 - }, - "roles": { - "type": [ - "string" - ], - "required": false - }, - "department": { - "type": "string", - "required": false, - "max": 30 - }, - "username": { - "type": "string", - "required": false, - "max": 30 - }, - "userTenantId": { - "type": "string", - "required": false, - "max": 30 - } - }, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "BaseUser", - "foreignKey": "userId" - } - }, - "isFrameworkModel": true, - "acls": [], - "methods": {}, - "disableInstanceCache": false, - "options":{ - "disableManualPersonalization": true, - "instanceCacheSize": 1000, - "instanceCacheExpiration": 180000 - }, - "mixins":{ - "ObserverMixin": true, - "CacheMixin": true - } -} \ No newline at end of file diff --git a/common/models/framework/base-ACL.js b/common/models/framework/base-ACL.js deleted file mode 100644 index 58112b9..0000000 --- a/common/models/framework/base-ACL.js +++ /dev/null @@ -1,221 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var loopback = require('loopback'); -var async = require('async'); -var debug = require('debug')('loopback:security:acl'); -var logger = require('oe-logger'); -var log = logger('BaseACL'); -var loopbackAccessContext = require('loopback/lib/access-context'); -var AccessContext = loopbackAccessContext.AccessContext; -// var Principal = loopbackAccessContext.Principal; -var AccessRequest = loopbackAccessContext.AccessRequest; - -var ACL = loopback.ACL; - -var RoleMapping = loopback.RoleMapping; -/** - * @classdesc This model controls the access control for APIs - * Earlier these methods were part of boot script - * @kind class - * @author Ajith / Praveen Gulati - * @class BaseACL - */ - -module.exports = function DBTransactionFn(BaseACL) { - /** - * Check if the request has the permission to access. - * @param {Object} context See below. - * @property {Object[]} principals An array of principals. - * @property {String|Model} model The model name or model class. - * @property {*} id The model instance ID. - * @property {String} property The property/method/relation name. - * @property {String} accessType The access type: - * READ, REPLICATE, WRITE, or EXECUTE. - * @param {Function} callback Callback function - */ - BaseACL.checkAccessForContext = function checkAccessForContext(context, callback) { - var registry = this.registry; - - if (!(context instanceof AccessContext)) { - context = new AccessContext(context); - } - - // Checking the AccessContext has accessToken with roles attached - if (context.accessToken && context.accessToken.roles && Array.isArray(context.accessToken.roles)) { - // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE - context.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); - }); - } - var model = context.model; - var property = context.property; - var accessType = context.accessType; - - var modelName = model.settings.variantOf || model.modelName; - - var methodNames = context.methodNames; - var propertyQuery = (property === ACL.ALL) ? null : { inq: methodNames.concat([ACL.ALL]) }; - - var accessTypeQuery = (accessType === ACL.ALL) ? - null : - (accessType === ACL.REPLICATE) ? { inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL] } : { inq: [accessType, ACL.ALL] }; - - var req = new AccessRequest(modelName, property, accessType, ACL.DEFAULT, methodNames); - - var effectiveACLs = []; - // for static ACLs using personalised model name - // but for dynamic models use variant Of Parent - var staticACLs = this.getStaticACLs(model.modelName, property); - - var self = this; - var roleModel = registry.getModelByType('BaseRole'); - this.find({ - where: { - model: model.clientModelName, property: propertyQuery, - accessType: accessTypeQuery - } - }, context.remotingContext.req.callContext, function modelFindCb(err, acls) { - if (err) { - if (callback) { - callback(err); - } - return; - } - - var inRoleTasks = []; - - acls = acls.concat(staticACLs); - if (context.remotingContext && - context.remotingContext.req && - context.remotingContext.req.callContext && - context.remotingContext.req.callContext.principals && - context.remotingContext.req.callContext.principals.length > 0) { - context.principals = context.principals.concat(context.remotingContext.req.callContext.principals); - } - acls.forEach(function aclsForEach(acl) { - // Check exact matches - for (var i = 0; i < context.principals.length; i++) { - var p = context.principals[i]; - var typeMatch = p.type === acl.principalType; - var idMatch = String(p.id) === String(acl.principalId); - if (typeMatch && idMatch) { - effectiveACLs.push(acl); - return; - } - } - - // Check role matches - if (acl.principalType === ACL.ROLE) { - inRoleTasks.push(function inRoleTasks(done) { - roleModel.isInRole(acl.principalId, context, - function checkIsInRole(err, inRole) { - if (!err && inRole) { - effectiveACLs.push(acl); - } - // else { - // console.log('reject acl ', acl); - // } - done(err, acl); - }); - }); - } - }); - - async.parallel(inRoleTasks, function asyncParallInRoleTasks(err, results) { - if (err) { - if (callback) { - callback(err, null); - } - return; - } - - var resolved = self.resolvePermission(effectiveACLs, req); - if (resolved && resolved.permission === ACL.DEFAULT) { - resolved.permission = (model && model.settings.defaultPermission) || ACL.ALLOW; - } - debug('---Resolved---'); - resolved.debug(); - if (callback) { - callback(null, resolved); - } - }); - }); - }; - - BaseACL.observe('after save', function aclModelObserveAfterSaveFn(ctx, next) { - if (ctx.instance) { - var acl = { - 'principalType': ctx.instance.principalType, - 'principalId': ctx.instance.principalId, - 'permission': ctx.instance.permission, - 'property': ctx.instance.property - }; - var model = loopback.findModel(ctx.instance.model, ctx.options); - if (model) { - model.settings.acls.push(acl); - log.debug(ctx.options, 'Added new ACL ', acl, ' to application'); - } - } else { - log.debug(ctx.options, 'Updated %s matching %j', ctx.Model.pluralModelName, ctx.where); - // TO-DO: Handle the case where a EV_ACL is updated - } - next(); - }); - - /* - * Adding a remote method called /removeacl as an alternative to the loopback provided - * delete method as the 'after delete' event does not provide the deleted object - * in the ctx - */ - BaseACL.removeacl = function aclModelRemoveACLFn(id, options, cb) { - // 'acl' contains the ACL entity for removal - BaseACL.findById(id, options, function aclModelRemoveACLFindCb(err, acl) { - if (err) { - cb(err); - } - // Obtain the name of the model for which the acl is applicable - var modelNameInACL = acl.model; - - // obtain the model corresponding to the modelname in the acl - var model = loopback.findModel(modelNameInACL, options); - - // Delete the ACL from the database - BaseACL.destroyById(id); - - // Filter out (remove) the ACL from the Model's ACL - // by matching each existing ACL against the ACL to be removed - model.settings.acls = model.settings.acls.filter(function aclModelRemoveACLFindFilterFn(a) { - return (!(a.property === acl.property && - a.principalType === acl.principalType && - a.principalId === acl.principalId && - a.permission === acl.permission)); - }); - var response = 'Removed ACL with id ' + id + ' from ' + model.modelName; - log.debug(options, response); - cb(null, response); - }); - }; - - // Registering the remote method as a 'DELETE' method - BaseACL.remoteMethod( - 'removeacl', { - http: { - path: '/removeacl', - verb: 'delete' - }, - accepts: { - arg: 'id', - type: 'string' - }, - returns: { - arg: 'status', - type: 'string' - } - } - ); -}; diff --git a/common/models/framework/base-ACL.json b/common/models/framework/base-ACL.json deleted file mode 100644 index b562d96..0000000 --- a/common/models/framework/base-ACL.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "BaseACL", - "plural": "BaseACLs", - "base": "ACL", - "description": "Access Control List(ACL) for models", - "autoscope": [ - "tenantId" - ], - "mixins": { - "ObserverMixin": true, - "HistoryMixin": false, - "CacheMixin": true, - "ModelValidations": false, - "ExpressionAstPopulatorMixin": false, - "AuditFieldsMixin": false, - "DataPersonalizationMixin": false, - "SwitchDatasourceMixin": true, - "VersionMixin": true, - "WorkflowMixin": false, - "BusinessRuleMixin": false, - "SoftDeleteMixin": false - }, - "properties": {}, - "validations": [], - "relations": { - }, - "options": { - "queryCacheSize": 20000, - "queryCacheExpiration": 86400000 -}, - "acls": [], - "cacheable": true, - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/base-actor-entity.js b/common/models/framework/base-actor-entity.js deleted file mode 100644 index baa33f8..0000000 --- a/common/models/framework/base-actor-entity.js +++ /dev/null @@ -1,803 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -var async = require('async'); -var loopback = require('loopback'); -var log = require('oe-logger')('BaseActorEntity'); -var actorPool = require('../../../lib/actor-pool'); -var StateModel; -var associatedModelsMap = {}; - -module.exports = function (BaseActorEntity) { - BaseActorEntity.setup = function () { - BaseActorEntity.base.setup.call(this); - var BaseActor = this; - BaseActor.disableRemoteMethod('__get__state', false); - BaseActor.disableRemoteMethod('__create__state', false); - BaseActor.disableRemoteMethod('__update__state', false); - BaseActor.disableRemoteMethod('__destroy__state', false); - BaseActor.remoteMethod( - 'validateAndReserveAtomicAction', { - http: { - path: '/validateAndReserveAtomicAction', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - BaseActor.remoteMethod( - 'validateNonAtomicAction', { - http: { - path: '/validateNonAtomicAction', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - BaseActor.remoteMethod( - 'journalSaved', { - http: { - path: '/drainMailBox', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'activity', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - BaseActor.remoteMethod( - 'clearActorMemory', { - http: { - path: '/clearActorMemory', - verb: 'post' - }, - isStatic: false, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - }; - - BaseActorEntity.disableRemoteMethod('__get__state', false); - BaseActorEntity.disableRemoteMethod('__create__state', false); - BaseActorEntity.disableRemoteMethod('__update__state', false); - BaseActorEntity.disableRemoteMethod('__destroy__state', false); - - BaseActorEntity.remoteMethod( - 'validateAndReserveAtomicAction', { - http: { - path: '/validateAndReserveAtomicAction', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - - BaseActorEntity.remoteMethod( - 'validateNonAtomicAction', { - http: { - path: '/validateNonAtomicAction', - verb: 'post' - }, - isStatic: false, - accepts: { - arg: 'filter', - type: 'object' - }, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - - BaseActorEntity.remoteMethod( - 'clearActorMemory', { - http: { - path: '/clearActorMemory', - verb: 'post' - }, - isStatic: false, - returns: { - arg: 'response', - type: 'object', - root: true - } - }); - - BaseActorEntity.prototype.stateModel = 'State'; - - BaseActorEntity.prototype.clearActorMemory = function (options, cb) { - var context = {}; - context.actorEntity = this; - context.activity = {}; - context.activity.modelName = this._type; - context.activity.entityId = this.id; - context.journalEntity = {}; - context.journalEntity.id = ''; - if (!options.ctx) { - options.ctx = {}; - } - options.ctx.noInstanceCache = true; - actorPool.getOrCreateInstance(context, options, function (err, ctx) { - if (err) { - return cb(err); - } - return cb(null, ctx.envelope.noCacheTime); - }); - }; - BaseActorEntity.prototype.getEnvelopeState = function getEnvelopeState(id, options, cb) { - var self = this; - self.constructor.findById(id, options, function (err, actor) { - if (err) { - return cb(err); - } else if (actor === null) { - return cb(new Error('no entity with id ' + id)); - } - actor.balanceProcess(options, cb); - }); - }; - BaseActorEntity.prototype.balanceProcess = function balanceProcess(options, cb) { - var self = this; - var context = {}; - context.actorEntity = self; - context.activity = {}; - context.activity.modelName = self._type; - context.activity.entityId = self.id; - context.journalEntity = {}; - context.journalEntity.id = ''; - if (!options.ctx) { - options.ctx = {}; - } - actorPool.getOrCreateInstance(context, options, function (err, newContext) { - if (err) { - return cb(err); - } - var envelope = newContext.envelope; - self.constructor.instanceLocker().acquire(self, options, self._version, function (releaseLockCb) { - self.getActorFromMemory(envelope, options, function (err, result) { - if (err) { - return releaseLockCb(err); - } - return releaseLockCb(null, result); - }); - }, function (err, ret) { - if (err) { - return cb(err); - } - return cb(null, ret); - }); - }); - }; - BaseActorEntity.prototype.getActorFromMemory = function getActorFromMemory(envelope, options, cb) { - var self = this; - this.processPendingStatus(envelope, options, function (err, actorData) { - if (err) { - return cb(err); - } - var copy = JSON.parse(JSON.stringify(self.__data)); - copy.state = {}; - copy.state.stateObj = actorData; - return cb(null, copy); - }); - }; - - function calculateVals(array, func, entityId, startingObj) { - var startingStateObj = startingObj.stateObj; - var startingSeqNum = startingObj.seqNum; - var resultState = array.filter(x => x.entityId === entityId).reduce(func, startingStateObj); - var resultSeqNum = array.map(x => x.seqNum).reduce(Math.max, startingSeqNum); - var resultObj = { resultState: resultState, resultSeqNum: resultSeqNum }; - return resultObj; - } - - BaseActorEntity.prototype.createMessage = function (activity, journalEntityType, journalEntityVersion, options) { - var message = {}; - message.isProcessed = false; - message.retryCount = 0; - message.skipCount = 0; - message.instructionType = activity.instructionType; - message.payload = activity.payload; - message.activity = activity; - message.version = journalEntityVersion; - message.journalEntityType = journalEntityType; - message.seqNum = activity.seqNum; - message.journalStatus = null; - message.options = options; - return message; - }; - - BaseActorEntity.prototype.addMessage = function (message, context) { - context.envelope.msg_queue.push(message); - }; - - BaseActorEntity.prototype.validateAndReserveAtomicAction = function (context, options, cb) { - var self = this; - context.actorEntity = this; - context.doNotDelete = true; - var actorCopy; - actorPool.getOrCreateInstance(context, options, function (err, ctx) { - if (err) { - return cb(err, { validation: false }); - } - var envelope = ctx.envelope; - self.constructor.instanceLocker().acquire(self, options, self._version, function (releaseLockCb) { - self.processPendingStatus(envelope, options, function (err, actorData) { - if (err) { - return releaseLockCb(err); - } - var validation = self.validateCondition(actorData, ctx.activity); - if (validation === true) { - envelope.seqNum = envelope.seqNum + 1; - ctx.activity.seqNum = envelope.seqNum; - if (self.constructor.settings.noBackgroundProcess) { - actorCopy = JSON.parse(JSON.stringify(actorData)); - if (!envelope.updatedActor) { - envelope.updatedActor = JSON.parse(JSON.stringify(actorData)); - } - actorCopy = self.atomicInstructions(actorCopy, context.activity); - } - self.reserveAmount(ctx, options); - envelope.doNotDelete--; - return releaseLockCb(null, true); - } - return releaseLockCb(null, false); - }); - }, function (err, isValid) { - if (err) { - return cb(err); - } else if (!isValid) { - return cb(null, { validation: false }); - } - if (self.constructor.settings.noBackgroundProcess) { - return cb(null, { validation: true, seqNum: envelope.seqNum, updatedActor: actorCopy}); - } - return cb(null, { validation: true, seqNum: envelope.seqNum}); - }); - }); - }; - - BaseActorEntity.prototype.validateNonAtomicAction = function (context, options, cb) { - context.actorEntity = this; - context.doNotDelete = true; - var self = this; - actorPool.getOrCreateInstance(context, options, function (err, ctx) { - if (err) { - return cb(err, { validation: false }); - } - var envelope = ctx.envelope; - envelope.seqNum = envelope.seqNum + 1; - ctx.activity.seqNum = envelope.seqNum; - self.nonAtomicAction(ctx, options, function (err, updatedActor) { - if (err) { - return cb(err); - } - envelope.doNotDelete--; - if (updatedActor) { - var actorCopy = JSON.parse(JSON.stringify(updatedActor)); - return cb(null, { validation: true, seqNum: envelope.seqNum, updatedActor: actorCopy}); - } - return cb(null, { validation: true, seqNum: envelope.seqNum}); - }); - }); - }; - // should be async - BaseActorEntity.prototype.reserveAmount = function (context, options) { - var journalEntityType = context.journalEntityType; - var journalEntityVersion = context.journalEntityVersion; - - var message = this.createMessage(context.activity, journalEntityType, journalEntityVersion, options); - this.addMessage(message, context); - }; - - var sendNonAtomicMesssage = function (context, self, options, cb) { - var journalEntityType = context.journalEntityType; - var journalEntityVersion = context.journalEntityVersion; - var message = self.createMessage(context.activity, journalEntityType, journalEntityVersion, options); - self.addMessage(message, context); - return cb(); - }; - - var nonAtomicActionNoBackgroundProcess = function (self, envelope, options, cb) { - var actorCopy; - self.constructor.instanceLocker().acquire(self, options, self._version, function (releaseLockCb) { - self.processPendingStatus(envelope, options, function (err, actorData) { - if (err) { - return releaseLockCb(err); - } - actorCopy = JSON.parse(JSON.stringify(actorData)); - if (!envelope.updatedActor) { - envelope.updatedActor = JSON.parse(JSON.stringify(actorData)); - } - actorCopy = self.nonAtomicInstructions(actorCopy, context.activity); - return releaseLockCb(null, true); - }); - }, function (err, isValid) { - if (err) { - return cb(err); - } - sendNonAtomicMesssage(context, self, options, function () { - return cb(null, actorCopy); - }); - }); - }; - - BaseActorEntity.prototype.nonAtomicAction = function (context, options, cb) { - var self = this; - if (this.constructor.settings.noBackgroundProcess) { - return nonAtomicActionNoBackgroundProcess(self, context.envelope, options, cb); - } - sendNonAtomicMesssage(context, self, options, function () { - return cb(); - }); - }; - - var actualBackgroundProcess = function (self, envelope, messages, stateObj, options, actorCb) { - async.eachSeries(messages, function (message, cb) { - self.processMessage(envelope, message, stateObj, options, cb); - }, function (err) { - if (err) { - log.error(options, err); - } - if (self.constructor.settings.noBackgroundProcess) { - envelope.updatedActor = stateObj; - envelope.msg_queue = envelope.msg_queue.filter(x => (!(x.isProcessed))); - return actorCb(); - } - envelope.msg_queue = envelope.msg_queue.filter(x => (!(x.isProcessed))); - if (stateObj.__data.seqNum < envelope.processedSeqNum ) { - stateObj.__data.seqNum = envelope.processedSeqNum; - self.constructor.instanceLocker().acquire(self, options, self._version, function (releaseLockCb) { - stateObj.updateAttributes(stateObj.__data, options, function (error, state) { - if (error) { - log.error(options, 'error while persisting actor ', error); - return releaseLockCb(error); - } - return releaseLockCb(); - }); - }, function (err, ret) { - if (err) { - return actorCb(err); - } - return actorCb(); - }); - } else { - return actorCb(); - } - }); - }; - - BaseActorEntity.prototype.processMessagesBackground = function (envelope, options, actorCb) { - var messages = envelope.msg_queue.slice(0); - var self = this; - - if (messages.length === 0) { - return actorCb(); - } - - if (self.constructor.settings.noBackgroundProcess && envelope.updatedActor) { - return actualBackgroundProcess(self, envelope, messages, envelope.updatedActor, options, actorCb); - } - - var stateModel = getStateModel(this.constructor.modelName, options); - stateModel.findById(this.stateId, options, function (err, stateObj) { - if (err) { - log.error(options, 'error in finding state: ', err); - return actorCb(err); - } else if (!stateObj) { - err = new Error('Actor state not found'); - err.retriable = false; - log.error(options, err); - return actorCb(err); - } - return actualBackgroundProcess(self, envelope, messages, stateObj, options, actorCb); - }); - }; - - BaseActorEntity.prototype.MAX_RETRY_COUNT = 10; - BaseActorEntity.prototype.processPendingMessage = function (message, atomicAmount) { - return atomicAmount; - }; - - var actualCalculate = function (envelope, actorData, self, options, cb) { - var messages = envelope.msg_queue; - for (var i = 0; i < messages.length; i++) { - var message = messages[i]; - if (envelope.isCurrentlyProcessing || !message.isProcessed) { - if (self.atomicTypes.indexOf(message.instructionType) !== -1) { - actorData = self.atomicInstructions(actorData, message); - } else if (self.nonAtomicTypes.indexOf(message.instructionType) !== -1) { - actorData = self.nonAtomicInstructions(actorData, message); - } - } - } - return cb(null, actorData); - }; - - BaseActorEntity.prototype.processPendingStatus = function (envelope, options, cb) { - var self = this; - - if (self.constructor.settings.noBackgroundProcess && envelope.updatedActor) { - return actualCalculate(envelope, JSON.parse(JSON.stringify(envelope.updatedActor)), self, options, cb); - } - - var stateModel = getStateModel(this.constructor.modelName, options); - stateModel.findById(this.stateId, options, function (err, state) { - if (err) { - return cb(err); - } - actualCalculate(envelope, state.__data.stateObj, self, options, cb); - if (self.constructor.settings.noBackgroundProcess && !envelope.updatedActor) { - envelope.updatedActor = JSON.parse(JSON.stringify(state.__data.stateObj)); - } - }); - }; - - BaseActorEntity.prototype.processMessage = function (envelope, message, state, options, cb) { - if (message.isProcessed === true) { - return cb(); - } - var self = this; - - var actualProcess = function (cb) { - if (self.atomicTypes.indexOf(message.instructionType) !== -1) { - if (state.__data) { - self.atomicInstructions(state.__data.stateObj, message.activity); - } else { - self.atomicInstructions(state, message.activity); - } - } else if (self.nonAtomicTypes.indexOf(message.instructionType) !== -1) { - if (state.__data) { - self.nonAtomicInstructions(state.__data.stateObj, message.activity); - } else { - self.nonAtomicInstructions(state, message.activity); - } - } - envelope.processedSeqNum = message.seqNum; - message.isProcessed = true; - return cb(); - }; - - if (message.journalStatus === 'saved') { - return actualProcess(cb); - } - - if (message.retryCount === self.MAX_RETRY_COUNT) { - var model = loopback.getModel(message.journalEntityType, options); - var query = { - where: { _version: message.version } - }; - return model.findOne(query, options, function (err, result) { - if (err) { - log.error(options, 'error in processMessage: ', err); - return cb(err); - } else if (!result) { - message.retryCount += 1; - return cb(new Error('no journal for message:' + message.seqNum)); - } else if (result) { - return actualProcess(cb); - } - }); - } - if (message.retryCount > self.MAX_RETRY_COUNT) { - log.error(options, 'did not find appropriate journal entry for ', message.instructionType, ' : ', message, ' after max retry'); - message.isProcessed = true; - return cb(); - } - message.retryCount += 1; - return cb(); - }; - - function filterfunc(filter, entry) { - return ((entry.entityId === filter.entityId) && (entry.modelName === filter.modelName) && (entry.seqNum > filter.seqNum)); - } - - function filterActivities(activitiesArray, filterBy) { - var filteredctivities = []; - for (var k = 0; k < activitiesArray.length; k++) { - if (filterfunc(filterBy, activitiesArray[k])) { - filteredctivities.push(activitiesArray[k]); - } - } - return filteredctivities; - } - - function filterResults(array, filterBy) { - var filteredArray = []; - for (var i = 0; i < array.length; i++) { - var currJournal = array[i]; - if (currJournal.id !== filterBy.journalId) { - currJournal.atomicActivitiesList = filterActivities(currJournal.atomicActivitiesList ? currJournal.atomicActivitiesList : currJournal.atomicactivitieslist, filterBy); - currJournal.nonAtomicActivitiesList = filterActivities(currJournal.nonAtomicActivitiesList ? currJournal.nonAtomicActivitiesList : currJournal.nonatomicactivitieslist, filterBy); - if (currJournal.atomicActivitiesList.length > 0 || currJournal.nonAtomicActivitiesList.length > 0) { - filteredArray.push(currJournal); - } - } - } - return filteredArray; - } - - BaseActorEntity.prototype.updateStateData = function (activitiesList, startingObj, state, instruction) { - if (activitiesList && activitiesList.length > 0) { - var resultObj = calculateVals(activitiesList, instruction, this.id.toString(), startingObj); - state.stateObj = resultObj.resultState; - state.seqNum = resultObj.resultSeqNum; - } - }; - - function createFilterObj(envelope, state, currentJournalEntityId) { - var filterBy = {}; - filterBy.modelName = envelope.modelName; - filterBy.entityId = envelope.actorId; - filterBy.seqNum = state.seqNum; - filterBy.journalId = currentJournalEntityId; - return filterBy; - } - - var journalFind = function (model, ds, query, options, cb) { - if (ds.name === 'loopback-connector-postgresql') { - var modefiedQuery = query.replace(/TRANSMODEL/g, '\"' + model.modelName.toLowerCase() + '\"'); - ds.connector.query(modefiedQuery, [], options, cb); - } else { - model.find(query, options, cb); - } - }; - - BaseActorEntity.prototype.performStartOperation = function (currentJournalEntityId, options, envelope, cb) { - var loopbackModelsCollection = getAssociatedModels(this.constructor.modelName, options); - envelope.msg_queue = []; - envelope.isCurrentlyProcessing = false; - var self = this; - var stateModel = getStateModel(this.constructor.modelName, options); - stateModel.findById(this.stateId, options, function (err, state) { - if (err) { - log.error(options, 'err in actor startup is ', err); - return cb(err); - } else if (!state) { - err = new Error('Actor state not found'); - err.retriable = false; - return cb(err); - } - envelope.processedSeqNum = envelope.seqNum = state.__data.seqNum; - - if (self.constructor.settings.noBackgroundProcess) { - return cb(); - } - - var query = {}; - var ds = self.getDataSource(options); - if (ds.name === 'mongodb') { - query = { - where: { - or: [ - { atomicActivitiesList: { elemMatch: { entityId: envelope.actorId, modelName: envelope.modelName, seqNum: { $gte: state.seqNum } } } }, - { nonAtomicActivitiesList: { elemMatch: { entityId: envelope.actorId, modelName: envelope.modelName, seqNum: { $gte: state.seqNum } } } } - ] - } - }; - } else if (ds.name === 'loopback-connector-postgresql') { - query = 'select * from public.actoractivity where modelname = \'' + envelope.modelName + '\'' + - ' and entityid = \'' + envelope.actorId + '\' and seqnum > ' + envelope.seqNum + ' order by seqnum asc;'; - } else { - query = { where: { startup: { regexp: '[0-9a-zA-Z]*' + envelope.modelName + envelope.actorId + '[0-9a-zA-Z]*' } } }; - } - async.each(loopbackModelsCollection, function (model, asyncCb) { - journalFind(model, ds, query, options, function (err, returnedInstances) { - if (err) { - if (err.message.includes(' \"' + model.modelName.toLowerCase() + '\" does not exist')) { - return asyncCb(); - } - log.error(options, 'err in actor startup is ', err); - return asyncCb(err); - } else if (returnedInstances.length === 0) { - log.debug('did not find journal instances in startup'); - return asyncCb(); - } - if (ds.name === 'loopback-connector-postgresql') { - var translatePropertyNames = function (origin) { - var originKeys = Object.keys(origin); - var modelKeys = Object.keys(loopback.getModel('ActorActivity', options)._ownDefinition.properties); - modelKeys.forEach(function (modelKey) { - originKeys.forEach(function (originKey) { - if (modelKey.toLowerCase() === originKey) { - origin[modelKey] = origin[originKey]; - delete origin[originKey]; - } - }); - }); - }; - for (var x = 0; x < returnedInstances.length; x++) { - translatePropertyNames(returnedInstances[x]); - returnedInstances[x].payload = JSON.parse(returnedInstances[x].payloadTxt); - var funcToApply = returnedInstances[x].atomic ? self.atomicInstructions : self.nonAtomicInstructions; - state.stateObj = funcToApply(state.stateObj, returnedInstances[x]); - state.seqNum = returnedInstances[x].seqNum; - } - } else { - var filterBy = createFilterObj(envelope, state, currentJournalEntityId); - returnedInstances = filterResults(returnedInstances, filterBy); - for (var i = 0; i < returnedInstances.length; i++) { - var startingObj = { stateObj: state.stateObj, seqNum: state.seqNum }; - self.updateStateData(returnedInstances[i].atomicActivitiesList, startingObj, state, self.atomicInstructions); - self.updateStateData(returnedInstances[i].nonAtomicActivitiesList, startingObj, state, self.nonAtomicInstructions); - log.debug(options, self._type, ' ', self.id, ' Starting Balance ', self); - } - } - envelope.processedSeqNum = envelope.seqNum = state.seqNum; - state.updateAttributes(state.__data, options, function (error, state) { - if (error) { - log.error(options, 'error while persisting actor ', error); - return asyncCb(error); - } - return asyncCb(); - }); - }); - }, function (err) { - if (err) { - return cb(err); - } - return cb(); - }); - }); - }; - - BaseActorEntity.prototype.journalSaved = function (activity, options, cb) { - var self = this; - var id = this.__data.id; - var modelName = this.constructor.modelName; - var setMessageStatus = function (currentEnvelope) { - currentEnvelope.msg_queue.forEach(function (message) { - if (message.seqNum === activity.seqNum) { - message.journalStatus = 'saved'; - return; - } - }, this); - }; - var processEnvelope = function () { - var currentEnvelope = actorPool.getEnvelopeAndReserve(modelName, id); - if (currentEnvelope.isCurrentlyProcessing) { - return cb(); - } - currentEnvelope.isCurrentlyProcessing = true; - self.processMessagesBackground(currentEnvelope, options, function (err) { - currentEnvelope.isCurrentlyProcessing = false; - if (err) { - return cb(err); - } - envelope.doNotDelete--; - if (envelope.doNotDelete === 0 && envelope.msg_queue.length === 0) { - actorPool.destroy(modelName, id); - } - return cb(); - }); - }; - var envelope = actorPool.getEnvelope(modelName, id); - if (envelope === null || typeof envelope === 'undefined') { - return cb(); - } - setMessageStatus(envelope); - if (global.inDBLockMode && global.inDBLockMode()) { - processEnvelope(); - } else { - return cb(); - } - }; - - var updateStateId = function (ctx, instance, value, cb) { - var data = { - stateId: value, - _version: instance._version - }; - instance.updateAttributes(data, ctx.options, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } - return cb(); - }); - }; - - - BaseActorEntity.observe('before save', function (ctx, next) { - if (ctx.instance && ctx.isNewInstance === true) { - ctx.hookState.stateObj = ctx.instance.stateObj; - delete ctx.instance.__data.stateObj; - delete ctx.instance.stateObj; - } - return next(); - }); - - BaseActorEntity.observe('after save', function (ctx, next) { - if (ctx.instance && ctx.isNewInstance === true) { - var stateData = {}; - stateData.stateObj = ctx.hookState.stateObj; - var stateModel = getStateModel(ctx.instance.constructor.modelName, ctx.options); - stateModel.create(stateData, ctx.options, function (err, instance) { - if (err) { - return next(err); - } - log.debug(ctx.options, 'created instance of State ', instance); - if (ctx.instance.stateId) { - return next(); - } - var stateId = instance.id.toString(); - return updateStateId(ctx, ctx.instance, stateId, next); - }); - } else { - return next(); - } - }); - - BaseActorEntity.observe('after delete', function deleteFromMemorypool(ctx, next) { - if (ctx.id) { - log.debug(ctx.options, 'calling memory pool destroy on:', ctx.Model.modelName, ctx.id); - actorPool.destroy(ctx.Model.modelName, ctx.id); - } - if (ctx.instance && ctx.instance.stateId) { - var stateModel = getStateModel(ctx.instance.constructor.modelName, ctx.options); - stateModel.destroyById(ctx.instance.stateId, ctx.options, function (err, res) { - if (err) { - next(err); - } else { - next(); - } - }); - } else { - next(); - } - }); - - function getStateModel(actorType, options) { - if (!StateModel) { - var instanceModel = loopback.getModel(actorType, options); - StateModel = loopback.getModel(instanceModel.prototype.stateModel, options); - } - return StateModel; - } - - function getAssociatedModels(actorType, options) { - if (!associatedModelsMap[actorType]) { - var instanceModel = loopback.getModel(actorType, options); - associatedModelsMap[actorType] = []; - associatedModelsMap[actorType] = instanceModel.prototype.associatedModels.map(function (obj) { - return loopback.getModel(obj, options); - }); - } - return associatedModelsMap[actorType]; - } -}; diff --git a/common/models/framework/base-actor-entity.json b/common/models/framework/base-actor-entity.json deleted file mode 100644 index 2e79cb9..0000000 --- a/common/models/framework/base-actor-entity.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "BaseActorEntity", - "base": "BaseEntity", - "idInjection": true, - "validations": [], - "relations": {}, - "strict": false, - "disableInstanceCache": false, - "properties": - { - "stateId": { - "type": "String" - }, - "noBackgroundProcess": false, - "noCacheTime": 30000 - }, - "options": { - "disableManualPersonalization": true, - "instanceCacheSize": 200000, - "instanceCacheExpiration": 0, - "proxyEnabled": true, - "proxyMethods": [{"name":"validateAndReserveAtomicAction"},{"name":"validateNonAtomicAction"},{"name":"journalSaved"},{"name":"clearActorMemory"}], - "dbLockRequired": true - }, - "acls": [], - "methods": {}, - "mixins": { - "RestApiActorsMixin": true - } -} diff --git a/common/models/framework/base-entity.js b/common/models/framework/base-entity.js deleted file mode 100644 index 42b0616..0000000 --- a/common/models/framework/base-entity.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * @classdesc This Model is the base of most models used in the Framework. - * This model defines hooks to intercept data being - * POSTed to models derived from this model and do the following:
    - *
      - *
    • encrypt properties (which are marked with an "encrypt" : true ) before saving - * the data to database.
      - *
    • decrypted properties (which are marked with an "encrypt" : true ) when fetching - * data from the model API
      - *
    • automatically set properties - * which are marked with a "setval" : ) before saving - * the data to database. - * The pattern-string should be a dot ('.') separated set of - * string values. The first value determines the "source object" - * from where a value needs to be picked up and set to the specified property. - * This first value (source) can be one of BODY, QUERY, HEADER, - * COOKIE, REQUEST, CTX, CALLCONTEXT, ACCESSTOKEN, USER or USERPROFILE.
      - * A property declared to be auto-populated using this feature will always - * override the value, if sent from the client. i.e., the system-generated - * value will overwrite the value supplied by the client in the API (POST, for e.g.,)
      - *
    • validate property values - * for existence in any other Model-field. For this validation to happen, - * properties should be declared with a
      - * "xmodelvalidate" : {"model":, "field": }
      - * where is the other Model against whose data validation needs - * to be done, and is the specific field of that is queried - * for validation. - *
    - * @kind class - * @class BaseEntity - * @author Ajith Vasudevan - */ - -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('baseentity'); - -module.exports = function BaseEntityFn(BaseEntity) { - BaseEntity.setup = function setupBaseEntity() { - BaseEntity.base.setup.call(this, arguments); - var Model = this; - - - Model.beforeRemote('**', function modelBeforeRemote(ctx, model, next) { - var method = ctx.method; - var Model = method.ctor; - // for now taking care of only scope - // so a different tenant will still be able to access - // but still with same scope - if (Model && Model._ownDefinition && Model._ownDefinition.scope) { - var resModel = loopback.findModel(Model.modelName, ctx.req.callContext); - if (!resModel) { - var msg = 'Unknown model or not authorised'; - log.error(ctx.req.callContext, msg + ' for model ' + Model.modelName); - var error = new Error(msg); - error.statusCode = error.status = 404; - error.code = 'MODEL_NOT_FOUND'; - error.name = 'Data Error'; - error.message = msg; - error.code = 'DATA_ERROR_070'; - error.type = 'noModelExists'; - error.retriable = false; - return next(error, null); - } - next(); - } else { - return next(); - } - }); - if (Model.modelName !== 'BaseEntity' && Model.modelName !== 'DbTransaction' && Model.definition.settings.variantOf) { - Model.evObserve('after accesss', getDataFromBaseModels); - } - - if (BaseEntity.modelName === 'BaseEntity') { - return; - } - }; -}; - -const getDataFromBaseModels = function getDataFromBaseModels(ctx, next) { - let result = ctx.accdata || []; - const modelSettings = ctx.Model.definition.settings; - if (modelSettings.variantOf) { - var variantModel = loopback.findModel(modelSettings.variantOf); - if (variantModel) { - if (isSameCollection(variantModel.definition.settings, modelSettings)) { - return next(); - } - variantModel.find(ctx.query, ctx.options, function (err, variantData) { - if (err) { - return next(err); - } - if (variantData && variantData.length) { - result = result.concat(variantData); - ctx.accdata = result; - } - return next(); - }); - } else { - return next(); - } - } else { - return next(); - } -}; - -/** - * This function is used to find whether whether variant model use same collection. - * - * @param {object}settings1 - settings of first model - * @param {object}settings2 - settings of second model - * @returns {boolean} - returns true for same collection. - * @function - */ -const isSameCollection = function isSameCollection(settings1, settings2) { - if (!settings1.mongodb || !settings2.mongodb) { - return false; - } - var collection1 = settings1.mongodb.collection; - if (collection1) { collection1 = collection1.toLowerCase(); } - var collection2 = settings2.mongodb.collection; - if (collection2) { collection2 = collection2.toLowerCase(); } - if (collection1 === collection2) { return true; } - return false; -}; diff --git a/common/models/framework/base-entity.json b/common/models/framework/base-entity.json deleted file mode 100644 index 4e35781..0000000 --- a/common/models/framework/base-entity.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "BaseEntity", - "base": "PersistedModel", - "idInjection": true, - "strict": true, - "autoscope": [ - "tenantId" - ], - "options": { - "validateUpsert": true, - "trackChanges": false, - "dbLockRequired": false, - "disableManualPersonalization":true - }, - "mixins": { - "ObserverMixin": true, - "CacheMixin": true, - "ModelValidations": true, - "PropertyExpressionMixin": true, - "ExpressionAstPopulatorMixin": true, - "AuditFieldsMixin": true, - "DataPersonalizationMixin": true, - "SwitchDatasourceMixin": true, - "BusinessRuleMixin": true, - "SoftDeleteMixin": true, - "VersionMixin": true, - "HistoryMixin": true, - "RetrySupportMixin": false, - "IdempotentMixin": true, - "FailsafeObserverMixin": true, - "DataHierarchyMixin": false, - "CryptoMixin": false, - "AutoFieldsMixin": false - }, - "hidden": [ - "_tenantId", - "_scope", - "_autoScope", - "_score", - "_weight", - "__oldVersion" - ], - "validations": [], - "relations": {}, - "acls": [{ - "accessType": "WRITE", - "principalType": "ROLE", - "principalId": "$unauthenticated", - "permission": "DENY" - }], - "methods": {}, - "disableInstanceCache": true -} \ No newline at end of file diff --git a/common/models/framework/base-journal-entity.js b/common/models/framework/base-journal-entity.js deleted file mode 100644 index 71c42be..0000000 --- a/common/models/framework/base-journal-entity.js +++ /dev/null @@ -1,286 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('journal-entity'); -var loopback = require('loopback'); -var actorModelsMap = {}; - -module.exports = function (BaseJournalEntity) { - var performAtomicOperation = function (journalEntity, operationContexts, next) { - if (operationContexts.length === 0) { - return next(); - } - var startup = ''; - - var seen = {}; - var hasDuplicates = operationContexts.some(function (currentObject) { - return seen.hasOwnProperty(currentObject.actorEntity.id) || (seen[currentObject.actorEntity.id] = false); - }); - - var asyncFunc = async.each; - if (hasDuplicates) { - asyncFunc = async.eachSeries; - } - asyncFunc(operationContexts, function (operationContext, callback) { - var actor = operationContext.actorEntity; - actor.validateAndReserveAtomicAction(operationContext, operationContext.options, function (err, validationObj) { - if (err) { - return callback(err); - } else if (validationObj.validation === false) { - var error2 = new Error('validation failed on atomic operation'); - error2.retriable = false; - return callback(error2); - } - operationContext.activity.seqNum = validationObj.seqNum; - startup = startup + operationContext.activity.modelName + operationContext.activity.entityId + '$'; - if (validationObj.updatedActor) { - operationContext.activity.updatedActor = validationObj.updatedActor; - } - return callback(); - }); - }, function (err) { - if (err) { - return next(err); - } - return next(null, startup); - }); - }; - - var performNonAtomicOperation = function (journalEntity, operationContext, next) { - var startup = ''; - var actor = operationContext.actorEntity; - delete operationContext.actorEntity; - var options = operationContext.options; - delete operationContext.options; - return actor.validateNonAtomicAction(operationContext, options, function (err, validationObj) { - if (err) { - return next(err); - } else if (!validationObj.validation) { - var error = new Error('Validation on non atomic activity failed'); - error.retriable = false; - return next(error); - } - operationContext.activity.seqNum = validationObj.seqNum; - startup = operationContext.activity.modelName + operationContext.activity.entityId; - if (validationObj.updatedActor) { - operationContext.activity.updatedActor = validationObj.updatedActor; - } - return next(null, startup); - }); - }; - - BaseJournalEntity.prototype.performOperations = function (ctx, next) { - var instance = ctx.instance; - var options = ctx.options; - var atomicActivityList = instance.atomicActivitiesList; - var nonAtomicActivityList = instance.nonAtomicActivitiesList; - - var createOperationContext = function (activity, callback) { - var Model = getActorModel(activity.modelName, ctx.options); - activity.modelName = Model.modelName; - var operationContext = {}; - var query = { where: { id: activity.entityId }, limit: 1 }; - Model.find(query, options, function (err, actor) { - if (err) { - return callback(err); - } else if (actor.length === 0) { - return callback(new Error('Invalid activity. No actor with id ' + activity.entityId)); - } else if (actor.length > 1) { - return callback(new Error('Something went wrong. Too many ctors with id ' + activity.entityId)); - } - operationContext.activity = activity; - operationContext.journalEntityId = instance.id; - operationContext.journalEntityVersion = instance._version; - operationContext.journalEntityType = ctx.Model.definition.name; - operationContext.activity = activity; - operationContext.actorEntity = actor[0]; - if (!ctx.hookState.actorInstancesMap) { - ctx.hookState.actorInstancesMap = {}; - } - ctx.hookState.actorInstancesMap[activity.entityId] = actor[0]; - operationContext.options = options; - return callback(null, operationContext); - }); - }; - - var mapAndPreformAtomic = function (atomicActivityList, cb) { - async.map(atomicActivityList, createOperationContext, function (err, operationContexts) { - if (err) { - return cb(err); - } - performAtomicOperation(instance, operationContexts, function (err, res) { - if (err) { - return cb(err); - } - return cb(null, res); - }); - }); - }; - - var createAndPerformeNonAtomic = function (activity, cb) { - createOperationContext(activity, function (err, operationContext) { - if (err) { - return cb(err); - } - performNonAtomicOperation(instance, operationContext, function (err, res) { - if (err) { - return cb(err); - } - return cb(null, res); - }); - }); - }; - - var mapAndPreformNonAtomic = function (nonAtomicActivityList, cb) { - async.map(nonAtomicActivityList, createAndPerformeNonAtomic, function (err, results) { - if (err) { - return cb(err); - } - return cb(null, results.join('$') ); - }); - }; - - if (atomicActivityList && atomicActivityList.length || nonAtomicActivityList && nonAtomicActivityList.length) { - mapAndPreformAtomic(atomicActivityList, function (err, resAtomic) { - if (err) { - return next(err); - } - mapAndPreformNonAtomic(nonAtomicActivityList, function (err, resNonAtomic) { - if (err) { - return next(err); - } - instance.startup = (resAtomic + resNonAtomic); - return next(); - }); - }); - } else { - return next(); - } - }; - - BaseJournalEntity.prototype.performBusinessValidations = function (options, ctx, cb) { - log.error('No business validations were implemented. Please Implement, and run again.'); - throw new Error('No business validations were implemented. Please Implement, and run again.'); - }; - - BaseJournalEntity.prototype.changeInstance = function (options, ctx, cb) { - return cb(); - }; - - BaseJournalEntity.prototype.startTarnsactionFlow = function (instance, ctx, options, next) { - instance.performBusinessValidations(options, ctx, function (err) { - if (err) { - log.error(ctx.options, err.message); - next(err); - } else { - BaseJournalEntity.prototype.performOperations(ctx, function (err, result) { - if (err) { - if (ctx.hookState && ctx.hookState.actorInstancesMap) { - async.each(Object.keys(ctx.hookState.actorInstancesMap), function (key, cb) { - var actor = ctx.hookState.actorInstancesMap[key]; - if (actor.constructor.settings.noBackgroundProcess) { - actor.clearActorMemory(ctx.options, function () { - cb(); - }); - } else { - cb(); - } - }, function () { - return next(err); - }); - } else { - return next(err); - } - } else { - return next(); - } - }); - } - }); - }; - - BaseJournalEntity.observe('before save', function (ctx, next) { - if (ctx.isNewInstance === false || !(ctx.instance)) { - var err = new Error('Cannot update existing journal entry'); - err.retriable = false; - return next(err); - } - if (ctx.instance.id === 'xxx') { - return next(); - } - var instance = ctx.instance; - var options = ctx.options; - instance.changeInstance(options, ctx, function (err) { - if (err) { - log.error(ctx.options, err.message); - return next(err); - } - return instance.startTarnsactionFlow(instance, ctx, options, next); - }); - }); - - BaseJournalEntity.observe('after delete', function (ctx, next) { - var err = new Error('Cannot delete journal entry'); - err.retriable = false; - next(err); - }); - - - var doAfterSave = function (ctx) { - var atomicActivitiesList = ctx.instance.atomicActivitiesList; - var nonAtomicActivitiesList = ctx.instance.nonAtomicActivitiesList; - var activities = atomicActivitiesList.concat(nonAtomicActivitiesList); - async.each(activities, function (activity, cb) { - var options = ctx.options; - var actor; - if (ctx.hookState && ctx.hookState.actorInstancesMap && ctx.hookState.actorInstancesMap[activity.entityId]) { - actor = ctx.hookState.actorInstancesMap[activity.entityId]; - } - if (actor) { - if (ctx.instance._connectorData && ctx.instance._connectorData.error - && actor.constructor.settings.noBackgroundProcess) { - log.info(ctx.instance._type + ' Failed! Clearing Actor ' + activity.entityId); - ctx.instance.startup = JSON.stringify(ctx.instance._connectorData.error); - actor.clearActorMemory(ctx.options, function () { - cb(); - }); - } else { - actor.journalSaved(activity.toObject(), options, function (err) { - if (err) { - return cb(err); - } - cb(); - }); - } - } else { - var err = new Error('Invalid activity. No actor with id ' + activity.entityId); - err.retriable = false; - return cb(err); - } - }, function (err) { - if (err) { - log.info('error in JournalSaved: ' + err); - } - log.info('JournalSaved done'); - return; - }); - }; - - BaseJournalEntity.observe('after save', function drainActorMailBox(ctx, next) { - next(); - return process.nextTick(doAfterSave, ctx); - }); - - function getActorModel(modelName, options) { - if (!actorModelsMap[modelName]) { - actorModelsMap[modelName] = loopback.getModel(modelName, options); - } - return actorModelsMap[modelName]; - } -}; diff --git a/common/models/framework/base-journal-entity.json b/common/models/framework/base-journal-entity.json deleted file mode 100644 index ae6c26b..0000000 --- a/common/models/framework/base-journal-entity.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "BaseJournalEntity", - "base": "BaseEntity", - "idInjection": true, - "description": "Enable to perform several activities togather in a way that all will succeed or fail", - "properties": { - "startup": { - "type": "string", - "index": true - } - }, - "hidden": [ "startup" ], - "validations": [], - "relations": { - "atomicActivities": { - "type": "embedsMany", - "model": "ActorActivity", - "property": "atomicActivitiesList" - }, - "nonAtomicActivities": { - "type": "embedsMany", - "model": "ActorActivity", - "property": "nonAtomicActivitiesList" - } - }, - "mixins": { - "ModelValidations": true, - "PropertyExpressionMixin": false, - "ExpressionAstPopulatorMixin": false, - "BusinessRuleMixin": false, - "SoftDeleteMixin": false, - "HistoryMixin": false, - "ReadOnlyMixin": true - }, - "triggers": [{ - "triggerName": "onCreate", - "triggerFunc": "create_activities", - "queryContext": { - "ctx": { - "remoteUser": "admin", - "tenantId": "default" - } - } - }], - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/base-otp.js b/common/models/framework/base-otp.js deleted file mode 100644 index 78fa986..0000000 --- a/common/models/framework/base-otp.js +++ /dev/null @@ -1,505 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * @classdesc This model provides methods related to OTP. - * - * @kind class - * @class OTP - * @author Gourav Gupta - */ - -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('base-otp'); -var crypto = require('crypto'); -var request = require('request'); -var async = require('async'); - -module.exports = function BaseOTP(otpModel) { - otpModel.disableRemoteMethod('create', true); - otpModel.disableRemoteMethod('upsert', true); - otpModel.disableRemoteMethod('updateAll', true); - otpModel.disableRemoteMethod('updateAttributes', false); - - otpModel.disableRemoteMethod('find', true); - otpModel.disableRemoteMethod('findById', true); - otpModel.disableRemoteMethod('findOne', true); - - otpModel.disableRemoteMethod('deleteById', true); - - otpModel.disableRemoteMethod('count', true); - otpModel.disableRemoteMethod('exists', true); - - otpModel.send = function send(data, req, res, options, cb) { - var self = this; - // validate for default wait resend time - if (typeof options === 'function') { - cb = options; - options = {}; - } - - var app = self.app; - var otpConfig = app.get('otp'); - - var defaultConfig = { - ttl: otpConfig.DEFAULT_TTL || 180000, - resend: otpConfig.MAX_FAILED_ATTEMPTS || 3, - failed: otpConfig.MAX_RESEND_ATTEMPTS || 3, - enableFailedTTL: (otpConfig.ENABLE_FAILED_TTL ? true : false) || false, - mail: (otpConfig.ENABLE_MAIL ? true : false) || false, - sms: (otpConfig.ENABLE_SMS ? true : false) || false - }; - - if (defaultConfig.enableFailedTTL) { - defaultConfig.failedTTL = 900000; - } - - var smsConfig; - - data.otp = self.generateOTP(); - // data.status = 'generated'; - data.resend = 0; - data.failed = 0; - - if (data.config && typeof data.config === 'object') { - data.config.ttl = data.config.ttl ? data.config.ttl : defaultConfig.ttl; - data.config.enableFailedTTL = (data.config.hasOwnProperty('enableFailedTTL')) ? data.config.enableFailedTTL : defaultConfig.enableFailedTTL; - if (data.config.enableFailedTTL) { - data.config.failedTTL = data.config.failedTTL ? data.config.failedTTL : defaultConfig.failedTTL; - } - data.config.failed = data.config.failed ? data.config.failed : defaultConfig.failed; - data.config.resend = data.config.resend ? data.config.resend : defaultConfig.resend; - data.config.mail = (data.config.hasOwnProperty('mail')) ? data.config.mail : defaultConfig.mail; - data.config.sms = (data.config.hasOwnProperty('sms')) ? data.config.sms : defaultConfig.sms; - } else { - data.config = defaultConfig; - } - - if (otpConfig.MAIL_FROM) { - data.config.mailFrom = otpConfig.MAIL_FROM; - } - if (otpConfig.SMS) { - smsConfig = otpConfig.SMS; - } - - data.expire = Date.now() + data.config.ttl; - - var orQuery = []; - if (data.phone) { - orQuery.push({ 'phone': data.phone }); - } - if (data.mail) { - orQuery.push({ 'mail': data.mail }); - } - - if (orQuery.length === 0) { - return cb(new Error('Require phone or mail to send OTP')); - } - - self.find({ 'where': { 'or': orQuery } }, options, function (findErr, findRes) { - if (findErr) { - log.error(options, findErr.message); - return cb(findErr); - } - - if (findRes.length !== 0) { - log.trace(options, 'send: updating existing instance'); - findRes = findRes[0]; - - if (findRes.config.enableFailedTTL && (findRes.failed >= findRes.config.failed) && ((Date.now() - findRes._modifiedOn.getTime()) <= findRes.config.failedTTL)) { - log.error(options, 'OTP failed wait time not exceeded'); - return cb(new Error('OTP failed wait time not exceeded')); - } - - var keys = Object.keys(data); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (data.hasOwnProperty(key)) { - findRes[key] = data[key]; - } - } - data = JSON.parse(JSON.stringify(findRes)); - } - - self.upsert(data, options, function (err, result) { - if (err) { - log.error(options, err.message); - return cb(err); - } - self.sendOTP(data, smsConfig, function (err, status) { - if (err) { - log.error(options, err.message); - return cb(err); - } - log.info(options, 'Inserted record and OTP sent'); - status.otpId = result.id; - return cb(null, status); - }); - }); - }); - }; - - otpModel.generateOTP = function generateOTP() { - var randomNumberSet = '123456789'; - var rnd = crypto.randomBytes(4); - var value = new Array(4); - var len = randomNumberSet.length; - - for (var i = 0; i < 4; i++) { - value[i] = randomNumberSet[rnd[i] % len]; - } - return parseInt(value.join(''), 10); - }; - - otpModel.verify = function verify(data, req, res, options, cb) { - var self = this; - // var otpInstanceID = req.cookies.otp_id; - var otpInstanceID = data.otpId; - if (!otpInstanceID) { - log.error(options, 'Unknown OTP request or Exceeded maximum retries'); - return cb(new Error('Unknown OTP request or Exceeded maximum retries')); - } - - self.findById(otpInstanceID, options, function (err, result) { - if (err) { - log.error(options, err.message); - return cb(err); - } - - if (!result) { - log.error(options, 'No record found'); - return cb(new Error('No record found')); - } - - if (result.failed >= result.config.failed) { - // res.clearCookie('otp_id'); - log.error(options, 'Exceeded maximum retries'); - return cb(new Error('Exceeded maximum retries')); - } - - if (Date.now() >= result.expire) { - // res.clearCookie('otp_id'); - log.error(options, 'OTP timed out'); - return cb(new Error('OTP timed out')); - } - - if (data.otp === result.otp) { - self.deleteById(otpInstanceID, options, function (err, deleteResp) { - if (err) { - log.error(options, 'Error when deleting record after OTP verified', err); - } - // res.clearCookie('otp_id'); - log.info(options, 'OTP verified'); - return cb(null, { 'status': 'verified' }); - }); - } else { - result.updateAttribute('failed', result.failed + 1, options, function (err, updateResp) { - if (err) { - log.error(options, 'Error when updating record after OTP verification failed', err); - return cb(err); - } - log.info(options, 'Verification failed'); - return cb(new Error('Verification failed')); - }); - } - }); - }; - - otpModel.resend = function resend(data, req, res, options, cb) { - var self = this; - // var otpInstanceID = req.cookies.otp_id; - var otpInstanceID = data.otpId; - - var app = self.app; - var otpConfig = app.get('otp'); - var smsConfig; - - if (otpConfig.SMS) { - smsConfig = otpConfig.SMS; - } - - if (!otpInstanceID) { - log.error(options, 'Unknown OTP request'); - return cb(new Error('Unknown OTP request')); - } - - self.findById(otpInstanceID, options, function (err, result) { - if (err) { - log.error(options, err.message, err); - return cb(err); - } - - if (!result) { - log.error(options, 'No record found'); - return cb(new Error('No record found')); - } - - if (result.resend >= result.config.resend) { - // res.clearCookie('otp_id'); - log.error(options, 'Exceeded maximum resend'); - return cb(new Error('Exceeded maximum resend')); - } - - if (Date.now() >= result.expire) { - // res.clearCookie('otp_id'); - log.error(options, 'OTP timed out'); - return cb(new Error('OTP timed out')); - } - - self.sendOTP(result, smsConfig, function (err, status) { - if (err) { - log.error(options, err.message, err); - return cb(err); - } - result.updateAttribute('resend', result.resend + 1, options, function (err, updateResp) { - if (err) { - log.error(options, err.message, err); - return cb(err); - } - log.info(options, 'Resend the OTP', status); - return cb(null, status); - }); - }); - }); - }; - - otpModel.sendOTP = function sendOTP(data, smsConfig, cb) { - var self = this; - var asyncFn = {}; - if (data.config.sms) { - asyncFn.sms = function (cb) { self.sendSMS(data, smsConfig, cb); }; - } - - if (data.config.mail) { - asyncFn.mail = function (cb) { self.sendMail(data, cb); }; - } - - async.parallel(asyncFn, function (err, results) { - if (err) { - return cb(err); - } - var resp = {}; - - if (data.config.sms) { - if (results.sms instanceof Error) { - resp.sms = { 'status': 'failed', 'error': results.sms.message }; - } else { - resp.sms = { 'status': 'success' }; - } - } - - if (data.config.mail) { - if (results.mail instanceof Error) { - resp.mail = { 'status': 'failed', 'error': results.mail.message }; - } else { - resp.mail = { 'status': 'success' }; - } - } - - cb(null, resp); - }); - }; - - otpModel.sendSMS = function sendSMS(data, smsConfig, cb) { - var numbers = data.phone; - var message = encodeURIComponent('OTP generated is ' + data.otp); - var smsAPI = smsConfig.API; - var apiKey = smsConfig.API_KEY; - // ignoring sender name as its not there for promotional account - // var sender = smsConfig.FROM; - // var getURL = smsAPI + '?apikey=' + apiKey + '&numbers=' + numbers + '&message=' + message + '&sender=' + sender; - var getURL = smsAPI + '?apikey=' + apiKey + '&numbers=' + numbers + '&message=' + message; - - var options = { - method: 'POST', - url: getURL, - headers: { 'content-type': 'application/x-www-form-urlencoded' } - }; - - request(options, function (error, response, body) { - if (error) { - log.error(log.defaultContext(), error.message, error); - return cb(null, error); - } - - if (body) { - if (body.status && body.status === 'success') { - log.info(log.defaultContext(), 'OTP sent successfully'); - cb(null, 'success'); - } else { - var errorMessage = ''; - if (body.errors && body.errors.length > 0) { - body.errors.forEach(function (err) { - errorMessage = errorMessage + err.message + ' ; '; - }); - } - if (body.warnings && body.warnings.length > 0) { - body.warnings.forEach(function (warn) { - errorMessage = errorMessage + warn.message + ' ; '; - }); - } - log.error(log.defaultContext(), errorMessage); - cb(null, new Error(errorMessage)); - } - } - }); - }; - - otpModel.sendMail = function sendMail(data, cb) { - var html = 'OTP generated is ' + data.otp; - var mailTo = data.mail; - var mailFrom = data.mailFrom; - var Email = loopback.findModel('Email'); - Email.send({ - to: mailTo, - from: mailFrom, - subject: 'OTP for login', - html: html - }, function emailSend(err) { - if (err) { - log.error(log.defaultContext(), err); - cb(null, err); - } else { - log.info(log.defaultContext(), 'otp mail sent to ', mailTo); - cb(null, 'success'); - } - }); - }; - - otpModel.remoteMethod( - 'send', - { - description: 'Send OTP', - accepts: [ - { arg: 'data', type: 'object', required: true, 'http': { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'id', - type: 'object', - root: true, - description: 'The response body contains otp instance ID' - }, - http: { verb: 'post' } - } - ); - - otpModel.remoteMethod( - 'verify', - { - description: 'Verify OTP', - accepts: [ - { arg: 'data', type: 'object', required: true, 'http': { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'accessToken', type: 'object', root: true, - description: - 'The response body contains status' - }, - http: { verb: 'post' } - } - ); - - otpModel.remoteMethod( - 'resend', - { - description: 'Resend OTP', - accepts: [ - { arg: 'data', type: 'object', 'http': { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'accessToken', type: 'object', root: true, - description: - 'The response body contains status' - }, - http: { verb: 'post' } - } - ); - - var originalSetup = otpModel.setup; - // this will be called everytime a - // model is extended from this model. - otpModel.setup = function () { - // This is necessary if your - // AnotherModel is based of another model, like PersistedModel. - originalSetup.apply(this, arguments); - - this.disableRemoteMethod('create', true); - this.disableRemoteMethod('upsert', true); - this.disableRemoteMethod('updateAll', true); - this.disableRemoteMethod('updateAttributes', false); - - this.disableRemoteMethod('find', true); - this.disableRemoteMethod('findById', true); - this.disableRemoteMethod('findOne', true); - - this.disableRemoteMethod('deleteById', true); - - this.disableRemoteMethod('count', true); - this.disableRemoteMethod('exists', true); - - this.remoteMethod( - 'send', - { - description: 'Send OTP', - accepts: [ - { arg: 'data', type: 'object', required: true, http: { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'id', - type: 'object', - root: true, - description: 'The response body contains otp instance ID' - }, - http: { verb: 'post' } - } - ); - - this.remoteMethod( - 'verify', - { - description: 'Verify OTP', - accepts: [ - { arg: 'data', type: 'object', required: true, http: { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'accessToken', type: 'object', root: true, - description: - 'The response body contains status' - }, - http: { verb: 'post' } - } - ); - - this.remoteMethod( - 'resend', - { - description: 'Resend OTP', - accepts: [ - { arg: 'data', type: 'object', 'http': { source: 'body' } }, - { arg: 'req', type: 'object', 'http': { source: 'req' } }, - { arg: 'res', type: 'object', 'http': { source: 'res' } } - ], - returns: { - arg: 'accessToken', type: 'object', root: true, - description: - 'The response body contains status' - }, - http: { verb: 'post' } - } - ); - }; -}; diff --git a/common/models/framework/base-otp.json b/common/models/framework/base-otp.json deleted file mode 100644 index b9a904b..0000000 --- a/common/models/framework/base-otp.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "BaseOTP", - "base": "PersistedModel", - "plural": "BaseOTP", - "idInjection": false, - "description": "This model is used in OTP functionality", - "readonly": false, - "strict": false, - "public": true, - "validateUpsert": false, - "mixins": { - "ObserverMixin": true, - "AuditFieldsMixin": true - }, - "options": { - "isFrameworkModel": true, - "validateUpsert": true - }, - "properties": { - "otp": { - "type": "number" - }, - "mail": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "failed": { - "type": "number" - }, - "resend": { - "type": "number" - }, - "expire": { - "type": "number" - }, - "config": { - "type": "object" - } - }, - "hidden": [], - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/base-role-mapping.json b/common/models/framework/base-role-mapping.json deleted file mode 100644 index 7319b1c..0000000 --- a/common/models/framework/base-role-mapping.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "BaseRoleMapping", - "plural": "BaseRoleMappings", - "base": "RoleMapping", - "properties": { - "providerRole": { - "type": "string", - "default": "none" - } - }, - "autoscope": [ - "tenantId" - ], - "options": { - "disableManualPersonalization":true - }, - "mixins": { - "ObserverMixin": true, - "HistoryMixin": true, - "CacheMixin": true, - "ModelValidations": true, - "ExpressionAstPopulatorMixin": true, - "AuditFieldsMixin": true, - "DataPersonalizationMixin": true, - "ServicePersonalizationMixin": true, - "SwitchDatasourceMixin": true, - "VersionMixin": false, - "WorkflowMixin": true, - "BusinessRuleMixin": true, - "SoftDeleteMixin": true - }, - "validations": [], - "relations": { - "role": { - "type": "belongsTo", - "model": "BaseRole", - "foreignKey": "roleId" - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/base-role.js b/common/models/framework/base-role.js deleted file mode 100644 index a65d611..0000000 --- a/common/models/framework/base-role.js +++ /dev/null @@ -1,225 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var loopbackAccessContext = require('loopback/lib/access-context'); -var AccessContext = loopbackAccessContext.AccessContext; -var loopback = require('loopback'); -var debug = require('debug')('loopback:security:role'); -var async = require('async'); -var assert = require('assert'); - -module.exports = function BaseRoleFn(BaseRole) { - // Set up the connection to users/applications/roles once the model - BaseRole.once('dataSourceAttached', function baseRoleDataSourceAttachedListner(roleModel) { - // Special roles - // owner of the object - BaseRole.OWNER = '$owner'; - // any User with a relationship to the object - BaseRole.RELATED = '$related'; - // authenticated user - BaseRole.AUTHENTICATED = '$authenticated'; - // authenticated user - BaseRole.UNAUTHENTICATED = '$unauthenticated'; - // everyone - BaseRole.EVERYONE = '$everyone'; - BaseRole.registerResolver(BaseRole.OWNER, function baseRoleRegisterResolver(role, context, callback) { - if (!context || !context.model || !context.modelId) { - process.nextTick(function noContext() { - if (callback) callback(null, false); - }); - return; - } - var modelClass = context.model; - var modelId = context.modelId; - var userId = context.getUserId(); - BaseRole.isOwner(modelClass, modelId, userId, context, callback); - }); - function isUserClass(modelClass) { - if (modelClass) { - return modelClass === loopback.User || - modelClass.prototype instanceof loopback.User; - } - return false; - } - function matches(id1, id2) { - if (typeof id1 === 'undefined' || id1 === null || id1 === '' || - typeof id2 === 'undefined' || id2 === null || id2 === '') { - return false; - } - // The id can be a MongoDB ObjectID - return id1 === id2 || id1.toString() === id2.toString(); - } - BaseRole.isOwner = function isOwner(modelClass, modelId, userId, context, callback) { - assert(modelClass, 'Model class is required'); - debug('isOwner(): %s %s userId: %s', modelClass && modelClass.modelName, modelId, userId); - // No userId is present - if (!userId) { - process.nextTick(function noUserId() { - callback(null, false); - }); - return; - } - - // Is the modelClass User or a subclass of User? - if (isUserClass(modelClass)) { - process.nextTick(function userClass() { - callback(null, matches(modelId, userId)); - }); - return; - } - - modelClass.findById(modelId, context.remotingContext.req.callContext, function modelClassFindById(err, inst) { - if (err || !inst) { - debug('Model not found for id %j', modelId); - if (callback) callback(err, false); - return; - } - debug('Model found: %j', inst); - var ownerId = inst.userId || inst.owner; - // Ensure ownerId exists and is not a function/relation - if (ownerId && typeof ownerId !== 'function') { - if (callback) callback(null, matches(ownerId, userId)); - return; - } - // Try to follow belongsTo - for (var r in modelClass.relations) { - if (modelClass.relations.hasOwnProperty(r)) { - var rel = modelClass.relations[r]; - if (rel.type === 'belongsTo' && isUserClass(rel.modelTo)) { - debug('Checking relation %s to %s: %j', r, rel.modelTo.modelName, rel); - inst[r](processRelatedUser); - return; - } - } - } - debug('No matching belongsTo relation found for model %j and user: %j', modelId, userId); - if (callback) callback(null, false); - - - function processRelatedUser(err, user) { - if (!err && user) { - debug('User found: %j', user.id); - if (callback) callback(null, matches(user.id, userId)); - } else if (callback) callback(err, false); - } - }); - }; - - - /** - * Check if a given principal is in the specified role. - * - * @param {String} role The role name. - * @param {Object} context The context object. - * @param {Function} callback Callback function. - * @param {Error} err Error object. - * @param {Boolean} isInRole True if the principal is in the specified role. - */ - BaseRole.isInRole = function baseRoleIsInRole(role, context, callback) { - if (!(context instanceof AccessContext)) { - context = new AccessContext(context); - } - - var options; - if (context.remotingContext && context.remotingContext.req.callContext) { - options = context.remotingContext.req.callContext; - } else { - options = context.options || {}; - } - - this.resolveRelatedModels(); - - debug('isInRole(): %s', role); - context.debug(); - - var resolver = loopback.Role.resolvers[role]; - if (resolver) { - debug('Custom resolver found for role %s', role); - resolver(role, context, callback); - return; - } - - if (context.principals.length === 0) { - debug('isInRole() returns: false'); - process.nextTick(function noRole() { - if (callback) { - callback(null, false); - } - }); - return; - } - - var inRole = context.principals.some(function inRole(p) { - var principalType = p.type || null; - var principalId = p.id || null; - - // Check if it's the same role - return principalType === loopback.RoleMapping.ROLE && principalId === role; - }); - - if (inRole) { - debug('isInRole() returns: %j', inRole); - process.nextTick(function noRole() { - if (callback) { - callback(null, true); - } - }); - return; - } - - var roleMappingModel = this.roleMappingModel; - this.findOne({ where: { name: role } }, options, function findOne(err, result) { - if (err) { - if (callback) { - callback(err); - } - return; - } - if (!result) { - if (callback) { - callback(null, false); - } - return; - } - debug('Role found: %j', result); - - // Iterate through the list of principals - async.some(context.principals, function principle(p, done) { - var principalType = p.type || null; - var principalId = p.id || null; - var roleId = result.id.toString(); - - if (principalId !== null && typeof principalId !== 'undefined' && (typeof principalId !== 'string')) { - principalId = principalId.toString(); - } - - if (principalType && principalId) { - roleMappingModel.findOne({ - where: { - roleId: roleId, - principalType: principalType, principalId: principalId - } - }, - options, function roleMappingModelFindOne(err, result) { - debug('Role mapping found: %j', result); - // The only arg is the result - done(!err && result); - }); - } else { - process.nextTick(function findOneErr() { - done(false); - }); - } - }, function asyncFinalCb(inRole) { - debug('isInRole() returns: %j', inRole); - if (callback) { - callback(null, inRole); - } - }); - }); - }; - }); -}; diff --git a/common/models/framework/base-role.json b/common/models/framework/base-role.json deleted file mode 100644 index 7c68e79..0000000 --- a/common/models/framework/base-role.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "BaseRole", - "plural": "BaseRoles", - "base": "Role", - "description": "Stores Authorization data of the user", - "autoscope": [ - "tenantId" - ], - "options": { - "disableManualPersonalization":true - }, - "mixins": { - "ObserverMixin": true, - "HistoryMixin": true, - "CacheMixin": true, - "ModelValidations": true, - "ExpressionAstPopulatorMixin": true, - "AuditFieldsMixin": true, - "DataPersonalizationMixin": true, - "ServicePersonalizationMixin": true, - "SwitchDatasourceMixin": true, - "VersionMixin": true, - "WorkflowMixin": true, - "BusinessRuleMixin": true, - "SoftDeleteMixin": true - }, - "properties": {}, - "validations": [], - "relations": { - "principals": { - "type": "hasMany", - "model": "BaseRoleMapping", - "foreignKey": "roleId" - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/base-type.json b/common/models/framework/base-type.json deleted file mode 100644 index a6431d4..0000000 --- a/common/models/framework/base-type.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "BaseType", - "base": "Model", - "idInjection": false, - "mixins": { - "ModelValidations": true, - "ExpressionAstPopulatorMixin": true - } -} \ No newline at end of file diff --git a/common/models/framework/base-user-identity.js b/common/models/framework/base-user-identity.js deleted file mode 100644 index b145715..0000000 --- a/common/models/framework/base-user-identity.js +++ /dev/null @@ -1,104 +0,0 @@ -/** -* -* 2016-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -* Bangalore, India. All Rights Reserved. -* -*/ - -var loopback = require('loopback'); -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('base-user-identity'); - -module.exports = function BaseUserIdentity(BaseUserIdentity) { - BaseUserIdentity.observe('after save', function (ctx, next) { - if (ctx.instance.authScheme === 'ldap' || (ctx.instance.profile.data && !ctx.instance.profile.data.memberOf)) { - var currentRoles; - var groups = [].concat(ctx.instance.profile.data.memberOf); - log.debug(ctx.options, 'running for groups: ', groups); - var principalId = ctx.instance.userId; - - var baseRoleMappingCb = function (err, roleMappings) { - if (err) { - log.error(ctx.options, err); - return next(err); - } - log.debug(ctx.options, 'found roles:', roleMappings); - currentRoles = roleMappings; - var ldapRoleMappingQuery = { - where: { - groupName: { - inq: groups - } - } - }; - var ldapRoleMapping = loopback.getModelByType('LdapRoleMapping'); - ldapRoleMapping.find(ldapRoleMappingQuery, ctx.options, ldapRoleMappingCb); - }; - - var roleMappingQuery = { - where: { - principalId: principalId, - providerRole: 'ldap' - } - }; - var BaseRoleMapping = loopback.getModelByType('BaseRoleMapping'); - BaseRoleMapping.find(roleMappingQuery, ctx.options, baseRoleMappingCb); - - var ldapRoleMappingCb = function ldapRoleMappingCb(err, ldapRoleMappings) { - if (err) { - log.error(ctx.options, err); - return next(err); - } - log.debug(ctx.options, 'found ldapRoleMappings:', ldapRoleMappings); - var roleMappings = createRoleMappings(ldapRoleMappings, principalId); - roleMappings.forEach(function (roleMap, roleMappingsIndex) { - var currentRolesIndex = currentRoles.find(function (currentRoleMap) { return currentRoleMap.roleId === roleMap.roleId;}); - if (currentRolesIndex) { - roleMappings.splice(roleMappingsIndex, 1); - currentRoles.splice(currentRolesIndex, 1); - } - }, this); - BaseRoleMapping.create(roleMappings, ctx.options, (err, results) => { - if (err) { - err.forEach((error) => { - log.error(ctx.options, error); - }); - return next(err); - } - log.debug(ctx.options, 'created new ldap roles for user:', results); - async.each(currentRoles, deleteRole, function (err) { - return next(err); - }); - function deleteRole(roleMapping, cb) { - BaseRoleMapping.destroyById(roleMapping.id, ctx.options, (err, count) => { - if (err) { - log.error(ctx.options, 'error in destroy: ', err); - return cb(err); - } - log.debug(ctx.options, 'destroyed ', count, ' invalid role mappings'); - return cb(); - }); - } - }); - }; - } else { - return next(); - } - }); -}; - -var createRoleMappings = function createRoleMappings(ldapRoleMappings, principalId) { - return ldapRoleMappings.map(function (ldapRoleMap) { - return ldapRoleMap.roles.map(function (role) { - return { - principalType: 'USER', - principalId: principalId, - roleId: role, - providerRole: 'ldap' - }; - }); - }).reduce(function (finalRolesArray, partialRolesArray) { - return finalRolesArray.concat(finalRolesArray, partialRolesArray); - }, []); -}; diff --git a/common/models/framework/base-user-identity.json b/common/models/framework/base-user-identity.json deleted file mode 100644 index d7bfc87..0000000 --- a/common/models/framework/base-user-identity.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "BaseUserIdentity", - "plural": "BaseUserIdentities", - "base": "UserIdentity", - "description": "3rd party provider user details", - "relations": { - "user": { - "type": "belongsTo", - "model": "BaseUser", - "foreignKey": "userId" - } - } -} \ No newline at end of file diff --git a/common/models/framework/base-user.js b/common/models/framework/base-user.js deleted file mode 100644 index 78156fe..0000000 --- a/common/models/framework/base-user.js +++ /dev/null @@ -1,737 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This is the companion js file of the BaseUSer Model. BaseUser model derived from loopback user model - * this has got overriden functions like hasPassword and OTP related functions - * - * Author: Praveen - */ - -var _ = require('lodash'); -var loopback = require('loopback'); -var logger = require('oe-logger'); -// @jsonwebtoken is internal dependency of @oe-jwt-generator -var jwt = require('jsonwebtoken'); -var log = logger('BaseUser'); -var async = require('async'); -var debug = require('debug')('base-user'); -var utils = require('../../../lib/common/util.js'); -var jwtUtil = require('../../../lib/jwt-token-util'); -// 15 mins in seconds -var DEFAULT_RESET_PW_TTL = 15 * 60; - -// var app = require('../../../server/server.js'); - -module.exports = function BaseUser(BaseUser) { - BaseUser.setup = function baseUserSetup() { - // We need to call the base class's setup method - BaseUser.base.setup.call(this); - }; - - // In case App has AppUser still this method will work - // App can also add remote method session on AppUser - BaseUser.remoteMethod('session', { - description: 'Gets session information of the logged in user', - accessType: 'READ', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - } - ], - http: { - verb: 'GET', - path: '/session' - }, - returns: { - type: 'object', - root: true - } - }); - - - BaseUser.validatePassword = function baseUserValidatePassword(plain, options) { - var passwordComplexity = BaseUser.app.get('passwordComplexity'); - var errMsg = 'Invalid password: ' + plain; - if (typeof plain === 'string' && plain) { - var passwdStrength = true; - if (passwordComplexity && passwordComplexity.regex && passwordComplexity.regex.length > 0) { - passwdStrength = new RegExp(passwordComplexity.regex).test(plain); - errMsg = passwordComplexity.errMsg; - } - if (passwdStrength) { - return true; - } - } - var err = new Error(errMsg); - err.statusCode = 422; - err.retriable = false; - throw err; - }; - - /** - * Login a user by with the given `credentials`. - * - * ```js - * User.login({username: 'foo', password: 'bar'}, function (err, token) { -* console.log(token.id); -* }); - * ``` - * - * @param {Object} credentials username/password or email/password - * @param {String[]|String} [include] Optionally set it to "user" to include - * the user info - * @callback {Function} callback Callback function - * @param {Error} err Error object - * @param {AccessToken} token Access token if login is successful - */ - - BaseUser.login = function BaseUserLogin(credentials, include, options, fn) { - var self = this; - // The first if clause is difficult to cover from test cases - // if we dont pass options and include both, options get initialized with {} - // which raises Trace: options is not being passed in datasource-juggler - if (typeof options === 'undefined' && typeof fn === 'undefined') { - if (typeof include === 'function') { - fn = include; - include = null; - options = {}; - } - } else if (typeof fn === 'undefined') { - if (typeof options === 'function') { - fn = options; - options = include; - include = null; - } - } - - fn = fn || utils.createPromiseCallback(); - include = (include || ''); - if (Array.isArray(include)) { - include = include.map(function includeMap(val) { - return val.toLowerCase(); - }); - } else { - include = include.toLowerCase(); - } - - // to support passting tenantId from explorer - // without using mod header. - if (credentials.tenantId && options.ctx) { - options.ctx.tenantId = credentials.tenantId; - } - var realmDelimiter; - // Check if realm is required - var realmRequired = !!(self.settings.realmRequired || - self.settings.realmDelimiter); - if (realmRequired) { - realmDelimiter = self.settings.realmDelimiter; - } - var query = self.normalizeCredentials(credentials, realmRequired, - realmDelimiter); - - if (realmRequired && !query.realm) { - var err1 = new Error('realm is required'); - err1.statusCode = 400; - err1.code = 'REALM_REQUIRED'; - err1.retriable = false; - fn(err1); - return fn.promise; - } - if (!query.email && !query.username) { - var err2 = new Error('username or email is required'); - err2.statusCode = 400; - err2.code = 'USERNAME_EMAIL_REQUIRED'; - err2.retriable = false; - fn(err2); - return fn.promise; - } - - self.findOne({ where: query }, options, function findOneCb(err, user) { - var defaultError = new Error('Incorrect username or password.'); - defaultError.statusCode = 401; - defaultError.code = 'LOGIN_FAILED'; - defaultError.retriable = false; - - function tokenHandler(err, token) { - if (err) { return fn(err); } - if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') { - // NOTE(bajtos) We can't set token.user here: - // 1. token.user already exists, it's a function injected by - // "AccessToken belongsTo User" relation - // 2. ModelBaseClass.toJSON() ignores own properties, thus - // the value won't be included in the HTTP response - // See also loopback#161 and loopback#162 - token.__data.user = user; - } - fn(err, token); - } - - if (err) { - debug('An error is reported from User.findOne: %j', err); - fn(defaultError); - } else if (user) { - if (user.status.toUpperCase() === 'DISABLED') { - err = new Error('Account Locked'); - err.statusCode = 401; - err.code = 'ACCOUNT_LOCKED'; - err.retriable = false; - return fn(err); - } - user.hasPassword(credentials.password, function userHasPassword(err, isMatch) { - if (err) { - debug('An error is reported from User.hasPassword: %j', err); - fn(defaultError); - } else if (isMatch) { - if (self.settings.emailVerificationRequired && !user.emailVerified) { - // Fail to log in if email verification is not done yet - debug('User email has not been verified'); - err = new Error('login failed as the email has not been verified'); - err.statusCode = 401; - err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'; - err.retriable = false; - fn(err); - } else { - if (user.failedTries > 0) { - user.updateAttribute('failedTries', 0, options, function userUpdateAttribute(err) { - if (err) { - log.error(options, '> error updating failedTries to 0', err); - } else { - log.debug(options, '> failedTries updated to 0'); - } - }); - } - user.createAccessToken(credentials.ttl, options, tokenHandler); - } - } else { - debug('The password is invalid for user %s', query.email || query.username); - BaseUser.emit('password incorrect', options, user); - fn(defaultError); - } - }); - } else { - debug('No matching record is found for user %s', query.email || query.username); - fn(defaultError); - } - }); - return fn.promise; - }; - - /** - * Logout a user with the given accessToken id. - * - * ```js - * User.logout('asd0a9f8dsj9s0s3223mk', function (err) { - * console.log(err || 'Logged out'); - * }); - * ``` - * - * @param {String} accessTokenID - * @callback {Function} callback - * @param {Error} err - */ - - BaseUser.logout = function baseUserLogout(tokenId, options, fn) { - // The if clause may not be covered in test cases - // if we manually call BaseUser.logout('accessToken', function(err) { }); - // the test cases are getting crashed with - // Trace: options is not being passed - // at Function.findById loopback-datasource-juggler/lib/dao.js:1787:17 - if (typeof options === 'function' && typeof fn === 'undefined') { - fn = options; - options = {}; - } - fn = fn || utils.createPromiseCallback(); - this.relations.accessTokens.modelTo.findById(tokenId, options, function findById(err, accessToken) { - if (err) { - fn(err); - } else if (accessToken) { - accessToken.destroy(fn); - } else { - var err1 = new Error('could not find accessToken'); - err1.retriable = false; - fn(err1); - } - }); - return fn.promise; - }; - - - /** - * Overriden from loopback to create Access Token - * this is used to store role, tenant, username and similar information in accessToken model which will be readily available later on - * @param {String} ttl time to live - * @returns {function} cb - callback to be called which will gives created record in access token(Same as .create of loopback model) - */ - - BaseUser.prototype.createAccessToken = function createAccessTokenFn(ttl, options, cb) { - if (typeof cb === 'undefined' && typeof options === 'function') { - // createAccessToken(ttl, cb) - cb = options; - options = null; - } - - cb = cb || utils.createPromiseCallback(); - - if (typeof ttl === 'object' && !options) { - // createAccessToken(options, cb) - options = ttl; - ttl = options.ttl; - } - - var userModel = this.constructor; - - var accessToken = {}; - - var RoleMapping = loopback.getModelByType('BaseRoleMapping'); - var Role = loopback.getModelByType('BaseRole'); - - var self = this; - - async.parallel([function roleMappingFind(callback) { - RoleMapping.find({ - where: { - principalId: self.id, - principalType: RoleMapping.USER - } - }, options, function roleMappingFindCb(err, rolemap) { - if (err) { - return err; - } - var roleIdArr = []; - rolemap.forEach(function roleIdExtractFn(role) { - roleIdArr.push(role.roleId); - }); - Role.find({ - where: { - id: { - inq: roleIdArr - } - } - }, options, function roleFindCb(err, roles) { - if (err) { - return cb(err); - } - var rolesArr = roles.map(function rolesArrMapCb(m) { - return m.name; - }); - // User can switch tenantId for a session, - // tenantId stores current tenantId - // userTenantid stores users tenantId - callback(null, (rolesArr && rolesArr.length > 0) ? rolesArr : options.ctx.roles); - }); - }); - }, - function userProfile(callback) { - var UserProfile = loopback.getModelByType('UserProfile'); - UserProfile.findOne({ - where: { - userId: self.id - } - }, options, function dbCallbackFn(err, userProfile) { - if (err) { - callback(err); - } - callback(null, userProfile ? userProfile : {}); - }); - }], - function finalCallBack(err, results) { - if (err) { - cb(err); - } - accessToken.roles = results[0]; - accessToken.department = results[1].department; - if (self._autoScope && self._autoScope.tenantId) { - accessToken.tenantId = self._autoScope.tenantId; - accessToken.userTenantId = self._autoScope.tenantId; - } else { - log.debug(options, 'base user autoscope or tenant is not present', self.username); - } - accessToken.username = self.username; - accessToken.ttl = userModel.app.get('accessTokenTTL') || Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL); - options = options || {}; - self.createAuthToken(accessToken, options, cb); - }); - - return cb.promise; - }; - - /** - * Creating Authentication Token based on the Auth type.. - * @param {object} accessToken - access token - * @param {object} options - options - * @returns {function} cb - callback to be called which will gives authentication token. - */ - - BaseUser.prototype.createAuthToken = function createAuthToken(accessToken, options, cb) { - if (typeof cb === 'undefined' && typeof options === 'function') { - // createAccessToken(ttl, cb) - cb = options; - options = null; - } - - cb = cb || utils.createPromiseCallback(); - var self = this; - - let jwtForAccessToken = process.env.JWT_FOR_ACCESS_TOKEN ? (process.env.JWT_FOR_ACCESS_TOKEN.toString() === 'true') : false; - if (jwtForAccessToken) { - var jwtConfig = jwtUtil.getJWTConfig(); - var jwtOpts = {}; - jwtOpts.issuer = jwtConfig.issuer; - jwtOpts.audience = jwtConfig.audience; - jwtOpts.expiresIn = accessToken.ttl; - var secretOrPrivateKey = jwtConfig.secretOrKey; - // Adding userId information, which is used - // in adding Principal of type USER to AccessContext - accessToken.userId = self.id; - jwt.sign(accessToken, secretOrPrivateKey, jwtOpts, function jwtSignCb(err, token) { - if (err) { - log.error(options, 'JWT signing error ', err); - log.debug(options, 'Got JWT signing error, Defaulting to accessToken generation.'); - return self.accessTokens.create(accessToken, options, cb); - } - var jwtToken = _.cloneDeep(accessToken); - jwtToken.id = token; - jwtToken.ttl = jwtOpts.expiresIn; - var loopback = BaseUser.app.loopback; - var AuthSession = loopback.getModelByType('AuthSession'); - cb(null, new AuthSession(jwtToken)); - }); - } else { - self.accessTokens.create(accessToken, options, cb); - } - - return cb.promise; - }; - - /** - * listening 'password incorrect' event. This is used to update loginTries property if entered incorrect password. - * @param {object} options - context object - * @returns {Object} user - BaseUser Instance - */ - - BaseUser.on('password incorrect', function baseUserPasswordIncorrectListner(options, user) { - var maxFailedLoginTries = BaseUser.app.get('maxFailedLoginTries'); - var updatedData = {}; - updatedData.failedTries = user.failedTries + 1; - if (user.failedTries === maxFailedLoginTries - 1) { - updatedData.status = 'disabled'; - setTimeout(BaseUser.unlock, BaseUser.app.get('UNLOCK_USER_ACCOUNT_TIME'), user.email || user.username, options, function (err, msg) { - if (err) { - log.error(options, err); - } else { - log.info(options, msg); - } - }); - } - user.updateAttributes(updatedData, options, function userUpdateFailedTries(err) { - if (err) { - log.error(options, '> error updating failed retries', err); - } else { - log.debug(options, '> number of failes retries updated'); - } - }); - }); - - - // Unlock api, ACL rules are defined in BaseUser to only allow admin ROLE to unlock user account. - BaseUser.unlock = function baseUserUnlock(data, options, cb) { - var query = { where: { or: [{ username: data }, { email: data }] } }; - BaseUser.findOne(query, options, function baseUserFindOne(err, user) { - if (err) { - return cb(err); - } else if (user) { - user.updateAttributes({ - 'failedTries': 0, - 'status': 'enabled' - }, options, function userUpdateAttributes(err, user) { - if (err) { - return cb(err); - } - cb(null, { 'status': 'account unlocked for username ' + user.username }); - }); - } else { - var userNotFoundErr = new Error('username or email not found'); - userNotFoundErr.retriable = false; - return cb(userNotFoundErr); - } - }); - }; - - BaseUser.remoteMethod('unlock', { - description: 'To unlock user account', - accessType: 'WRITE', - accepts: { - arg: 'username', - type: 'string', - description: 'username or email' - }, - http: { - verb: 'POST', - path: '/unlock' - }, - returns: { - type: 'object', - root: true - } - }); - - var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - // ignore: - // line - BaseUser.validatesFormatOf('email', { - with: re, - message: 'Must provide a valid email' - }); - - // TODO - // put the validation properyl in before save or part of validations - - BaseUser.on('resetPasswordRequest', function BaseUserResetPasswdCb(info) { - var url = 'http://' + BaseUser.app.get('host') + ':' + BaseUser.app.get('port') + '/resetPassword'; - var html = 'Click here to reset your password'; - - BaseUser.app.models.Email.send({ - to: info.email, - from: info.email, - subject: 'Password reset', - html: html - }, function BaseUserResetPasswdEmailCb(err) { - if (err) { - return log.error(log.defaultContext(), '> error sending password reset email', err); - } - log.debug(log.defaultContext(), '> sending password reset email to:', info.email); - }); - }); - - /** - * Create a short lived acess token for temporary login. Allows users - * to change passwords if forgotten. - * - * @param {Object} options - user detail options - * @param {object} opts - call context options - * @param {function}cb - callback - * @returns {function}cb - callback - * @prop {String} email The user's email address - * @callback {Function} callback - */ - BaseUser.resetPassword = function resetPassword(options, opts, cb) { - cb = cb || utils.createPromiseCallback(); - var UserModel = this; - var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; - - options = options || {}; - if (typeof options.email !== 'string') { - var err = new Error('Email is required'); - err.statusCode = 400; - err.code = 'EMAIL_REQUIRED'; - err.retriable = false; - cb(err); - return cb.promise; - } - - UserModel.findOne({ where: { email: options.email } }, opts, function userModelFindOne(err, user) { - if (err) { - return cb(err); - } - if (!user) { - err = new Error('Email not found'); - err.statusCode = 404; - err.code = 'EMAIL_NOT_FOUND'; - err.retriable = false; - // removed err parameter from return cb(err) for generic response. - return cb(); - } - // create a short lived access token for temp login to change password - // TODO(ritch) - eventually this should only allow password change - user.accessTokens.create({ ttl: ttl }, opts, function accesstOkenCreate(err, accessToken) { - if (err) { - return cb(err); - } - cb(); - BaseUser.emit('resetPasswordRequest', { - email: options.email, - accessToken: accessToken, - user: user - }); - }); - }); - - return cb.promise; - }; - /** - * This method is used to switch tenant by admin user to create first user for tenant or to do something in that tenant - * @param {objct} ctx - context object which has data - * @param {string} tenantId - tenantId - * @param {object} options - options - * @param {function} cb -continuation callback function handle - */ - BaseUser.switchTenant = function SwitchTenantFn(ctx, tenantId, options, cb) { - // The if clause may not be covered in test cases - // if we manually call BaseUser.switchTenant(ctx, switchTenantId, function(err, res) { }); - // the test cases are getting crashed with - // Trace: options is not being passed - // at Function.findById loopback-datasource-juggler/lib/dao.js:1787:17 - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - var data = { tenantId: '' }; - var AuthSession = loopback.getModelByType('AuthSession'); - var accessToken = ctx.req.accessToken; - - if (accessToken) { - AuthSession.findById(accessToken.id, options, function authSessionFindById(err, token) { - if (err) { - return cb(err); - } - if (token) { - token.tenantId = tenantId; - AuthSession.upsert(token, options, function authSessionUpsert(err, updatedToken) { - if (err) { - cb(err); - } - data.tenantId = updatedToken.tenantId; - cb(null, data); - }); - } else { - cb(null, data); - } - }); - } else { - // This else case cannot covered from test cases - // To cover this ctx.req.accessToken to be not set(null or undefined), for that we - // should not pass access_token from query params and since /switch-tenant api is enabled - // for logged in users, it is returning authorization required, the control flow - // not even coming to this method itself. - var err = new Error('not logged in'); - err.retriable = false; - cb(err, data); - } - }; - - BaseUser.session = function session(ctx, options, cb) { - var data = {}; - // Cannot cover below code with coverage without passing options - // since the next below if clause accesses options.ctx object which is undefined. - // Test cases crash with TypeError: Cannot read property 'userId' of undefined - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - if (ctx.req.accessToken) { - data.username = ctx.req.accessToken.username; - data.userid = options.ctx.userId; - data.tenantId = options.ctx.tenantId; - data.roles = options.ctx.roles; - } else { - data.username = ''; - data.userid = ''; - data.error = 'Not logged in'; - data.roles = []; - data.tenantId = options.ctx.tenantId; - } - - cb(null, data); - }; - - - BaseUser.remoteMethod('switchTenant', { - description: 'To switch the tenant for the loggedin session', - accessType: 'WRITE', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - }, { - arg: 'tenantId', - type: 'string', - description: 'TenantId' - }], - http: { - verb: 'POST', - path: '/switch-tenant' - }, - returns: { - type: 'object', - root: true - } - }); -}; - -/* function registerObserver(BaseUser) { - BaseUser.observe('after save', function BaseUserAfterSaveCb(ctx, next) { - - // currentcontext because on update instance will be null and - // currentInstance will have the Instance data - // we cant relay on ctx.data because it may not contain id. - // Only when upsert is use for update ctx.data will have id. - - // in case of updateAll the currentInstance will also be not available. - // and since we have disabled updateAll remote method this will never - // happen but developer should not use updateAll programmatically. - if (config && !config.disableWorkflow) { - log.info('register observer to support sync with workflow.'); - var data = ctx.instance || ctx.currentInstance || ctx.data; - BaseUser.emit('SyncUserWithWorkflow', data.id); - } - next(); - }); -} - -function registerSyncUserEvent(BaseUser) { - BaseUser.on('SyncUserWithWorkflow', function BaseUserSyncUserCb(userId) { - - // Get the user details. - BaseUser.findById(userId, function BaseUserSyncUserFindCb(err, userDetails) { - if (err) { - // Error occurred. We must retry. - // createTaskForReconsiliation(); - log.eror('error : unable to find record with Id : ' + userId); - } else if (userDetails) { - // Push to workflow engine. - - var WorkflowUserModel = loopback.getModel('WorkflowUser'); - if (WorkflowUserModel) { - var firstName = userDetails.firstName || userDetails.username; - var lastName = userDetails.lastName || userDetails.username; - var workflowUser = { - 'id': userDetails.id, - 'firstName': firstName, - 'lastName': lastName, - 'email': userDetails.email, - 'passowrd': 'default-password' - }; - - WorkflowUserModel.create(workflowUser, function BaseUserSyncUserWFCreateCb(err, workflowUserDetails) { - if (err) { - // Error occured. We must retry. - log.error('Failed to create workFlowUser [Error : ]', JSON.stringify(err), null, 4); - } else { - // Do nothing. - log.debug('successfully create workFlowUser : userDetails : ', workflowUserDetails); - } - }); - } else { - log.info('workFlowUser model not found.'); - } - - } else { - log.info('No user found with', userId); - } - }); - }); -}*/ diff --git a/common/models/framework/base-user.json b/common/models/framework/base-user.json deleted file mode 100644 index dc7815d..0000000 --- a/common/models/framework/base-user.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "name": "BaseUser", - "plural": "BaseUsers", - "base": "User", - "description": "User login details", - "autoscope": [ - "tenantId" - ], - "options": { - "disableManualPersonalization":true - }, - "mixins": { - "ObserverMixin": true, - "HistoryMixin": true, - "CacheMixin": true, - "ModelValidations": false, - "ExpressionAstPopulatorMixin": true, - "AuditFieldsMixin": true, - "DataPersonalizationMixin": true, - "ServicePersonalizationMixin": false, - "SwitchDatasourceMixin": true, - "VersionMixin": false, - "WorkflowMixin": true, - "BusinessRuleMixin": true, - "SoftDeleteMixin": true - }, - "properties": { - "failedTries": { - "type": "number", - "default": 0 - }, - "status": { - "type": "string", - "default": "enabled" - } - }, - "validations": [], - "relations": { - "accessTokens": { - "type": "hasMany", - "model": "AuthSession", - "foreignKey": "userId" - }, - "identities": { - "type": "hasMany", - "model": "BaseUserIdentity", - "foreignKey": "userId" - }, - "credentials": { - "type": "hasMany", - "model": "userCredential", - "foreignKey": "userId" - } - }, - "acls": [ - { - "principalType": "ROLE", - "principalId": "admin", - "permission": "ALLOW", - "property": "findById", - "accessType": "READ" - }, - { - "principalType": "ROLE", - "principalId": "admin", - "permission": "ALLOW", - "property": "find", - "accessType": "READ" - }, - { - "principalType": "ROLE", - "principalId": "$evadmin", - "permission": "ALLOW", - "property": "findById", - "accessType": "READ" - }, - { - "principalType": "ROLE", - "principalId": "$evadmin", - "permission": "ALLOW", - "property": "find", - "accessType": "READ" - }, - { - "principalType": "ROLE", - "principalId": "admin", - "permission": "ALLOW", - "property": "switchTenant", - "accessType": "*" - }, - { - "principalType": "ROLE", - "principalId": "$evadmin", - "permission": "ALLOW", - "property": "switchTenant", - "accessType": "*" - }, - { - "principalType": "ROLE", - "principalId": "admin", - "permission": "ALLOW", - "property": "unlock", - "accessType": "*" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "session", - "accessType": "*" - } - ], - "methods": {}, - "hidden": [ - "failedTries", - "status" - ] -} \ No newline at end of file diff --git a/common/models/framework/batch-run.json b/common/models/framework/batch-run.json deleted file mode 100644 index b74702c..0000000 --- a/common/models/framework/batch-run.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "BatchRun", - "plural": "BatchRuns", - "base": "BaseEntity", - "strict": true, - "idInjection": true, - "properties": { - "startTimeMillis": "number", - "endTimeMillis": "number", - "durationMillis": "number", - "totalRecordCount": "number", - "successCount": "number", - "failureCount": "number", - "startTime": "date", - "endTime": "date", - "filePath": "string", - "options": "object", - "error": "object" - }, - "validations": [], - "relations": { - }, - "acls": [], - "methods": {} - } - \ No newline at end of file diff --git a/common/models/framework/batch-status.json b/common/models/framework/batch-status.json deleted file mode 100644 index c9ba44f..0000000 --- a/common/models/framework/batch-status.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "BatchStatus", - "plural": "BatchStatus", - "base": "BaseEntity", - "strict": true, - "idInjection": true, - "properties": { - "requestOpts": "object", - "response": "object", - "fileRecordData": "object", - "payload": "object", - "error": "object", - "statusText": "string", - "statusCode": "number" - }, - "validations": [], - "relations": { - }, - "acls": [], - "methods": {} - } - \ No newline at end of file diff --git a/common/models/framework/business-rule.js b/common/models/framework/business-rule.js deleted file mode 100644 index e6adc29..0000000 --- a/common/models/framework/business-rule.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * - * @classdesc This Model processes all the business rules(grammar expressions), create their ASTs and attach it to BusinessRule model. - * @kind class - * @class BusinessRule - * @author Sambit Kumar Patra - */ - -var exprLang = require('../../../lib/expression-language/expression-language.js'); -var logger = require('oe-logger'); -var log = logger('model-validations'); - -module.exports = function businessRule(BusinessRule) { - /** - * This 'after save' hook is used to intercept data sucessfully - * POSTed to BusinessRule model, create ASTs of all the - * expressions POSTed and attach it to "_ast" of BusinessRule Model - * @param {object} ctx - context object - * @param {function} next - next middleware function - * @function businessRuleBeforeSaveCb - */ - - BusinessRule.observe('after save', function businessRuleBeforeSaveCb(ctx, next) { - var data = ctx.instance || ctx.currentInstance || ctx.data; - log.debug(ctx.options, 'BusinessRule before save remote attaching expression to _ast'); - BusinessRule._ast[data.expression] = exprLang.createAST(data.expression); - log.debug(ctx.options, 'expression : ', data.expression, 'attached to _ast of BusinessRule model'); - next(); - }); -}; diff --git a/common/models/framework/business-rule.json b/common/models/framework/business-rule.json deleted file mode 100644 index 1d6a876..0000000 --- a/common/models/framework/business-rule.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "BusinessRule", - "base": "BaseEntity", - "description": "This Model processes all the business rules(grammar expressions), create their ASTs and attach it to BusinessRule model", - "options": { - "validateUpsert": true, - "isFrameworkModel": true - }, - "properties": { - "modelName": { - "type": "string", - "required": true, - "max": 30 - }, - "expression": { - "type": "string", - "required": true, - "max": 250 - }, - "verb": { - "type": [ - "string" - ], - "required": true - }, - "category": { - "type": "string", - "max": 30 - }, - "code": { - "type": "string", - "max": 30 - }, - "description": { - "type": "string", - "max": 500 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/framework/cache-manager.js b/common/models/framework/cache-manager.js deleted file mode 100644 index 05c7ddd..0000000 --- a/common/models/framework/cache-manager.js +++ /dev/null @@ -1,390 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * @classdesc This model is used to define some remote methods for the - * management of Cache. These remote methods include the following - - *
    - *  1.  getcacheables       - Gets a map of all cacheable models.
    - *  2.  evictallcache       - Evict cache for all Models
    - *  3.  getcachestatistics  - Returns cache statistics
    - *  4.  iscacheable         - Returns whether the specified model is marked as cacheable or not
    - *  5.  evictcacheformodels - Evicts/deletes the cache keys of the specified models
    - * 
    - * - * @kind class - * @class CacheManager - * @author Ajith Vasudevan - */ -var logger = require('oe-logger'); -var log = logger('cache-manager'); - -module.exports = function CacheManagerFn(CacheManager) { - /** - * Function to get a map of all cacheable models. - * Value 'true' indicates that the model is marked as cacheable - * - * @memberof CacheManager - * @name getcacheables - * @param {object}options - options - * @param {function} cb - callback - */ - CacheManager.getcacheables = function cacheManagerGetCacheablesFn(options, cb) { - log.debug(options, 'CACHE-MANAGER', 'getcacheables', 'evcacheables', global.evcacheables); - cb(null, global.evcacheables); - }; - // Remote method for above function - CacheManager.remoteMethod( - 'getcacheables', { - description: 'Get a map of all cacheable models. Value \'true\' indicates that the model is marked as cacheable', - http: { - path: '/getcacheables', - verb: 'get' - }, - returns: { - arg: 'cacheables', - type: 'object', - root: true - } - } - ); - - /** - * Function to evict cache for all Models - * - * @memberof CacheManager - * @name evictallcache - * @param {object}options - options - * @param {function} cb - callback - */ - CacheManager.evictallcache = function cacheManagerEvictAllCacheFn(options, cb) { - log.debug(options, 'CACHE-MANAGER', 'evictallcache'); - global.evcache = null; - delete global.evcache; - cb(null, { - 'result': 'Cache Evicted for all Models' - }); - }; - - CacheManager.remoteMethod( - 'evictallcache', { - description: 'Evict cache for all Models', - http: { - path: '/evictallcache', - verb: 'get' - }, - returns: { - arg: 'result', - type: 'object', - root: true - } - } - ); - - - /** - * Function for returning cache statistics (currently number - * of cache keys and size in bytes for each cached model) - * - * @memberof CacheManager - * @name getcachestatistics - * @param {object}options - options - * @param {function} cb - callback - */ - CacheManager.getcachestatistics = function cacheManagerGetCacheStatisticsFn(options, cb) { - log.debug(options, 'CACHE-MANAGER', 'getcachestatistics'); - var result = { - totalkeycount: 0, - totalcachesizeinbytes: 0, - statspermodel: JSON.parse(JSON.stringify(global.evcacheables)) - }; - var key; - for (key in result.statspermodel) { - if (result.statspermodel.hasOwnProperty(key)) { - result.statspermodel[key] = { - keycount: 0, - sizeinbytes: 0 - }; - } - } - if (global.evcache) { - for (key in global.evcache) { - if (global.evcache.hasOwnProperty(key)) { - var model = key.substring(0, key.indexOf('_')); - if (global.evcache.hasOwnProperty(key)) { - var sizeofobj = sizeOfObjectInBytes(global.evcache[key]); - result.statspermodel[model].keycount = result.statspermodel[model].keycount + 1; - result.statspermodel[model].sizeinbytes = result.statspermodel[model].sizeinbytes + sizeofobj; - result.totalkeycount = result.totalkeycount + 1; - result.totalcachesizeinbytes = result.totalcachesizeinbytes + sizeofobj; - } - } - } - } - // log.debug("CACHE-MANAGER", "evcache", global.evcache); - cb(null, result); - }; - - // Remote method for above function - CacheManager.remoteMethod( - 'getcachestatistics', { - description: 'Returns cache statistics (currently number of cache keys and size in bytes for each cached model)', - http: { - path: '/getcachestatistics', - verb: 'get' - }, - returns: { - arg: 'result', - type: 'object', - root: true - } - } - ); - - - /** - * Function to returns whether the specified model - * is marked as cacheable or not - * - * @memberof CacheManager - * @name iscacheable - * @param {object}model - model constructor - * @param {object}options - options - * @param {function} cb - callback - */ - CacheManager.iscacheable = function cacheManagerIsCacheableFn(model, options, cb) { - log.debug(options, 'CACHE-MANAGER', 'iscacheable', model); - - var cacheable = false; - if (global.evcacheables && global.evcacheables[model] === true) { - cacheable = true; - } - - cb(null, { - 'model': model, - 'cacheable': cacheable - }); - }; - - // Remote method for above function - CacheManager.remoteMethod( - 'iscacheable', { - description: 'Returns whether the specified model (path parameter) is marked as cacheable or not', - http: { - path: '/iscacheable/:model', - verb: 'get' - }, - accepts: { - arg: 'model', - type: 'string', - http: { - source: 'path' - } - }, - returns: { - arg: 'cacheable', - type: 'object', - root: true - } - } - ); - - - /** - * Function to evicts/deletes the cache keys of the specified models. - * Input is a string array. Output is a map of model vs number - * of keys deleted. - * - * @memberof CacheManager - * @name evictcacheformodels - * @param {object}models - model constructor - * @param {object}options - options - * @param {function} cb - callback - * @returns {function}cb - callback - */ - CacheManager.evictcacheformodels = function cacheManagerEvictCacheForModelsFn(models, options, cb) { - log.debug(options, 'CACHE-MANAGER', 'evictcacheformodels', models); - - if (!models || models === '' || !models.length) { - log.debug(options, 'CACHE-MANAGER', 'evictcacheformodels', 'ERROR: models is not a string array'); - var err = new Error('models should be a string array'); - err.retriable = false; - return cb(err, null); - } - - var keycount = 0; - var response = {}; - function cacheManagerEvictCacheForModelsForEachFn(model) { - if (!response[model]) { - response[model] = 0; - } - if (key.indexOf(model + '_') === 0) { - global.evcache[key] = null; - delete global.evcache[key]; - log.debug(options, 'CACHE-MANAGER', 'deleted key', key); - response[model] = response[model] + 1; - } - } - if (global.evcache) { - for (var key in global.evcache) { - if (global.evcache.hasOwnProperty(key)) { - models.forEach(cacheManagerEvictCacheForModelsForEachFn); - } - } - log.debug(options, 'CACHE-MANAGER', 'response', response); - } - log.debug(options, 'CACHE-MANAGER', 'Number of evicted keys:', keycount); - cb(null, response); - }; - - // Remote method for above function - CacheManager.remoteMethod( - 'evictcacheformodels', { - description: 'Evicts/Deletes the cache keys of the specified models. Input is a string array. Output is a map of model vs number of keys deleted.', - http: { - path: '/evictcacheformodels', - verb: 'post' - }, - accepts: { - arg: 'models', - type: 'object', - http: { - source: 'body' - } - }, - returns: { - arg: 'evictedkeycount', - type: 'object', - root: true - } - } - ); - - // Disable all remote methods of CacheManager like find, findById, - // create, etc., except the methods defined in this file - disableAllMethodsBut(CacheManager, ['getcachestatistics', 'evictallcache', 'evictcacheformodels', 'iscacheable', 'getcacheables']); -}; - - -/** - * Function to disable all remote methods of specified model - * except the supplied array of methods - * - * @memberof CacheManager - * @name disableAllMethodsBut - * @param {object}model - model constructor - * @param {array}methodsToExpose - remote methods - */ -function disableAllMethodsBut(model, methodsToExpose) { - if (model && model.sharedClass) { - methodsToExpose = methodsToExpose || []; - - // var modelName = model.sharedClass.name; - var methods = model.sharedClass.methods(); - var relationMethods = []; - var hiddenMethods = []; - - try { - Object.keys(model.definition.settings.relations).forEach(function disableAllMethodsButRelationForEachFn(relation) { - relationMethods.push({ - name: '__findById__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__destroyById__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__exists__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__link__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__get__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__create__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__update__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__destroy__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__unlink__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__count__' + relation, - isStatic: false - }); - relationMethods.push({ - name: '__delete__' + relation, - isStatic: false - }); - relationMethods.push({ - name: 'bulkupload' + relation, - isStatic: true - }); - relationMethods.push({ - name: 'history' + relation, - isStatic: true - }); - }); - } catch (err) { - // Handle error - } - - methods.concat(relationMethods).forEach(function disableAllMethodsButConcatRelationMethodsFn(method) { - var methodName = method.name; - if (methodsToExpose.indexOf(methodName) < 0) { - hiddenMethods.push(methodName); - model.disableRemoteMethod(methodName, method.isStatic); - } - }); - } -} - - -/** - * Function to return size in bytes of the specified Object - * - * @memberof CacheManager - * @name sizeOfObjectInBytes - * @param {object}object - input object - * @returns {number} - number of bytes - */ -function sizeOfObjectInBytes(object) { - var objectList = []; - var stack = [object]; - var bytes = 0; - while (stack.length) { - var value = stack.pop(); - if (typeof value === 'boolean') { - bytes += 4; - } else if (typeof value === 'string') { - bytes += value.length * 2; - } else if (typeof value === 'number') { - bytes += 8; - } else if (typeof value === 'object' && objectList.indexOf(value) === -1) { - objectList.push(value); - for (var i in value) { - if (value.hasOwnProperty(i)) { - stack.push(value[i]); - } - } - } - } - return bytes; -} diff --git a/common/models/framework/cache-manager.json b/common/models/framework/cache-manager.json deleted file mode 100644 index c1d6005..0000000 --- a/common/models/framework/cache-manager.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "CacheManager", - "base": "BaseEntity", - "description": "Adds cache related remote utility API", - "idInjection": true, - "plural": "CacheManager", - "options": { - "validateUpsert": true, - "isFrameworkModel": true - }, - "properties": {}, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/change-request.js b/common/models/framework/change-request.js deleted file mode 100644 index 75bf88c..0000000 --- a/common/models/framework/change-request.js +++ /dev/null @@ -1,266 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * Problem Statement: When ever there is any update, and it requires - * approval of another user before updating the instance in Database.

    - * - * Solution: This models provides an interface to create a change - * request for any model instance update, and exposes /publish, /rejected and - * /cancel api to user to perform action on change request, in case user publish - * the record, it internally updates the actual model instance for which change - * request was created otherwise it doesnt update the entity. - * - * @class ChangeRequest - * @author Sivankar jain - */ -// function has too many parameters.' -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('change-request'); -var uuidv4 = require('uuid/v4'); - -// const SUBMITTED = 'submitted'; -// const PENDING_APPROVAL = 'pending_approval'; -const - PUBLISHED = 'published'; -const - REJECTED = 'rejected'; -const - CANCELLED = 'cancelled'; - -module.exports = function ChangeRequestFn(ChangeRequest) { - addMethods(ChangeRequest); - exposeAsRemoteMethods(ChangeRequest); -}; - -/** - * Expose /publish, /rejected and /cancel end points to Change Request model. - * - * @param {ModelCont} - * Model Change request model, to add remote methods to it. - * @method exposeAsRemoteMethods - * @memberof ChangeRequest - */ -function exposeAsRemoteMethods(Model) { - // Method to publish an entity. - Model.remoteMethod('publish', { - description: 'To change the status of entity as "published" ', - accessType: 'WRITE', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - }, { - arg: 'id', - type: 'string', - description: 'change request id' - }], - http: { - verb: 'POST', - path: '/:id/publish' - }, - returns: { - type: Model, - root: true - } - }); - - // Method to reject an entity. - Model.remoteMethod('reject', { - description: 'To change the status of entity as "rejected" ', - accessType: 'WRITE', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - }, { - arg: 'id', - type: 'string', - description: 'change request id' - }], - http: { - verb: 'POST', - path: '/:id/reject' - }, - returns: { - type: Model, - root: true - } - }); - - // Method to cancel an entity. - Model.remoteMethod('cancel', { - description: 'To change the status of entity as "cancelled" ', - accessType: 'WRITE', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - }, { - arg: 'id', - type: 'string', - description: 'change request id' - }], - http: { - verb: 'POST', - path: '/:id/cancel' - }, - returns: { - type: Model, - root: true - } - }); -} - -/** - * Adds /publish, /rejected and /cancel remote methods to Change Request model. - * - * @param {ModelCont} - * Model Change request model, to add remote methods to it. - * @memberof ChangeRequest - */ -function addMethods(Model) { - // Status change end points if not already custom registered in respective - // models - - Model.publish = function exposeAsRemoteMethodsPublishFn(ctx, id, options, cb) { - log.debug(options, 'Base Entity\'s publish end point called for model [', this.modelName, ']'); - proceedUpdate(ctx, this.modelName, id, PUBLISHED, options, cb); - }; - - Model.reject = function exposeAsRemoteMethodsRejectFn(ctx, id, options, cb) { - log.debug(options, 'Base Entity\'s reject end point called for model [', this.modelName, ']'); - proceedUpdate(ctx, this.modelName, id, REJECTED, options, cb); - }; - - Model.cancel = function exposeAsRemoteMethodsCancelFn(ctx, id, options, cb) { - log.debug(options, 'Base Entity\'s cancel end point called for model [', this.modelName, ']'); - proceedUpdate(ctx, this.modelName, id, CANCELLED, options, cb); - }; - - /** - * This method updates the entity in data base, in case status is published - * it updates the actual model Instance for which change request was - * created. Otherwise it doesnt update the actual model entity.
    - * Finally it updates change request instance with appropriate status - * published, rejected or cancel. - * @memberof ChangeRequest - * @param {object} ctx - dao context - * @param {string} modelName - modelname - * @param {id} id - id of an instance - * @param {string} status - status of an instance - * @param {object} options - callcontext options - * @param {function} cb -callback - */ - function proceedUpdate(ctx, modelName, id, status, options, cb) { - // this check is necessary when we try from swagger usually options is - // defined as cb and cb is undefined. - // and programmatically also if user doesn’t send options then the - // options - // will be function and cb will be undefined. - - if (typeof cb === 'undefined') { - if (typeof options === 'function') { - // create(data, cb); - cb = options; - options = {}; - } - } - - log.debug(options, 'in change-request persistUpdates() called with id[', id, '] and status [', status, ']'); - var CrModel = loopback.getModel('ChangeRequest'); - log.debug(options, 'Change Request with id [', id, ']'); - - CrModel.findById(id, options, function changeRequestModelFindCb(err, crResult) { - if (err) { - cb(err, null); - } - if (crResult === null) { - log.error(options, 'No Change Request found with id [', id, ']'); - return cb('No Change Request found with id [' + id + ']', null); - } - log.debug(options, 'CR instance found with status [', crResult._status, ']'); - - var crInstance = crResult; - if (status === PUBLISHED) { - log.debug(options, 'Published the changes to the original entity'); - - var originalEntityId = crInstance.originalEntityId; - var originalEntityType = crInstance.originalEntityType; - var updatedEntity = crInstance.changedEntity; - // Id needs to be populated as it may not be part of - // payload. (updateAttributes) - updatedEntity.id = originalEntityId; - - // Find model by id and update the requested - log.debug(options, 'Change request updating for model [', originalEntityType, ']'); - - var Model = loopback.getModel(originalEntityType); - if (typeof Model === 'undefined') { - var err1 = new Error(originalEntityType + ' Model not found'); - err1.retriable = false; - return cb(err1, null); - } - - updatedEntity._version = updatedEntity._oldVersion; - - // we need to set updatedByWorkflow because this update is done by workflow and - // not EndUser, so if in case there is any workflow settings for update it - // should not be triggered - - options.updatedByWorkflow = true; - - Model.upsert(updatedEntity, options, function changeRequestOriginalEntityFindUpdateCb(err, mInstance) { - if (err) { - log.error(options, 'Error occured while updating the changedModel model [', originalEntityType, ']', err); - cb(err, null); - } else if (mInstance) { - log.debug(options, 'CR model for Entity[', originalEntityType, '] is updated succesfully'); - - // Update the status of change-request - // with the status send - - crInstance._status = status; - crInstance._newVersion = uuidv4(); - CrModel.upsert(crInstance, options, function changeRequestOriginalEntityFindUpdateUpsertCb(err, crInstance) { - if (err) { - log.error(options, 'Error occured while updating the CR model'); - cb(err, null); - } else { - log.debug(options, 'CR instance updated with status [', crInstance._status, ']'); - cb(null, crInstance); - } - }); - } - }); - } else { - log.debug(options, 'No Action required for status [', status, ']'); - crInstance._status = status; - crInstance._newVersion = uuidv4(); - options.updatedByWorkflow = true; - CrModel.upsert(crInstance, options, function changeRequestUpsertCb(err, crInstance) { - if (err) { - log.error(options, 'Error occured while updating the CR model'); - cb(err, null); - } else { - log.debug(options, 'CR instance updated with status [', crInstance._status, ']'); - cb(null, crInstance); - } - }); - } - }); - } -} diff --git a/common/models/framework/change-request.json b/common/models/framework/change-request.json deleted file mode 100644 index 7dc1798..0000000 --- a/common/models/framework/change-request.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "ChangeRequest", - "plural": "changeRequests", - "base": "BaseEntity", - "description": "used for approval workflow", - "idInjection": true, - "options": { - "validateUpsert": true, - "evEnableWorkflow": { - "create": "xyz" - }, - "isFrameworkModel": true - }, - "properties": { - "originalEntityId": { - "type": "string", - "required": true, - "index": true - }, - "originalEntityType": { - "type": "string", - "required": true, - "max": 200 - }, - "changedEntity": { - "type": "object", - "required": true, - "index": true - } - }, - "oeValidations": [], - "validations": [], - "relations": {}, - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/data-ACL.js b/common/models/framework/data-ACL.js deleted file mode 100644 index 0cc4f4f..0000000 --- a/common/models/framework/data-ACL.js +++ /dev/null @@ -1,285 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * - * @classdesc DataACL provides the filter condition on data for a given model which a principal (user or role) can access. - * It allows seprate filters for a given access type (READ/WRITE) - * DataACL works in combinatioin with ACLACL controls whether user can access the model/api or not - * DataACL controls what data user can access for that api - * If DataACL is not defined i.e. user can access all the data - * provided ACL has allowed it. - * A single DataACL can have a complex filter consisting and or of multiple conditions - * The format of filter is what loopback uses for filter - * In case you post multiple DataACLs for same model and same principal - * These are clubbed group wise (Filter in each group will be or condition) - * All filters then will be joined across group using and operator - * group is optional, in that case each filter goes to a default group - * Additionally DataACL allows using context variables in values used in filter condition, by using @ctx. syntax - * @kind class - * @class DataACL - * @author Praveen Kumar Gulati - */ - -var logger = require('oe-logger'); -var log = logger('data-acl'); -var loopback = require('loopback'); -var applyFilter = require('loopback-filters'); -var mergeQuery = require('loopback-datasource-juggler/lib/utils').mergeQuery; -var async = require('async'); -var loopbackAccessContext = require('loopback/lib/access-context'); -var AccessContext = loopbackAccessContext.AccessContext; -var errorUtil = require('../../../lib/common/error-utils').getValidationError; -var RoleMapping = loopback.RoleMapping; - -// Gets specified `value` on `target` going levels down if required. -function getValue(target, field) { - var retVal; - if (field) { - var fields = field.split('.'); - var leaf = fields.pop(); - - var currentTarget = target; - fields.forEach(function _forEachCb(field) { - currentTarget = currentTarget ? currentTarget[field] : null; - }); - retVal = currentTarget ? currentTarget[leaf] : null; - } - return retVal; -} - -function buildFilter(filter, ctx) { - Object.keys(filter).map(function filterForEachKey(item) { - var value = filter[item]; - if (typeof value === 'string') { - if (value.startsWith('@ctx.')) { - filter[item] = getValue(ctx, value.substr(5)); - if (!filter[item]) { - var err = new Error('Context not present'); - err.retriable = false; - throw err; - } - } else if (value.startsWith('@CC.')) { - filter[item] = getValue(ctx, value.substr(4)); - if (!filter[item]) { - var err1 = new Error('Context not present'); - err1.retriable = false; - throw err1; - } - } - } else if (typeof value === 'object') { - filter[item] = buildFilter(value, ctx); - } - }); - - return filter; -} - -// {"where":{"key":{"like":"N"}}} - -module.exports = function DataACLFn(DataACL) { - // Define constants - DataACL.READ = AccessContext.READ; - DataACL.WRITE = AccessContext.WRITE; - DataACL.EXECUTE = AccessContext.EXECUTE; - DataACL.USER = 'USER'; - DataACL.ROLE = 'ROLE'; - DataACL.ALL = '*'; - - DataACL.applyFilter = function dataAclApplyFilter(ctx, callback) { - var roleModel = loopback.getModelByType('BaseRole'); - - var method = ctx.method; - var req = ctx.req; - var Model = method.ctor; - - var accessType = DataACL.ALL; - if (Model) { - accessType = Model._getAccessTypeForMethod(method); - } - - accessType = accessType || AccessContext.WRITE; - - var modelName = Model.clientModelName || Model.modelName; - var accessTypeQuery = { - inq: [accessType, DataACL.ALL] - }; - var propertyQuery = { - inq: [method.name, DataACL.ALL] - }; - - var wc = { - where: { - model: modelName, - property: propertyQuery, - accessType: accessTypeQuery - } - }; - this.find(wc, ctx.req.callContext, function modelFind(err, dataacls) { - if (err) { - callback(err); - } - // dataFilter will have 2 elements one where property is exact matched and one for wild card match as property name in datacl can be * - var dataFilter = []; - var inRoleTasks = []; - dataFilter.push({}); - dataFilter.push({}); - - if (dataacls.length === 0) { - return callback(); - } - - var context = new AccessContext({ - accessToken: req.accessToken, - model: Model, - property: method.name, - method: method.name, - sharedMethod: method, - accessType: accessTypeQuery, - remotingContext: ctx - }); - // Checking the AccessContext has accessToken with roles attached - if (ctx.req.accessToken && ctx.req.accessToken.roles && Array.isArray(ctx.req.accessToken.roles)) { - // Looping through Roles in accessToken and adding them to context.principals as RoleMapping.ROLE - ctx.req.accessToken.roles.forEach((role) => { - context.addPrincipal(RoleMapping.ROLE, role); - }); - } - var errorCode; - dataacls.forEach(function dataaclsForEach(dataacl) { - dataacl.filter = dataacl.filter || {}; - dataacl.property = dataacl.property || '*'; - var exactMatch = dataacl.property === method.name ? 1 : 0; - dataacl.group = dataacl.group || '__DEFAULT__'; - if (dataacl.principalType === DataACL.USER) { - if (req.accessToken && String(req.accessToken.userId) === String(dataacl.principalId)) { - errorCode = errorCode || dataacl.errorCode; - dataFilter[exactMatch][dataacl.group] = dataFilter[exactMatch][dataacl.group] || []; - dataFilter[exactMatch][dataacl.group].push(dataacl.filter); - } - } else if (dataacl.principalType === DataACL.ROLE) { - inRoleTasks.push(function inRoleTasksFn(done) { - roleModel.isInRole(dataacl.principalId, context, - function checkIsInRole(err, inRole) { - if (!err && inRole) { - dataFilter[exactMatch][dataacl.group] = dataFilter[exactMatch][dataacl.group] || []; - dataFilter[exactMatch][dataacl.group].push(dataacl.filter); - errorCode = errorCode || dataacl.errorCode; - } - done(err, dataacl.filter); - }); - }); - } - }); - - async.parallel(inRoleTasks, function inRoleTasks(err, results) { - if (err) { - return callback(err, null); - } - - var filterUsed = (dataFilter[1] && Object.keys(dataFilter[1]).length) ? dataFilter[1] : dataFilter[0]; - // console.log('dataFilter ', JSON.stringify(dataFilter)); - - if (Object.keys(filterUsed).length === 0) { - return callback(); - } - - var obj = {}; - var callContext = ctx.req.callContext || {}; - try { - buildFilter(filterUsed, callContext.ctx); - } catch (err) { - var error = new Error(); - error.name = 'DataACL Definition Error'; - error.message = `The DataACL defined is invalid for model ${modelName} `; - error.code = 'DATA_ACL_ERROR_090'; - error.type = 'value in context is missing'; - log.error(log.defaultContext(), 'DataACL filter error', error); - return callback(error); - } - var filter = ctx.args.filter || {}; - if (typeof filter === 'string') { - filter = JSON.parse(filter); - } - - Object.keys(filterUsed).forEach(function filterUsedForEach(group) { - if (filterUsed[group].length === 1) { - mergeQuery(filter, { - 'where': { - 'and': filterUsed[group] - } - }); - } else { - mergeQuery(filter, { - 'where': { - 'or': filterUsed[group] - } - }); - } - }); - - log.debug(callContext, 'filter in dataacl ', filter); - - var failed = []; - ctx.args.filter = filter; - if (accessType === 'READ') { - // ctx.args.filter = filter; - return callback(); - } - var coll = []; - if (ctx.instance) { - coll.push(ctx.instance); - try { - var result = applyFilter(coll, filter); - if (!result.length) { - failed.push(ctx.instance); - } - } catch (e) { - // TODO what is this error - failed.push(ctx.instance); - } - } - - var updateMethods = ['create', 'updateAttributes', 'update', 'updateOrCreate', 'upsert', 'replaceById', 'replaceOrCreate']; - var applyDataACLOnBody = updateMethods.indexOf(method.name) >= 0 || method.applyDataACLOnBody; - // Only if this method is for parent model - // and only for those methods which accepts body - // relation method name starts with '__', so checkBody will be false - if (applyDataACLOnBody && ctx.req.body) { - var arr = ctx.req.body; - if (!Array.isArray(arr)) { - arr = []; - arr.push(ctx.req.body); - } - arr.forEach(function recordForEach(record) { - var coll = []; - coll.push(record); - try { - var result = applyFilter(coll, filter); - if (!result.length) { - failed.push(record); - } - } catch (e) { - // TODO what is this error - failed.push(record); - } - }); - } - if (failed.length) { - errorCode = errorCode || 'data-acl-err-001'; - obj.options = callContext; - errorUtil(errorCode, obj, function errorUtil(err) { - err.statusCode = 403; - log.error(log.defaultContext(), 'Data Access Control does not allow access', err); - return callback(err, failed); - }); - } else { - return callback(); - } - }); - }); - }; -}; diff --git a/common/models/framework/data-ACL.json b/common/models/framework/data-ACL.json deleted file mode 100644 index 53b5eb7..0000000 --- a/common/models/framework/data-ACL.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "DataACL", - "plural": "DataACLs", - "description": "Stores Filter conditions on data for specific role or user", - "base": "BaseEntity", - "properties": { - "model": { - "type": "string", - "required": true, - "description": "The name of the model", - "max": 60 - }, - "accessType": { - "type": "string", - "required": false, - "description": "Valid values are READ, WRITE, EXECUTE, *", - "default": "*", - "max": 30 - }, - "principalType": { - "type": "string", - "required": true, - "description": "USER or ROLE", - "max": 30 - }, - "property": { - "type": "string", - "default": "*", - "required": false, - "description": "Method name of model, for all methods * can be given" - }, - "principalId": { - "type": "string", - "max": 60, - "required": true, - "description": "userId or roleId depending upon principalType" - }, - "filter": { - "type": "object", - "required": true, - "description": "Loopback style filter, additionaly you can use @CC. syntax for call context values in value field of conditions." - }, - "group": { - "type": "string", - "required": false, - "max": 30, - "description": "If group name is different then each group is and condition in overall filter. Each condition in same group name is alsways or condition" - }, - "errorCode": { - "type": "string", - "required": false, - "description": "errorCode to be used for unauthorised data access" - } - }, - "validations": [], - "cacheable": true, - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/data-source-definition.js b/common/models/framework/data-source-definition.js deleted file mode 100644 index 3b95e7b..0000000 --- a/common/models/framework/data-source-definition.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var util = require('../../../lib/common/util'); - -/** - * @classdesc This model is to hold DataSourceDefinition, actual data sources created of application - * This is used to create / delete data source dynamically - * Only admin guys should have access to this model - * @kind class - * @author Praveen/Atul - * @class DataSourceDefinition - */ - -module.exports = function dataSourceDefinitionModelFn(dataSourceDefinitionModel) { - /* - * 'after save' - hook is used to create actual data source in loopback - * User posts the data to DataSourceDefinition model and then this hoook is executed - * when data is saved. After that this hook uses utility function to create data source - * - * @param {object} ctx - saved data context which contains actual data saved - * @param {function} next - next: a callback function for continuation - */ - dataSourceDefinitionModel.observe('after save', function dataSourceDefinitionAfterSave(ctx, next) { - var dataSourceDefn = ctx.instance; - util.createDataSource(ctx.Model.app, dataSourceDefn, ctx.options); - return next(); - }); - /** - * 'before delete' - hook is used to delete data source from loopback. - * User deletes a records from DataSourceDefinition model this is executed. - * @param {object} ctx - data context which contains actual data - * @param {function} next - next: a callback function for continuation - */ - dataSourceDefinitionModel.observe('before delete', function dataSourceDefinitionBeforeDelete(ctx, next) { - var where = ctx.where; - dataSourceDefinitionModel.find({ - where: where - }, ctx.options, function dataSourceDefinitionModelBeforeDeleteFindCb(err, results) { - if (err) { - ctx.Model.app.handleError({ - 'message': 'Database error while trying to fetch DataSourceDefinitions from DS Definition DB', - 'cause': err, - 'details': '' - }); - return next(); - } - results.forEach(function dataSourceDeleteFn(r) { - util.deleteDatasource(ctx.Model.app, r); - }); - }); - next(); - }); -}; diff --git a/common/models/framework/data-source-definition.json b/common/models/framework/data-source-definition.json deleted file mode 100644 index a64a069..0000000 --- a/common/models/framework/data-source-definition.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "DataSourceDefinition", - "base": "BaseEntity", - "description" : "Application Data Sources configuration", - "idinjection": true, - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization":false - }, - "properties": { - - "name": { - "type": "string", - "required": true, - "unique" : true, - "max":100 - }, - "connector": { - "type": "string", - "max":100 - }, - "host": { - "type": "string", - "max":100 - }, - "port": { - "type": "number" - }, - "url": { - "type": "string", - "max":250 - }, - "database": { - "type": "string", - "max":100 - }, - "user": { - "type": "string", - "max":100 - }, - "password": { - "type": "string", - "max":100 - }, - "priority": { - "type": "Number", - "default": 0 - }, - "connectionTimeout" : { - "type" : "Number", - "default" : 5000 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/data-source-mapping.js b/common/models/framework/data-source-mapping.js deleted file mode 100644 index 97372d5..0000000 --- a/common/models/framework/data-source-mapping.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * @classdesc This is the companion js file of the DataSourceMapping Model, which gets executed - * once during the lifetime of the application (at the time of DataSourceMapping model creation) - * This model maitains mapping between datasource and model for a context. This is derived from baseEntity - * @kind class - * @author Praveen/Atul - * @class DataSourceMapping - */ - -module.exports = function dataSourceMappingModelFn(dataSourceMappingModel) { - /** - * update datasource mapping in memory used for datasource switch mixin - * This will keep data base and in memory collection in sync - * - * @param {object} ctx - save context which contains data - * @param {function} next - next continuation callback - */ - - // - dataSourceMappingModel.observe('after save', function dataSourceMappingAfterSave(ctx, next) { - var mapping = ctx.instance; - if (!mapping) { - return next(); - } - - var app = dataSourceMappingModel.app; - app.locals.dataSourceMappings = app.locals.dataSourceMappings || {}; - app.locals.dataSourceMappings[mapping.modelName] = app.locals.dataSourceMappings[mapping.modelName] || []; - var idx = app.locals.dataSourceMappings[mapping.modelName].findIndex(function findById(element, index, array) { - // converting id (bson object) into string and comparing the ids. - if (element.id.toString() === mapping.id.toString()) { - return true; - } - return false; - }); - - if (idx >= 0) { - app.locals.dataSourceMappings[mapping.modelName][idx] = mapping; - } else { - app.locals.dataSourceMappings[mapping.modelName].push(mapping); - } - return next(); - }); - - // after delete - dataSourceMappingModel.observe('after delete', function dataSourceMappingBeforeDelete(ctx, next) { - next(); - }); -}; diff --git a/common/models/framework/data-source-mapping.json b/common/models/framework/data-source-mapping.json deleted file mode 100644 index d69da3f..0000000 --- a/common/models/framework/data-source-mapping.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "DataSourceMapping", - "base": "BaseEntity", - "plural": "DataSourceMappings", - "description": "Mapping between Data Sources and Model", - "options": { - "isFrameworkModel": true, - "disableManualPersonalization":false - }, - "properties": { - "modelName": { - "type": "string", - "required": true, - "max": 30 - }, - "dataSourceName": { - "type": "string", - "required": true, - "default": "db", - "max": 30 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/framework/db-transaction.js b/common/models/framework/db-transaction.js deleted file mode 100644 index af26093..0000000 --- a/common/models/framework/db-transaction.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('DbTransaction'); -// var debug = require('debug')('DbTransaction'); -var async = require('async'); - -/** - * @classdesc This model is to hold intermediate transaction data in an ongoing two phased commit - * This is a private model not exposed as REST API - * No ACL for this model as whoever initiate a two phased commit will use this model programmatically - * @kind class - * @author Dipayan - * @class db-transaction - */ - -module.exports = function DBTransactionFn(DbTransaction) { - /** - * This reconcile event in db-transaction reconciles failed two phased commits - * This is a event listner, should be configured and called using job scheduler. - * accepts unique transaction object - * rollback create as soft delete (set _isDeleted as true) - * rollback update with oldData in transaction object for each transcation - * - * @param {object} transaction - a unique transaction data from db-transaction collection - */ - DbTransaction.on('reconcile', function dbTransactionOnReconcileFn(obj) { - log.debug(obj.options, 'Reconcile job started '); - var dbTxn = loopback.getModel('DbTransaction'); - // dbTxn.find({ and: [{ status: { neq: "done" } }, { or: [{ opData: null }, { opData: undefined }] }] }, {}, function (err, res) { - // async.each(transactions, function (txn, callback) { - var transactionId = obj.transactionId; - obj.options = obj.options || { ctx: {} }; - dbTxn.find({ - 'where': { - 'and': [{ - 'transactionId': transactionId - }, { - 'opData': { - 'neq': null - } - }] - } - }, obj.options, function dbTransactionFindCb(err, txnEach) { - if (err || (txnEach && txnEach.length === 0)) { - return; - } - async.each(txnEach, function dbTransactionAsyncEachTxnFn(t, callback) { - if (t.opData) { - var model = loopback.getModel(t.opData.modelName); - var conn = model.getDataSource(obj.options).connector; - // defaulting to idName as id in case of rest (as at this moment no way to find idName for rest) - var idName = conn.dataSource.name === 'rest' ? 'id' : conn.idName(t.opData.modelName); - switch (t.opData.op) { - case 'create': - var setIsDeletedTrue = (t.opData.data && t.opData.data._version) ? { '_version': t.opData.data._version, '_isDeleted': true } : { '_isDeleted': true }; - if (conn.updateAttributes.length === 5) { - conn.updateAttributes(t.opData.modelName, t.opData.data[idName], setIsDeletedTrue, {}, function reconcileCreateVerForDb(err) { - if (err) { - log.error(obj.options, 'Error: Reconcile error - ', err.message); - } - callback(); - }); - } else { - conn.updateAttributes(t.opData.modelName, t.opData.data[idName], setIsDeletedTrue, function reconcileCreateVerForRest(err) { - if (err) { - log.error(obj.options, 'Error: Reconcile error - ', err.message); - } - callback(); - }); - } - break; - case 'updateAll': - // TO DO: check, by now the data is updated successfully by other means; if it is, do nothing; else revert - delete t.opData.oldData[idName]; - if (t.opData.data && t.opData.data._version) { - if (t.opData.oldData && !t.opData.oldData.__oldVersion) { - t.opData.oldData.__oldVersion = t.opData.data._version; - } - t.opData.oldData._version = t.opData.data._version; - } - conn.update(t.opData.modelName, { idName: t.opData.data[idName] }, t.opData.oldData, {}, function reconcileUpdateAll(err) { - if (err) { - log.error(obj.options, 'Error: Reconcile error - ', err.message); - } - callback(); - }); - break; - case 'updateOrCreate': - break; - case 'updateAttributes': - // TO DO: check, by now the data is updated successfully by other means; if it is, do nothing; else revert - delete t.opData.oldData[idName]; - if (t.opData.data && t.opData.data._version) { - if (t.opData.oldData && !t.opData.oldData.__oldVersion) { - t.opData.oldData.__oldVersion = t.opData.data._version; - } - t.opData.oldData._version = t.opData.data._version; - } - if (conn.updateAttributes.length === 5) { - conn.updateAttributes(t.opData.modelName, t.opData.where, t.opData.oldData, {}, function reconcileUpdateAttrForDb(err) { - if (err) { - log.error(obj.options, 'Error: Reconcile error - ', err.message); - } - callback(); - }); - } else { - conn.updateAttributes(t.opData.modelName, t.opData.where, t.opData.oldData, function reconcileUpdateAttrForRest(err) { - if (err) { - log.error(obj.options, 'Error: Reconcile error - ', err.message); - } - callback(); - }); - } - break; - default: - } - } - }, function dbTransactionAsyncEachTxnErrCb(err) { - if (err) { - log.error(obj.options, 'Rollback failed!!! TransactionId ', transactionId); - return; - } - dbTxn.getDataSource().connector.update('DbTransaction', { - transactionId: transactionId - }, { - 'status': 'reconciled' - }, {}, function dbTransactionAsyncEachTxnErrUpdateCb(err) { - if (!err) { - log.debug(obj.options, 'Transactions rollback successful with transaction id : ', transactionId); - } else { - log.debug(obj.options, 'Transactions rollback NOT successful with transaction id : ', transactionId); - } - return; - }); - }); - }); - }); -}; diff --git a/common/models/framework/db-transaction.json b/common/models/framework/db-transaction.json deleted file mode 100644 index 66325fc..0000000 --- a/common/models/framework/db-transaction.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "DbTransaction", - "base": "BaseEntity", - "description": "This model is used to hold intermediate transaction data in an ongoing two phased commit", - "mixins": { - "ObserverMixin": false, - "HistoryMixin": false, - "CacheMixin": false, - "ModelValidations": false, - "ExpressionAstPopulatorMixin": false, - "AuditFieldsMixin": false, - "DataPersonalizationMixin": false, - "SwitchDatasourceMixin": false, - "VersionMixin": false, - "WorkflowMixin": false, - "BusinessRuleMixin": false, - "SoftDeleteMixin": false, - "PropertyExpressionMixin": false, - "FailsafeObserverMixin": false, - "IdempotentMixin": false, - "CryptoMixin": false - }, - "options": { - "validateUpsert": false, - "dbTxn": true, - "isFrameworkModel": true - }, - "properties": { - "transactionId": { - "type": "string", - "required": true, - "index": true, - "max": 30 - }, - "opData": { - "type": "object" - }, - "status": { - "type": "string", - "required": true, - "index": true, - "max": 30 - }, - "lastUpdatedTime": { - "type": "date", - "required": false - } - }, - "validations": [], - "acls": [], - "methods": {} -} diff --git a/common/models/framework/decision-graph.js b/common/models/framework/decision-graph.js deleted file mode 100644 index 4da23dc..0000000 --- a/common/models/framework/decision-graph.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var XLSX = require('xlsx'); -var jsFeel = require('js-feel')(); -var DL = jsFeel.decisionLogic; -var logger = require('oe-logger'); -var log = logger('decision-graph'); -var serialize = require('serialize-error'); - -var { createDecisionGraphAST, executeDecisionService } = jsFeel.decisionService; - - -module.exports = function (DecisionGraph) { - // Remote method to validate the nodes of a decision graph POSTed from the Rule Designer - DecisionGraph.remoteMethod('validate', { - description: 'Validate the nodes of a decision graph from the Rule Designer', - accessType: 'WRITE', - isStatic: true, - accepts: [{ - arg: 'inputData', type: 'object', http: { source: 'body' }, - required: true, description: 'The JSON containing the graph node data to validate' - } - ], - http: { - verb: 'POST', - path: '/validate' - }, - returns: { - type: 'object', - root: true - } - }); - - // Validates the nodes with data POSTed from the Rule Designer - DecisionGraph.validate = function validateDecisionGraph(inputData, options, cb) { - var output = {}; - Object.keys(inputData).forEach(function (key) { - var isValid = false; - var message = null; - try { - jsFeel.feel.parse(inputData[key]); - isValid = true; - } catch (e) { - message = { - name: e.name, - location: e.location - }; - } - output[key] = { - valid: isValid, - errormessage: message - }; - }); - cb(null, output); - }; - - DecisionGraph.observe('before save', function beforeSaveDecisionGraph(ctx, next) { - var data = ctx.instance || ctx.data; - var document = ctx.options.graphDocument; - if (document) { - return next(); - } - DecisionGraph.validate(data.data, ctx.options, function validateHandler(err, output) { - if (err) { - next(err); - } - var isInValid = Object.keys(output).find(function invalidFinder(k) { - return !output[k].valid; - }); - - if (isInValid) { - next(new Error('Decision graph contains an invalid FEEL node.')); - } else { - next(); - } - }); - }); - // Remote method to execute a Decision Service with data POSTed from the Rule Designer - DecisionGraph.remoteMethod('execute', { - description: 'Executes a Decision Service Payload Posted from the Rule Designer', - accessType: 'WRITE', - isStatic: true, - accepts: [{ arg: 'inputData', type: 'object', http: { source: 'body' }, - required: true, description: 'The JSON containing the graph data and payload to execute' } - ], - http: { - verb: 'POST', - path: '/execute' - }, - returns: { - type: 'object', - root: true - } - }); - - // Executes a Decision Service with data POSTed from the Rule Designer - DecisionGraph.execute = function (inputData, options, cb) { - var decisionMap = inputData.jsonFeel; - var decisions = inputData.decisions; - var payload = inputData.payload; - - var ast = createDecisionGraphAST(decisionMap); - - var promises = decisions.map(d => executeDecisionService(ast, d, payload)); - - Promise.all(promises).then(answers => { - cb(null, answers); - }).catch(err => { - log.error(err); - cb(serialize(err), null); - }); - }; - - DecisionGraph.observe('before save', function DecisionGraphBeforeSaveFn( - ctx, - next - ) { - var dataObj = ctx.instance || ctx.data; - var document = ctx.options.graphDocument; - if (!document) return next(); - var base64String = document.documentData.split(',')[1]; - // var binaryData = Buffer.from(base64String, 'base64').toString('binary'); - var binaryData = new Buffer(base64String, 'base64').toString('binary'); - var workbook = XLSX.read(binaryData, { type: 'binary' }); - try { - var jsonFeel = DL.parseWorkbook(workbook); - dataObj.data = jsonFeel; - next(); - } catch (e) { - log.error(ctx.options, 'Unable to process workbook data -', e); - next( - new Error( - 'Decision Graph workbook data could not be parsed. Please correct errors in the workbook.' - ) - ); - } - }); -}; diff --git a/common/models/framework/decision-graph.json b/common/models/framework/decision-graph.json deleted file mode 100644 index 917c0a8..0000000 --- a/common/models/framework/decision-graph.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "DecisionGraph", - "base": "BaseEntity", - "description": "This model is used for storing decision graphs", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization":false - }, - "properties": { - "name": { - "type": "string", - "required": true, - "unique": true, - "id": true - }, - "data": { - "type": "object", - "hidden": true, - "description": "The json feel data" - }, - "decisions": { - "type": "Array", - "hidden": true, - "description": "The decisions to execute", - "required" : false - }, - "graph": { - "type": "object", - "hidden": true, - "description": "The graph", - "required" : false - }, - "payload": { - "type": "string", - "hidden": true, - "description": "The payload to execute the decisions on", - "required" : false - } - - }, - "oeValidations": [], - "validations": [], - "cacheable": true, - "acls": [], - "methods": {}, - "relations": { - "graphDocument": { - "type": "belongsTo", - "model": "DocumentData", - "foreignKey": "" - } - } -} \ No newline at end of file diff --git a/common/models/framework/decision-service.js b/common/models/framework/decision-service.js deleted file mode 100644 index 9a872f9..0000000 --- a/common/models/framework/decision-service.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -// var XLSX = require('xlsx'); -var feel = require('js-feel')(); -var logger = require('oe-logger'); -var log = logger('decision-service'); -var util = require('util'); -// var app = require('loopback')(); -var loopback = require('loopback'); - - -module.exports = function (DecisionService) { - var DecisionGraph; - DecisionService.observe('before save', function (ctx, next) { - var dataObj = ctx.instance || ctx.data; - var decisions = dataObj.decisions; - // var app = loopback(); - if (!DecisionGraph) { - DecisionGraph = loopback.findModel('DecisionGraph'); - } - - // dataObj['decision-graph'](ctx.options, function (err, result) { - // if (err) { - // next(err); - // } else { - // // var keys = result.data; - // // eslint-disable-next-line - // if (decisions.every(p => p in result.data)) { - // next(); - // } else { - // var idx = decisions.findIndex(d => !(d in result.data)); - // var item = decisions[idx]; - // var errStr = util.format('Decision %s does not belong to the decision graph: %s', item, result.name); - // log.error(errStr); - // next(new Error(errStr)); - // } - // } - - // }); - - DecisionGraph.findById(dataObj.graphId, ctx.options, function (err, graph) { - if (err) { - next(err); - } else if (decisions.every(p => p in graph.data)) { - next(); - } else { - var idx = decisions.findIndex(d => !(d in graph.data)); - var item = decisions[idx]; - var errStr = util.format('Decision "%s" does not belong to the decision graph: "%s"', item, graph.name); - log.error(errStr); - next(new Error(errStr)); - } - }); - }); - - DecisionService.remoteMethod('invoke', { - description: 'Invoke service with name and payload', - accepts: [ - { - arg: 'name', - type: 'string', - http: { - source: 'path' - }, - required: true, - description: 'name of the service' - }, - { - arg: 'payload', - type: 'object', - description: 'the payload for this decision service', - http: { - source: 'body' - }, - required: true - } - ], - returns: { - arg: 'response', - type: 'object', - root: true - }, - http: { - verb: 'POST', - path: '/invoke/:name' - } - }); - - DecisionService.invoke = function DecisionServiceInvoke(name, payload, options, cb) { - DecisionService.findOne({ where: { name: name } }, options, (err, result) => { - if (err) { - cb(err); - } else { - var decisions = result.decisions; - result['decision-graph'](options, (err, graph) => { - if (err) { - cb(err); - } else { - // var decisionMap = graph.data; - // var ast = DS.createDecisionGraphAST(decisionMap); - // var promises = decisions.map(d => DS.executeDecisionService(ast, d, payload)); - // Promise.all(promises).then(answers => { - // var final = answers.reduce((hash, answer) => { - // return Object.assign({}, hash, answer); - // }, {}); - - // cb(null, final); - // }) - // .catch(err => { - // cb(err); - // }); - feel.executeDecisionGraph(graph, decisions, payload) - .then(answers => { - var result = answers.reduce((hash, ans, idx) => { - var r = { [decisions[idx]]: ans }; - return Object.assign({}, hash, r); - }, {}); - cb(null, result); - }) - .catch(cb); - } - }); - } - }); - }; -}; diff --git a/common/models/framework/decision-service.json b/common/models/framework/decision-service.json deleted file mode 100644 index 305face..0000000 --- a/common/models/framework/decision-service.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "DecisionService", - "base": "BaseEntity", - "description": "This model is used to expose services from decision graphs stored in DecisionGraph model", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization":false - }, - "properties": { - "name": { - "type": "string", - "required": true, - "description": "name for decision service", - "unique": true - }, - "decisions": { - "type": ["string"], - "description": "decisions this service exposes from its associated decision graph", - "required": true - } - }, - "oeValidations": [], - "validations": [], - "relations": { - "decision-graph": { - "type": "belongsTo", - "model": "DecisionGraph", - "foreignKey": "graphId" - } - }, - "cacheable": true, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/decision-table.js b/common/models/framework/decision-table.js deleted file mode 100644 index 8b620e0..0000000 --- a/common/models/framework/decision-table.js +++ /dev/null @@ -1,349 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var XLSX = require('xlsx'); -var jsFeel = require('js-feel')(); -var request = require('request'); -var utils = require('../../../lib/common/util'); -var assert = require('assert'); -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('decision-table'); -var getError = require('../../../lib/common/error-utils').getValidationError; -var delimiter = '&SP'; - -const dTable = jsFeel.decisionTable; - -module.exports = function decisionTableFn(decisionTable) { - function droolsUrl() { - var droolsHost = process.env.DROOLS_HOST || 'localhost'; - var droolsPort = process.env.DROOLS_PORT || '8080'; - return 'http://' + droolsHost + ':' + droolsPort + '/evdrools'; - } - decisionTable.observe('before save', function decisionTableBeforeSave( - ctx, - next - ) { - var businessRuleEngine = 'evBusinessRule'; - var document = ctx.options.document; - if (document) { - var SystemModel = loopback.getModel('SystemConfig'); - SystemModel.find( - { - where: { - key: 'businessRuleEngine' - } - }, - ctx.options, - function decisionTableBeforeSaveCb(err, configData) { - if (err) { - next(err); - } - if (configData.length) { - businessRuleEngine = configData[0].value.engine; - } - if (businessRuleEngine === 'DROOLS') { - var url = droolsUrl(); - var path = 'validate'; - var reqOptions = { - method: 'POST', - url: url + '/' + path, - headers: { - 'content-type': 'application/json' - }, - body: document, - json: true - }; - request(reqOptions, function requestFn(error, response, body) { - if (error) { - return next(error); - } - if (body.message === 'valid') { - next(); - } else { - var err = new Error(JSON.stringify(body.info)); - err.retriable = false; - return next(err); - } - }); - } else { - try { - // Code to handle files for jsFEEL - if ( - typeof document.documentData !== 'string' || - document.documentData.indexOf('base64') < 0 - ) { - return next( - new Error( - 'Decision table data provided is not a base64 encoded string' - ) - ); - } - var base64String = document.documentData.split(',')[1]; - // var base64String = ctx.instance.documentData.replace('data:' + ctx.instance.fileType + ';base64,', ''); - // The following usage of new Buffer() is deprecated from node v6.0 - var binaryData = new Buffer(base64String, 'base64').toString( - 'binary' - ); - var workbook = XLSX.read(binaryData, { - type: 'binary' - }); - var sheet = workbook.Sheets[workbook.SheetNames[0]]; - var csv = XLSX.utils.sheet_to_csv(sheet, { FS: delimiter }); - var decisionRules = dTable.csv_to_decision_table(csv); - - var data = ctx.instance || ctx.data; - data.decisionRules = JSON.stringify(decisionRules); - next(); - } catch (err) { - log.error( - ctx.options, - 'Error - Unable to process decision table data -', - err - ); - var error = new Error( - 'Decision table data provided could not be parsed, please provide proper data' - ); - return next(error); - } - } - } - ); - } else { - next(); - } - }); - - // Remote method to parse the excel sheet and return a valid decision table object - decisionTable.remoteMethod('parseExcel', { - description: 'Parse the uploaded excel and return valid decision table', - accessType: 'WRITE', - isStatic: true, - accepts: [{ - arg: 'inputData', type: 'object', http: { source: 'body' }, - required: true, description: 'The JSON containing the document data to parse' - } - ], - http: { - verb: 'POST', - path: '/parseExcel' - }, - returns: { - type: 'object', - root: true - } - }); - - // Parses the excel uploaded from the feel designer - decisionTable.parseExcel = function (inputData, options, cb) { - var document = inputData; - if ( - typeof document.documentData !== 'string' || - document.documentData.indexOf('base64') < 0 - ) { - return cb( - new Error( - 'Decision table data provided is not a base64 encoded string' - ) - ); - } - var base64String = document.documentData.split(',')[1]; - var binaryData = new Buffer(base64String, 'base64').toString( - 'binary' - ); - var workbook = XLSX.read(binaryData, { - type: 'binary' - }); - - var sheet = workbook.Sheets[workbook.SheetNames[0]]; - var csv = XLSX.utils.sheet_to_csv(sheet, { FS: delimiter }); - var decisionRules = dTable.csv_to_decision_table(csv); - cb(null, decisionRules); - }; - - decisionTable.remoteMethod('exec', { - description: 'execute a business rule', - accessType: 'WRITE', - accepts: [ - { - arg: 'documentName', - type: 'string', - required: true, - http: { - source: 'path' - }, - description: 'Name of the Document to be fetched from db for rule engine' - }, - { - arg: 'data', - type: 'object', - required: true, - http: { - source: 'body' - }, - description: 'An object on which business rules should be applied' - } - ], - http: { - verb: 'post', - path: '/exec/:documentName' - }, - returns: { - arg: 'data', - type: 'object', - root: true - } - }); - - decisionTable.exec = function decisionTableExec( - documentName, - data, - options, - callback - ) { - var businessRuleEngine = 'evBusinessRule'; - if (typeof callback === 'undefined') { - if (typeof options === 'function') { - // execrule (documentName, data, callback) - callback = options; - options = {}; - } - } - - data = data || {}; - options = options || {}; - callback = callback || utils.createPromiseCallback(); - - assert( - typeof documentName === 'string', - 'The documentName argument must be string' - ); - assert( - typeof data === 'object', - 'The data argument must be an object or array' - ); - assert( - typeof options === 'object', - 'The options argument must be an object' - ); - assert( - typeof callback === 'function', - 'The callback argument must be a function' - ); - - decisionTable.find( - { - where: { - name: documentName - } - }, - options, - function decisionTableFind(err, decisionTableData) { - if (err) { - callback(err); - } else if (decisionTableData.length) { - var SystemModel = loopback.getModel('SystemConfig'); - SystemModel.find( - { - where: { - key: 'businessRuleEngine' - } - }, - options, - function systemModelFind(err, configData) { - if (err) { - callback(err); - } else if (configData.length) { - businessRuleEngine = configData[0].value.engine; - } - var docId = decisionTableData[0].documentId; - if (businessRuleEngine === 'DROOLS') { - var path = 'execrule'; - var url = droolsUrl(); - // Request module - var reqOptions = { - method: 'POST', - url: url + '/' + path + '/' + docId, - headers: { - 'content-type': 'application/json' - }, - body: data, - json: true - }; - request(reqOptions, function requestCallback( - error, - response, - body - ) { - if (error) { - callback(error, null); - } else { - callback(null, body); - } - }); - } else { - var rules = JSON.parse(decisionTableData[0].decisionRules); - dTable.execute_decision_table(docId, rules, data, function ( - err, - results - ) { - results = results || []; - if (rules.hitPolicy === 'V') { - if (err) { - getError('JS_FEEL_ERR', { options: options, name: 'JS_FEEL' }, function validateMaxGetErrCb(error) { - error.errMessage = err; - results.push(error); - callback(null, results); - }); - } else { - callback(null, results); - } - } else if (err) { - callback(err, null); - } else { - data = processPayload(results, data); - callback(null, data); - } - }); - } - } - ); - } else { - var err1 = new Error( - 'No Document found for DocumentName ' + documentName - ); - err1.retriable = false; - callback(err1); - } - } - ); - }; -}; - -function processPayload(results, payload) { - var deltaPayload = {}; - if (Array.isArray(results)) { - results.forEach(function resultsForEach(rowObj) { - Object.keys(rowObj).forEach(function rowObjectsForEachKey(key) { - if (results.length > 1) { - deltaPayload[key] = deltaPayload[key] || []; - deltaPayload[key].push(rowObj[key]); - } else { - deltaPayload[key] = deltaPayload[key] || {}; - deltaPayload[key] = rowObj[key]; - } - }); - }); - } else { - deltaPayload = results || {}; - } - - Object.keys(deltaPayload).forEach(function deltaPayloadForEachKey(k) { - payload[k] = deltaPayload[k]; - }); - return payload; -} diff --git a/common/models/framework/decision-table.json b/common/models/framework/decision-table.json deleted file mode 100644 index a4540ef..0000000 --- a/common/models/framework/decision-table.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "DecisionTable", - "base": "BaseEntity", - "description": "This model is used to business rule", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization":false - }, - "properties": { - "name": { - "type": "String", - "required": true, - "unique": true - }, - "decisionRules": { - "type": "String", - "hidden": true, - "oracle":{ - "dataType":"CLOB" - } - } - }, - "oeValidations": [], - "validations": [], - "relations": { - "document": { - "type": "belongsTo", - "model": "DocumentData", - "foreignKey": "" - } - }, - "cacheable": true, - "acls": [], - "methods": {}, - "mixins":{ - "CacheMixin": false - } -} \ No newline at end of file diff --git a/common/models/framework/dev.js b/common/models/framework/dev.js deleted file mode 100644 index 69eeeea..0000000 --- a/common/models/framework/dev.js +++ /dev/null @@ -1,201 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/** - * - * @classdesc This Model is to enable utility methods. - * @kind class - * @class devutil - * @author Praveen Kumar Gulati - */ - -var loopback = require('loopback'); -var async = require('async'); - -var loopbackAccessContext = require('loopback/lib/access-context'); -var AccessContext = loopbackAccessContext.AccessContext; - -module.exports = function DevModelFn(devmodel) { - devmodel.getinfo = function GetInfoFn(ctx, options, cb) { - var data = { callContext: {}, accessToken: {} }; - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - data.accessToken = ctx.req.accessToken; - data.callContext = ctx.req.callContext; - data.options = ctx.options; - - cb(null, data); - }; - - devmodel.checkACL = function (ctx, modelName, propertyName, options, cb) { - var data = {}; - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - var accessTypeQuery = { - inq: ['READ', 'WRITE', '*'] - }; - - var propertyQuery = { - inq: [propertyName, '*'] - }; - - var modelClass = loopback.findModel(modelName); - var BaseACL = loopback.getModelByType('BaseACL'); - var staticACLs = BaseACL.getStaticACLs(modelName, propertyName); - data.staticACLs = staticACLs; - if (staticACLs.length === 0) { - data.staticACLMessage = 'no static ACLs applicable for model and property'; - if (modelClass && modelClass.settings.acls) { - data.modelACLs = modelClass.settings.acls; - } - } - - var roleModel = loopback.getModelByType('BaseRole'); - var context = new AccessContext({ - accessToken: ctx.req.accessToken, - model: modelClass, - property: propertyName, - method: propertyName, - accessType: accessTypeQuery, - remotingContext: ctx - }); - - var filter = { where: { model: modelName, property: propertyQuery } }; - BaseACL.find(filter, options, function (err, acls) { - if (err) { - cb(err); - } - data.dbACLs = acls; - - var inRoleTasks = []; - - var allAcls = acls.concat(staticACLs); - - data.matchedACLs = []; - data.unmatchedACLs = []; - allAcls.forEach(function (acl) { - if (acl.principalType === BaseACL.USER) { - if (options.userId === String(acl.principalId)) { - data.matchedACLs.push(acl); - } else { - data.unmatchedACLs.push(acl); - } - } - - // Check role matches - if (acl.principalType === 'ROLE') { - inRoleTasks.push(function (done) { - roleModel.isInRole(acl.principalId, context, - function (err, inRole) { - if (!err && inRole) { - data.matchedACLs.push(acl); - } else { - data.unmatchedACLs.push(acl); - } - done(err, acl); - }); - }); - } - }); - - async.parallel(inRoleTasks, function (err, results) { - cb(err, data); - }); - }); - }; - - devmodel.personaliseModel = function personaliseModel(ctx, modelName, options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = {}; - } - var ModelDefinition = loopback.getModel('ModelDefinition'); - - ModelDefinition.findOne({ where: { name: modelName } }, options, function (err, modeldef) { - // console.log('model find ', err, modelName, modeldef); - if (err || !modeldef) { - return callback(err, 'Model Definition not found'); - } - var variantData = { - 'name': modeldef.name, - 'strict': false, - 'variantOf': modeldef.name, - 'base': modeldef.name - }; - // console.log(variantData); - ModelDefinition.create(variantData, options, function (err, cb) { - callback(err, cb); - }); - }); - }; - - - devmodel.remoteMethod('getinfo', { - description: 'Gets Current Context', - accessType: 'READ', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - } - ], - http: { - verb: 'GET', - path: '/getinfo' - }, - returns: { - type: 'object', - root: true - } - }); - - devmodel.remoteMethod('checkACL', { - description: 'Check ACL for a model and property', - accessType: 'READ', - accepts: [{ - arg: 'ctx', - type: 'object', - description: 'context', - http: { - source: 'context' - } - }, - { - arg: 'model', - type: 'string', - description: 'model name', - http: { - source: 'path' - } - }, - { - arg: 'property', - description: 'property of the model', - type: 'string', - http: { - source: 'path' - } - } - ], - http: { - verb: 'GET', - path: '/checkACL/:model/:property' - }, - returns: { - type: 'object', - root: true - } - }); -}; diff --git a/common/models/framework/dev.json b/common/models/framework/dev.json deleted file mode 100644 index 0625ec7..0000000 --- a/common/models/framework/dev.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "dev", - "base": "Model", - "plural": "dev", - "strict": false, - "options": { - "isFrameworkModel": true - }, - "properties": {}, - "validations": [], - "acls": [ - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "accessType": "*" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "accessType": "getinfo" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "accessType": "getinfo" - } - ], - "relations": {}, - "methods": {} -} diff --git a/common/models/framework/document-data.json b/common/models/framework/document-data.json deleted file mode 100644 index a8b565d..0000000 --- a/common/models/framework/document-data.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "DocumentData", - "base": "PersistedModel", - "options": { - "validateUpsert": true, - "isFrameworkModel": true - }, - "properties": { - "documentName": { - "type": "String", - "required": true - }, - "documentData": { - "type": "String", - "required": true, - "oracle": { - "dataType": "CLOB" - } - } - }, - "oeValidations": [], - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/document.js b/common/models/framework/document.js deleted file mode 100644 index a5930ef..0000000 --- a/common/models/framework/document.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This Model provides the functionality to upload any file. -* Additionally User can also Delete and View the uploaded files. -* -* @kind class -* @class Document -* @author Sambit Kumar Patra -*/ - -var config = require('../../../server/config.js'); - -module.exports = function DocumentFn(Document) { - /** -* This 'before remote' hook is used to intercept data -* POSTed to Document model, validate the file extension -* and pass the maximum allowed file size in limits object -* @param {object} ctx - context object -* @param {object }modelInstance - data posted -* @param {function} next - next middleware function -* @function fileUploadBeforeRemoteFn -*/ - Document.beforeRemote('upload', function fileUploadBeforeRemoteFn(ctx, modelInstance, next) { - var limits = { fileSize: config.maxFileSize ? config.maxFileSize * 1024 : null }; - ctx.req.limits = limits; - ctx.req.supportedFileExtns = config.supportedFileExtns && config.supportedFileExtns.length > 0 ? config.supportedFileExtns : null; - ctx.req.fileNamePattern = config.fileNamePattern ? config.fileNamePattern : null; - next(); - }); -}; diff --git a/common/models/framework/document.json b/common/models/framework/document.json deleted file mode 100644 index 26ff743..0000000 --- a/common/models/framework/document.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "Document", - "plural": "documents", - "base": "Model", - "idInjection": false, - "description": "Functionality to upload any file", - "options": { - "validateUpsert": true, - "isFrameworkModel": true - }, - "properties": {}, - "oeValidations": [], - "validations": [], - "relations": {}, - "acls": [ - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - }, - { - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW" - } - ], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/error.js b/common/models/framework/error.js deleted file mode 100644 index 513fedd..0000000 --- a/common/models/framework/error.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This Model stores all the Customized error details. -* -* Property | Description -* ---------|------------------------------- -* `errCode` | Error Code. -* `errMessage` | Message Associated with this code. -* `moreInformation` | Detailed description of Error. -* -* @kind class -* @class ErrorResponse -* @author Sambit Kumar Patra -*/ diff --git a/common/models/framework/error.json b/common/models/framework/error.json deleted file mode 100644 index d3a94f4..0000000 --- a/common/models/framework/error.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "Error", - "base": "BaseEntity", - "plural": "errors", - "idInjection": false, - "description": "This Model stores all the Customized error details", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization": false - }, - "properties": { - "errCode": { - "type": "string", - "max": 100 - }, - "errMessage": { - "type": "string", - "max": 250 - }, - "errCategory": { - "type": "string", - "max": 100 - }, - "moreInformation": { - "type": "string", - "max": 500 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/event-history.json b/common/models/framework/event-history.json deleted file mode 100644 index 15612a1..0000000 --- a/common/models/framework/event-history.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "EventHistory", - "plural": "EventsHistroy", - "base": "PersistedModel", - "strict": true, - "description": "ensure reliability on failing events on all entities", - "idInjection": false, - "properties": { - "hostName": { - "type": "string" - }, - "timestamp": { - "type": "Date" - }, - "doStartUpOperation": { - "type": "boolean" - }, - "latestUpdatePerModel": { - "type": "object", - "default": {} - }, - "status": { - "type": "string", - "default": "undefined" - } - }, - "options": { - "proxyEnabled": true, - "proxyMethods": [{"name":"createEventHistoryRemote"}] - } -} diff --git a/common/models/framework/failed-observer-log.json b/common/models/framework/failed-observer-log.json deleted file mode 100644 index f75d8d4..0000000 --- a/common/models/framework/failed-observer-log.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "FailedObserverLog", - "plural": "FailedObserverLogs", - "base": "PersistedModel", - "description": "Log of failed observer execution", - "idInjection": true, - "properties": { - "modelName": { - "type": "string" - }, - "version": { - "type": "string" - }, - "timestamp": { - "type": "Date" - }, - "operation":{ - "type": "string" - }, - "hostName": { - "type": "string" - }, - "hostUUID": { - "type": "string" - }, - "status": { - "type": "string" - }, - "retry": { - "type": "number" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/ldap-role-mapping.json b/common/models/framework/ldap-role-mapping.json deleted file mode 100644 index 6715a99..0000000 --- a/common/models/framework/ldap-role-mapping.json +++ /dev/null @@ -1,19 +0,0 @@ - -{ - "name": "LdapRoleMapping", - "base": "BaseEntity", - "description": "LDAP group to role mappings", - "autoscope": [ - "tenantId" - ], - "properties": { - "groupName": { - "type": "string", - "description": "DN of the ldap group to be mapped to role array" - }, - "roles": { - "type": "array", - "description": "Array of BaseRole ids" - } - } -} \ No newline at end of file diff --git a/common/models/framework/lock.json b/common/models/framework/lock.json deleted file mode 100644 index 5646239..0000000 --- a/common/models/framework/lock.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "Lock", - "base": "PersistedModel", - "description": "used in lock implementation for no-sql databases", - "properties": { - "modelName": { - "type": "string", - "required": true - }, - "modelId": { - "type": "string", - "required": true - }, - "noddeId": { - "type": "string" - }, - "lockStatus": { - "type": "string", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/logger-config.js b/common/models/framework/logger-config.js deleted file mode 100644 index cf76e35..0000000 --- a/common/models/framework/logger-config.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var loopback = require('loopback'); -var loggingModule = require('oe-logger'); -var log = loggingModule('logger-config'); -var msgService = require('../../../lib/common/global-messaging'); -var uuidv4 = require('uuid/v4'); - -msgService.subscribe('LoggerConfig', dataUpdater); - -var levelMap = { - 'trace': 10, - 'debug': 20, - 'info': 30, - 'warn': 40, - 'error': 50, - 'fatal': 60 -}; - -var reverseMap = { - '10': 'trace', - '20': 'debug', - '30': 'info', - '40': 'warn', - '50': 'error', - '60': 'fatal' -}; - -function updateLogArray(err, model) { - var instance = loggingModule('LOGGER-CONFIG'); - var loggerArray = instance.getLoggers(); - var currentLogger; - - if (err) { - log.error('recieved error on find model ', err); - return; - } else if (!model || model === {}) { - log.warn('did not find any logger configuration in the db.'); - return; - } - log.debug('found model was ', model); - var defaultLevel = model.data.default || 30; - var data = model.data; - if (!data) { - log.error('data cannot be empty'); - return; - } - - if (data.all) { - if (data.all && levelMap[data.all]) { - for (currentLogger in loggerArray) { - if (loggerArray.hasOwnProperty(currentLogger)) { - instance.changeLogger(loggerArray[currentLogger], levelMap[data.all]); - } - } - } - return; - } - - for (currentLogger in loggerArray) { - if (loggerArray.hasOwnProperty(currentLogger)) { - if ((Object.keys(data)).indexOf(currentLogger) > -1) { - if (!((data[currentLogger]) && levelMap[data[currentLogger]])) { - instance.changeLogger(loggerArray[currentLogger], defaultLevel); - } else { - instance.changeLogger(loggerArray[currentLogger], levelMap[data[currentLogger]]); - } - } else { - // default : turn off the logger - instance.changeLogger(loggerArray[currentLogger], defaultLevel); - } - } - } -} - -function dataUpdater() { - var loggerConfig = loopback.findModel('LoggerConfig'); - loggerConfig.findOne({}, { tenantId: 'default' }, updateLogArray); -} - -module.exports = function LoggerConfig(loggerConfig) { - loggerConfig.observe('before save', function loggerConfig(ctx, next) { - var loggerConfigModel = loopback.findModel('LoggerConfig'); - loggerConfigModel.deleteAll({}, ctx.options, function loggerConfigDestroyAll(err, info) { - if (err) { - err.message = 'Failed to delete old LoggerConfigModel due to: ' + err.message; - next(err); - } else { - return next(); - } - }); - }); - - loggerConfig.observe('after save', function loggerConfigAfterSave(ctx, next) { - updateLogArray(null, ctx.instance); - msgService.publish('LoggerConfig', uuidv4()); - return next(); - }); - - loggerConfig.list = function loggerConfigList(options, cb) { - var result = {}; - var loggerArray = loggingModule('LOGGER-CONFIG').getLoggers(); - Object.keys(loggerArray).forEach(function loggerArrayForEach(curval) { - result[curval] = reverseMap[loggerArray[curval].level]; - }); - cb(null, result); - }; - - loggerConfig.remoteMethod( - 'list', - { - returns: { arg: 'Loggers', type: 'object' } - } - ); -}; diff --git a/common/models/framework/logger-config.json b/common/models/framework/logger-config.json deleted file mode 100644 index 735c4f4..0000000 --- a/common/models/framework/logger-config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "LoggerConfig", - "base": "PersistedModel", - "description" : "Stores Logger configuration", - "isFrameworkModel": true, - "properties": { - "data": { - "type": "Object" - } - }, - "mixins": { - "ObserverMixin": true, - "CacheMixin": true - } -} diff --git a/common/models/framework/model-definition.js b/common/models/framework/model-definition.js deleted file mode 100644 index 18b1b52..0000000 --- a/common/models/framework/model-definition.js +++ /dev/null @@ -1,591 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var util = require('../../../lib/common/util'); -var debug = require('debug')('db-models'); -var log = require('oe-logger')('model-definition'); -var inflection = require('inflection'); -var loopback = require('loopback'); -var _ = require('lodash'); -var config = require('../../../server/config'); -var messaging = require('../../../lib/common/global-messaging'); - -module.exports = function ModelDefintionFn(modelDefinition) { - // Prevent creation of ModelDefinitions with filebased: true using the REST API - modelDefinition.beforeRemote('**', function dbModelsModelDefinitionBeforeRemoteFn(ctx, model, next) { - var modeldefinition = ctx.args.data; - if ((ctx.req.method === 'POST' || ctx.req.method === 'PUT') && modeldefinition.filebased === true) { - var msg = 'ERROR: \'filebased\' should be false for ModelDefinitions created via REST API'; - log.error(ctx.req.callContext, msg); - var err = new Error(msg); - err.retriable = false; - return next(err); - } - log.debug(ctx.req.callContext, 'modeldefinition', modeldefinition); - - if (ctx.req.method === 'DELETE') { - modeldefinition = ctx.args; - modelDefinition.find({ - 'where': { - 'id': modeldefinition.id - } - }, ctx.req.callContext, function dbModelsModelDefinitionBeforeRemoteModelDefFindCb(err, data) { - if (err) { - log.info(ctx.req.callContext, err); - return next(); - } - log.debug(ctx.req.callContext, 'data', data); - if (!(data && data.length && data.length === 1)) { - return next(); - } - log.debug(ctx.req.callContext, 'data[0].name', data[0].name); - // PKGTODO hasDependency to be rewritten to check with model and not with app - // if (hasDependency(app, data[0].name)) { - // var msg = 'ERROR: Other models are dependent on this model (' + data[0].name + '). Please delete/modify them and try again.'; - // log.info(ctx.req.callContext, msg); - // var err1 = new Error(msg); - // err1.retriable = false; - // return next(); - // } - next(); - }); - } else { - next(); - } - }); - - /** - * This is helper function and is called from Before Save Hook for ModelDefinition. This function handles mongodb specific logic - * Basically, if mongodb parameter is not set by User will set it to default to model name. - * However if model is being personalized by having variantOf field set, it will use parent model's collection name - * @callback - * @param {object} modeldefinition - The ModelDefinition object being posted for save operation - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - * @returns {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - */ - function mongoSpecificHandling(modeldefinition, ctx, next) { - if (!modeldefinition.mongodb) { - debug('Posted modeldefinition does not have the \'mongodb\' property'); - var autoscopeFields = ctx.Model.definition.settings.autoscope; - if (modeldefinition.filebased) { - let ctxStr = util.createDefaultContextString(autoscopeFields); - if (!modelDefinition.app.personalizedModels[modeldefinition.name]) { - modelDefinition.app.personalizedModels[modeldefinition.name] = {}; - } - modelDefinition.app.personalizedModels[modeldefinition.name][ctxStr] = { - 'modelId': modeldefinition.name, 'context': ctxStr - }; - } else if (modeldefinition.variantOf) { - var parentVariantModel = loopback.findModel(modeldefinition.variantOf, ctx.options); - if (parentVariantModel) { - debug('Found a parent model (not a variant itself)'); - var parentVariantMongodbParam = parentVariantModel.definition.settings.mongodb; - if (parentVariantMongodbParam) { - var parentVariantCollectionName = parentVariantMongodbParam.collection; - if (parentVariantCollectionName) { - modeldefinition.mongodb = { - collection: parentVariantCollectionName - }; - } - } - } - if (!modeldefinition.mongodb) { - modeldefinition.mongodb = { - collection: modeldefinition.variantOf - }; - } - } else { - let ctxStr = util.createContextString(autoscopeFields, ctx.options.ctx); - if (ctxStr === util.createDefaultContextString(autoscopeFields)) { - modeldefinition.mongodb = { - collection: modeldefinition.name - }; - } else { - modeldefinition.mongodb = { - collection: modeldefinition.modelId - }; - } - } - return next(); - } - return next(); - } - - /** - * This Before Save Hook for ModelDefinition is responsible for adding the filebased flag, - * the base value of BaseEntity as default, changing the model name in case the model is a variant, - * validation that the base specified is BaseEntity or a model derived from BaseEntity - * and handling variants in case of non-Mongo DBs. - * @callback - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - */ - var mdBeforeSave = function mdBeforeSave(ctx, next) { - log.debug(ctx.options, 'DEBUG: boot/db-models.js: ModelDefinition Before save called.'); - var modeldefinition = ctx.instance || ctx.currentInstance || ctx.data; - var contextString; - if (ctx.options.ignoreAutoscope || ctx.options.ignoreAutoScope) { - contextString = util.createContextString(ctx.Model.definition.settings.autoscope, {}); - } else { - contextString = util.createContextString(ctx.Model.definition.settings.autoscope, ctx.options.ctx); - } - modeldefinition.modelId = modeldefinition.modelId || util.createModelId(modeldefinition.name, contextString, - modelDefinition.definition.settings.autoscope); - modeldefinition.clientModelName = modeldefinition.name; - if (ctx.IsNewInstance && ctx.options.upsertWithNewRecord) { - modeldefinition.filebased = false; - modeldefinition.variantOf = modeldefinition.name; - } - // check the validitiy of modeldefinition(like checking the validity of expressions attached to a model) - util.isModelDefinitionValid(modeldefinition, ctx.options, function checkModelDefinitionValidCb(err) { - // if model is not valid then pass the error forward and do not create the model - if (err && err.length > 0) { - return next(err); - } - if (!modeldefinition.filebased) { - if (!modeldefinition.plural && modeldefinition.name) { - modeldefinition.plural = createPlural(modeldefinition.name); - log.debug(ctx.options, 'Created plural ', modeldefinition.plural, 'for model', modeldefinition.name); - } - modeldefinition.clientPlural = modeldefinition.plural; - } - if (modeldefinition.variantOf) { - modeldefinition.base = modeldefinition.variantOf; - } - if (!modeldefinition.base) { - modeldefinition.base = 'BaseEntity'; - } - let baseModel = loopback.findModel(modeldefinition.base, ctx.options); - if (!baseModel) { - var err1 = new Error('Specified base (\'' + modeldefinition.base + '\') does not exist'); - err1.retriable = false; - return next(err1); - } - if (modeldefinition.variantOf) { - modeldefinition.plural = baseModel.pluralModelName; - modeldefinition.name = baseModel.modelName; - } - return mongoSpecificHandling(modeldefinition, ctx, next); - }); - }; - - function registerModel(modeldefinition, app, next) { - var options = { - fetchAllScopes: true, - bootContext: true - }; - util.createModel(app, modeldefinition, options, function () { - modelDefinition.events.emit('model-' + modeldefinition.name + '-available'); - // Find all child models and re-create them so that the new base properties - // are reflected in them - modelDefinition.find({ - where: { - base: modeldefinition.name - } - }, options, function (err, modeldefinitions) { - if (err) { - log.error(options, err); - return next(err); - } - if (modeldefinitions && modeldefinitions.length) { - modeldefinitions.forEach(function (md) { - util.createModel(app, md, options, function () { - log.debug(options, 'emitting event model available ', md.name); - modelDefinition.events.emit('model-' + md.name + '-available'); - }); - }); - } - next(); - }); - }); - } - - messaging.subscribe('RegisterModel', function (modelId) { - var options = { - fetchAllScopes: true, - ignoreAutoScope: true - }; - log.debug(options, 'RegisterModel ', modelId); - modelDefinition.findById(modelId, options, function (err, modelInstance) { - if (err) { - log.error(options, 'Error RegisterModel ', modelId, err); - } - registerModel(modelInstance, modelDefinition.app, function () { - log.debug(options, 'RegisterModel done ', modelId); - }); - }); - }); - - /** - * This After Save Hook for ModelDefinition is responsible for creating a model - * in Loopback after the definition is created using the create ModelDefinition API. - * @callback - * @param {object} ctx - The context object containing the model instance. - * @param {function} next - The function to be called for letting Loopback know that it can proceed with the next hook. - */ - var mdAfterSave = function mdAfterSave(ctx, next) { - log.debug(ctx.options, 'DEBUG: boot/db-models.js: ModelDefinition After save called.'); - var modeldefinition = ctx.instance || ctx.currentInstance; - - // when update is done using updateAll the changed data is present is - // ctx.data (type object), and ctx.where has the query. so the ctx.data is not - // a model constructor and has limited data, so to create the updated model in app - // find the record using id and use the updated instance to create the updated model in the app. - if (typeof modeldefinition === 'undefined') { - var where = ctx.where; - var id; - // the below logic is to find out id in where clause. - // if context-mixin is applied it where = {and} --> and is a array - // if context-mixin is not applied then where clause will be have id in where.id. - var forEachCb = function forEachCb(element) { - if (!Array.isArray(element)) { - if (element.id) { - id = element.id; - } - } - }; - for (var key in where) { - if (key === 'id') { - id = where.id; - } else if (Array.isArray(where[key])) { - where[key].forEach(forEachCb); - } - } - - - modelDefinition.findById(id, ctx.options, function dbModelsMdAfterSaveModelDefFindCb(err, modelInstance) { - if (err) { - log.warn(ctx.options, { - 'message': 'WARNING', - 'cause': err, - 'details': '' - }); - } - if (modelInstance) { - modeldefinition = modelInstance; - if (!modeldefinition.filebased) { - util.createModel(modelDefinition.app, modeldefinition, ctx.options, function dbModelsMdAfterSaveModelDefFindModelCreateCb() { - modelDefinition.events.emit('model-' + modeldefinition.name + '-available'); - doAutoUpdate(modeldefinition, ctx.options); - // Find all child models and re-create them so that the new base properties - // are reflected in them - modelDefinition.find({ - where: { - base: modeldefinition.name - } - }, ctx.options, - function dbModelsMdAfterSaveMdAfterSaveUtilCreateModelFindCb(err, modeldefinitions) { - if (err) { - next(); - log.warn(ctx.options, { - 'message': 'WARNING', - 'cause': err, - 'details': '' - }); - return; - } - if (modeldefinitions && modeldefinitions.length) { - modeldefinitions.forEach(function dbModelMdAfterSaveMdForEachFn(md) { - // For each Model defined in the DB which has the current model as base ... - util.createModel(modelDefinition.app, md, ctx.options, function dbModelMdAfterSaveMdForEachCreateModelCb() { - log.debug(ctx.options, 'emitting event model available ', md.name); - modelDefinition.events.emit('model-' + md.name + '-available'); - doAutoUpdate(md, ctx.options); - }); - }); - } - }); - }); - } - } - }); - } else if (!modeldefinition.filebased) { - util.createModel(modelDefinition.app, modeldefinition, ctx.options, function dbModelMdAfterSaveMdFileBasedCreateCb() { - log.debug(ctx.options, 'emitting event model available ', modeldefinition.name); - modelDefinition.events.emit('model-' + modeldefinition.name + '-available'); - doAutoUpdate(modeldefinition, ctx.options); - // Find all child models and re-create them so that the new base properties - // are reflected in them - modelDefinition.find({ - where: { - base: modeldefinition.name - } - }, ctx.options, function dbModelMdAfterSaveMdFileBasedCreateMdFindCb(err, modeldefinitions) { - if (err) { - next(); - log.warn(ctx.options, { - 'message': 'WARNING', - 'cause': err, - 'details': '' - }); - } - if (modeldefinitions && modeldefinitions.length) { - modeldefinitions.forEach(function dbModelMdAfterSaveMdFileBasedCreateMdFindCreateForEachFn(md) { - // For each Model defined in the DB which has the current model as base ... - util.createModel(modelDefinition.app, md, ctx.options, function dbModelMdAfterSaveMdFileBasedCreateMdFindCreateForEachCreateModelCb() { - log.debug(ctx.options, 'emitting event model available ', md.name); - modelDefinition.events.emit('model-' + md.name + '-available'); - doAutoUpdate(md, ctx.options); - }); - }); - } - }); - messaging.publish('RegisterModel', modeldefinition.id); - }); - } - next(); - }; - - /** - * This function finds the model and does check and do autoupdate - * - * @param {Object} modeldefinition Model Definition - * @param {Object} options Options - */ - function doAutoUpdate(modeldefinition, options) { - let model = loopback.findModel(modeldefinition.name, options); - util.checkAndDoAutoUpdate(model, options); - } - - /** - * This function returns the plural form of specified input word - * - * @param {string} name - The word whose plural is to be returned - * @return {string} The plural of the specified word - */ - function createPlural(name) { - return inflection.pluralize(name); - } - - /** - * This function checks if the specified model has any dependency on other models - * in the specified app. - * - * @param {object} app - Loopback's app object containing all models which need to be checked for dependency - * @param {string} modelname - The name of the model that needs to be checked if it has any dependency - * @return {boolean} true or false depending on whether if there is a dependency or not, respectively. - */ - /* function hasDependency(app, modelname) { - var result = false; - var allModels = app.models; - for (var key in allModels) { - if (allModels.hasOwnProperty(key)) { - var currentModel = null; - if (allModels.hasOwnProperty(key)) { - currentModel = allModels[key]; - } - if (!currentModel) { - continue; - } - var baseModelName = currentModel.definition && currentModel.definition.settings && currentModel.definition.settings.base; - if (!baseModelName) { - continue; - } - if (baseModelName === modelname && !currentModel.definition.settings._isDeleted) { - log.debug(log.defaultContext(), 'baseModelName exists and not deleted ', baseModelName, currentModel.definition.name); - result = true; - break; - } - } - } - return result; - } - */ - // Define 'after save' hook for the ModelDefinition model so - // that loopback Models can be created at runtime corresponding to - // the ModelDefinition instance created - modelDefinition.observe('after save', mdAfterSave); - - // Validate base before saving - modelDefinition.observe('before save', mdBeforeSave); - - function _isHiddenProperty(model, propName, options) { - var settings = model.definition.settings; - if (settings.hidden && settings.hidden.indexOf(propName) >= 0) { - return true; - } - if (options.skipSystemFields) { - if (propName === 'id' || propName === 'scope' || propName.startsWith('_')) { - return true; - } - } - return false; - } - - // generate model definition - function _extractMeta(model, options, allDefinitions) { - var properties = {}; - var associations = []; - Object.keys(model.definition.properties).forEach(function forEachPropertyCB(propName) { - if (!_isHiddenProperty(model, propName, options)) { - var propDetails = _.cloneDeep(model.definition.properties[propName]); - if (propDetails.evtype) { - propDetails.type = propDetails.evtype; - } - if (typeof propDetails.type === 'function') { - if (propDetails.type.name === 'ModelConstructor') { - /* Property value is composite model */ - if (propDetails.type.definition) { - associations.push(propDetails.type); - propDetails.modeltype = propDetails.type.definition.name; - propDetails.type = 'model'; - } - } else { - /* Property value is primitive like string, date, number, boolean etc. */ - propDetails.type = propDetails.type.name.toLowerCase(); - } - } else if (Array.isArray(propDetails.type)) { - /* type is an array */ - var itemType = propDetails.type[0]; - if (typeof itemType === 'function') { - /* Array of another model */ - if (itemType.name === 'ModelConstructor') { - associations.push(itemType); - propDetails.itemtype = 'model'; - propDetails.modeltype = itemType.definition.name; - } else { - /* Array of primitive */ - propDetails.itemtype = itemType.name.toLowerCase(); - } - } - propDetails.type = 'array'; - } - if (propDetails.refcodetype) { - associations.push(loopback.findModel(propDetails.refcodetype, options)); - } - if (propDetails.enumtype) { - var enumModel = model.app.models[propDetails.enumtype]; - if (enumModel) { - // enumtype is pointing to model - propDetails.listdata = enumModel.settings.enumList; - } else { - // enumtype is not pointing to model - log.error(options, 'error finding enumtype ', propDetails.enumtype); - } - } - properties[propName] = propDetails; - } - }); - - var relations = model.relations; - var modelDefn = { - id: model.definition.name, - base: model.base.modelName, - plural: model.pluralModelName, - resturl: config.restApiRoot + model.http.path, - properties: properties, - relations: relations - }; - - allDefinitions[model.definition.name] = modelDefn; - allDefinitions[model.clientModelName] = modelDefn; - - if (options.dependencies) { - Object.keys(relations).forEach(function relationsForEachKey(relationName) { - var related = relations[relationName].modelTo; - associations.push(related); - }); - - for (var i = 0; i < associations.length; i++) { - var associated = associations[i]; - if (associated) { - if (!allDefinitions[associated.definition.name]) { _extractMeta(associated, options, allDefinitions); } - } - } - } - } - - function _flattenMetadata(modelName, allModels) { - var flatProperties = {}; - - var modelDefnMeta = allModels[modelName] || { - properties: {} - }; - - for (var propName in modelDefnMeta.properties) { - if (propName !== 'id') { - var propObj = modelDefnMeta.properties[propName]; - - /* if type of this property is not present in all models, then it should be a primitive property.*/ - if (propObj.type === 'model') { - /** - * It is a composite type. We need sub-model's properties add thos multiple fields - * User{ - * address : Address - * } - * We flatten Address and add address.line1, address.city etc into our control-list. - */ - var subObj = _flattenMetadata(propObj.modeltype, allModels); - for (var subProp in subObj.properties) { - if (subObj.properties.hasOwnProperty(subProp)) { - flatProperties[propName + '.' + subProp] = subObj.properties[subProp]; - } - } - } else { - /* a primitive property*/ - flatProperties[propName] = propObj; - } - } - } - modelDefnMeta.properties = flatProperties; - // console.log('@@@@@@@@@@@@',modelDefnMeta,'@@@@@@@@@@'); - return modelDefnMeta; - } - - modelDefinition._extractMeta = _extractMeta; - modelDefinition.extractMeta = function extractMeta(modelName, options, callback) { - options = options || {}; - if (options.flatten) { - options.dependencies = true; - } - var model = loopback.findModel(modelName, options); - var result = {}; - if (model) { - var allDefinitions = {}; - _extractMeta(model, options, allDefinitions); - - /** - * If we are returning a different personalized model against the requested one, - * also make sure this is available under original requested name - */ - if (model.clientModelName !== modelName) { - allDefinitions[modelName] = allDefinitions[model.modelName]; - } - result = allDefinitions; - if (options.flatten) { - /** - * Inter-weave the embedded Model's field as sub-fields. - */ - result = _flattenMetadata(model.modelName, allDefinitions); - } - } - callback && callback(null, result); - return result; - }; - - modelDefinition.remoteMethod('extractMeta', { - description: 'Returns Model Meta Data', - accessType: 'READ', - accepts: [{ - arg: 'modelName', - type: 'string', - description: 'model name', - required: true, - http: { - source: 'path' - } - }], - http: { - verb: 'GET', - path: '/modelmeta/:modelName' - }, - returns: { - type: 'object', - root: true - } - }); -}; diff --git a/common/models/framework/model-rule.json b/common/models/framework/model-rule.json deleted file mode 100644 index fa72b87..0000000 --- a/common/models/framework/model-rule.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "ModelRule", - "base": "BaseEntity", - "strict": true, - "plural": "ModelRules", - "options": { - "validateUpsert": true, - "isFrameworkModel": true - }, - "properties": { - "modelName": { - "type": "string", - "required": true, - "unique": true - }, - "disabled": { - "type": "boolean", - "default": false - }, - "defaultRules": { - "type": ["string"], - "default": [] - }, - "validationRules": { - "type": ["string"], - "default": [] - }, - "isService" : { - "type" : "boolean", - "default": false - } - }, - "cacheable": false, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/pending-journal.js b/common/models/framework/pending-journal.js deleted file mode 100644 index f70fed2..0000000 --- a/common/models/framework/pending-journal.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('pening-journal'); - -module.exports = function (PendingJournal) { - var updatePending = function (ctx, instance, status, cb) { - var pendingModel = loopback.findModel('PendingJournal'); - - pendingModel.findById(instance.id, ctx.options, function (err, inst) { - if (err) { - cb(err); - } else { - inst.status = status; - inst.updateAttribute('status', status, ctx.options, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } - return cb(); - }); - } - }); - }; - - PendingJournal.observe('after save', function (ctx, next) { - var instance = ctx.instance; - - if (ctx.isNewInstance === true && instance.isFirstPending === true) { - ctx.instance.isFirstPending = false; - return next(new Error('journal pending saved')); - } - - if (instance.status !== 'pending') { - return next(); - } - var ctxRetry = JSON.parse(instance.savedCtx); - var journalModel = loopback.findModel(instance.journalName, ctxRetry); - var journalData = JSON.parse(instance.savedData); - journalData.fromPending = true; - - journalModel.create(journalData, ctxRetry, function (err, res) { - if (err) { - if (err.retriable === false) { - next(); - updatePending(ctx, instance, 'failed', function (err, res) { - if (err) { - return next(err); - } - return; - }); - } else { - return next(err); - } - } else { - next(); - updatePending(ctx, instance, 'success', function (err, res) { - if (err) { - return next(err); - } - return; - }); - } - }); - }); -}; diff --git a/common/models/framework/personalization-rule.json b/common/models/framework/personalization-rule.json deleted file mode 100644 index 41d059a..0000000 --- a/common/models/framework/personalization-rule.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "PersonalizationRule", - "base": "BaseEntity", - "plural": "PersonalizationRules", - "description": "Service Personalization metadata", - "idInjection": false, - "strict": true, - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization": false - }, - "properties": { - "ruleName": { - "type": "string" - }, - "disabled": { - "type": "boolean", - "default": false - }, - "modelName": { - "type": "string", - "required": true, - "unique": true, - "notin": [ - "PersonalizationRule" - ] - }, - "personalizationRule": { - "type": "object", - "required": true - } - }, - "cacheable": true, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/save-point.json b/common/models/framework/save-point.json deleted file mode 100644 index aa7a757..0000000 --- a/common/models/framework/save-point.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "SavePoint", - "plural": "SavePoints", - "base": "PersistedModel", - "description": "Represents a point in time where all after save observers finished successfully for current host", - "idInjection": true, - "properties": { - "timestamp": { - "type": "Date" - }, - "hostName": { - "type": "string" - }, - "hostUUID": { - "type": "string" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/state.js b/common/models/framework/state.js deleted file mode 100644 index 0fb7098..0000000 --- a/common/models/framework/state.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -module.exports = function State(state) { -}; - diff --git a/common/models/framework/state.json b/common/models/framework/state.json deleted file mode 100644 index c142340..0000000 --- a/common/models/framework/state.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "State", - "base": "BaseEntity", - "description": "holds transactional data on the actor itself", - "properties": { - "stateObj": { - "type": "object", - "required": true - }, - "lastUpdated": { - "type": "Date" - }, - "seqNum": { - "type": "Number", - "default": 0 - } - }, - "cacheable": true, - "disableInstanceCache": false, - "mixins": { - "CacheMixin": true, - "FailsafeObserverMixin": false - }, - "overridingMixins": { - "ModelValidations": false, - "DataPersonalizationMixin": false, - "HistoryMixin": false, - "IdempotentMixin": false, - "EvVersionMixin": false, - "FailsafeObserverMixin": false, - "BusinessRuleMixin": false, - "SoftDeleteMixin": true, - "AuditFieldsMixin": true, - "ExpressionAstPopulatorMixin": false, - "CryptoMixin": false, - "PropertyExpressionMixin": false - }, - "options": { - "instanceCacheSize": 200000, - "instanceCacheExpiration": 0, - "disableManualPersonalization": true - } -} diff --git a/common/models/framework/system-config.json b/common/models/framework/system-config.json deleted file mode 100644 index 4a4e450..0000000 --- a/common/models/framework/system-config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "SystemConfig", - "base": "BaseEntity", - "description": "Stores system config", - "properties": { - "key": { - "type": "string", - "required": true, - "unique": true - }, - "value": { - "type": "object" - } - } -} \ No newline at end of file diff --git a/common/models/framework/tenant.json b/common/models/framework/tenant.json deleted file mode 100644 index 750c7f0..0000000 --- a/common/models/framework/tenant.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "Tenant", - "base": "BaseEntity", - "description": "Stores tenant information", - "options": { - "validateUpsert": true - }, - "properties": { - "tenantId": { - "type": "string", - "required": true, - "index": true, - "unique": true, - "max": 30 - }, - "tenantName": { - "type": "string", - "required": true, - "index": true, - "unique": true, - "max": 30 - } - }, - "validations": [], - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/trusted-app.js b/common/models/framework/trusted-app.js deleted file mode 100644 index 3fb98cc..0000000 --- a/common/models/framework/trusted-app.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var jwt = require('jsonwebtoken'); -const loopback = require('loopback'); -const log = require('oe-logger')('trusted-app'); -var jwtUtil = require('../../../lib/jwt-token-util'); - - -module.exports = function TrustedApp(trustedApp) { - /** - * This function accepts username and password, - * and authenticates the user with service account set with trusted app - * - * - * @param {object} data - {"username":"", "password":"", "appId":""} - * @param {object} options - callcontext options - * @param {function} cb - callback/next function - * @returns {string} token - jwt token to use to use - */ - trustedApp.authenticate = function authenticateTrustedApp(data, options, cb) { - var self = this; - var error; - // cb = cb || utils.createPromiseCallback(); - // verify the trusted app is assigned with this username - if (!data.username || !data.password || !data.appId) { - error = new Error(); - error.message = 'username, password and appId; all three values are mandatory'; - error.statusCode = 400; - error.code = 'USERNAME_PASSWORD_REQUIRED'; - error.retriable = false; - return cb(error); - } - var where = { - 'where': { - 'and': [{ - 'username': data.username - }, - { - 'appId': data.appId - } - ] - } - }; - self.findOne(where, options, function fnFetchTrustedApp(err, app) { - if (err) { - log.info(options, err); - return cb(err); - } - - if (app && app.appId) { - log.debug(options, 'trusted app configured properly for ', data.appId, ' and username ', data.username); - // make login call - if (app) { - var baseUser = loopback.getModelByType('BaseUser'); - baseUser.login({ 'username': data.username, 'password': data.password }, options, function fnTrustedAppLoggedIn(err, user) { - if (err) { - log.error(options, err); - return cb(err); - } - - if (!user) { - log.debug(options, 'Associated service user not found for ', data.appId, ' and username ', data.username); - error = new Error(); - error.message = 'Trusted app not configured properly.'; - error.statusCode = 400; - error.code = 'TRUSTED_APP_AUTH_FAILED'; - error.retriable = false; - return cb(error); - } - // generate a jwt for trusted app - if (user && user.id) { - var jwtConfig = jwtUtil.getJWTConfig(); - var jwtOpts = {}; - var jwtData = {}; - jwtOpts.issuer = jwtConfig.issuer; - jwtOpts.audience = jwtConfig.audience; - // access token ttl set to jwt's expiry in seconds - jwtOpts.expiresIn = user.ttl; - jwtData.username = user.username; - jwtData.userId = user.userId; - jwtData.roles = user.roles; - jwtData.tenantId = user.tenantId; - jwtData.expiresIn = jwtOpts.expiresIn; - jwtData[jwtConfig.keyToVerify] = app.appId; - - jwt.sign(jwtData, jwtConfig.secretOrKey, jwtOpts, function jwtSignCb(err, token) { - if (err) { - log.error(options, 'Trusred app JWT signing error ', err); - log.debug(options, err); - return cb(err); - } - - cb(null, token); - }); - } - }); - } - } else { - log.debug(options, 'trusted app not configured properly for ', data.appId, ' and username ', data.username); - error = new Error(); - error.message = 'Trusted app not configured properly.'; - error.statusCode = 400; - error.code = 'TRUSTED_APP_ERROR'; - error.retriable = false; - return cb(error); - } - }); - }; - // accepts object with username, - trustedApp.remoteMethod('authenticate', { - description: 'authenticate a trusted app service account', - accepts: [{ arg: 'data', type: 'object', required: true, http: { source: 'body' } }], - http: { - verb: 'POST', - path: '/authenticate' - }, - returns: { - arg: 'token', - type: 'string', - root: true - } - }); -}; diff --git a/common/models/framework/trusted-app.json b/common/models/framework/trusted-app.json deleted file mode 100644 index c2ae1d7..0000000 --- a/common/models/framework/trusted-app.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "TrustedApp", - "base": "BaseEntity", - "strict": true, - "plural": "TrustedApps", - "description": "holds trusted application details", - "options": { - "validateUpsert": true, - "isFrameworkModel": true, - "queryCacheSize": 20000, - "queryCacheExpiration": 86400000 - }, - "properties": { - "appId": { - "type": "string", - "required": true - }, - "appName": { - "type": "string" - }, - "username": { - "type": "string" - }, - "publicKey": { - "type": "string" - }, - "expiry": { - "type": "number" - }, - "supportedRoles": { - "type": ["string"] - } - }, - "validations": [], - "relations": {}, - "acls": [{ - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "*", - "accessType": "*" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "authenticate" - }, - { - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW", - "property": "*", - "accessType": "*" - } - ], - "cacheable": true, - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/user-credential.json b/common/models/framework/user-credential.json deleted file mode 100644 index 7c35a0c..0000000 --- a/common/models/framework/user-credential.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "userCredential", - "plural": "userCredentials", - "base": "UserCredential", - "description": "This model is used to store user credential information", - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "BaseUser", - "foreignKey": "userId" - } - }, - "isFrameworkModel": true, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/user-identity.json b/common/models/framework/user-identity.json deleted file mode 100644 index db1663e..0000000 --- a/common/models/framework/user-identity.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "userIdentity", - "plural": "userIdentities", - "base": "UserIdentity", - "description": "This model is used to store user credential identity information from 3rd party", - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "BaseUser", - "foreignKey": "userId" - } - }, - "isFrameworkModel": true, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/framework/user-profile.json b/common/models/framework/user-profile.json deleted file mode 100644 index ab4b9da..0000000 --- a/common/models/framework/user-profile.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "UserProfile", - "base": "BaseEntity", - "idInjection": false, - "description": "Stores extended user information like firstname", - "options": { - "validateUpsert": true - }, - "properties": { - "firstName": { - "type": "string", - "required": true - }, - "lastName": { - "type": "string", - "required": true - }, - "userId": { - "type": "string", - "required": true - }, - "department": { - "type": "string", - "required": false - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/model-definition.js b/common/models/model-definition.js new file mode 100644 index 0000000..7834423 --- /dev/null +++ b/common/models/model-definition.js @@ -0,0 +1,44 @@ +/** + * + * �2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + +// Author : Atul +// This file implements before save/after save hook to create model when record is posted to ModelDefinition +// This file will ultimately create Model in oeCloud and raise event. +var loopback = require('loopback'); + +module.exports = (ModelDefinition) => { + ModelDefinition.observe('after save', (ctx, next) => { + var app = ModelDefinition.app; + var r = ctx.instance || ctx.currentInstance; + var instance = Object.assign({}, r); + var ds = app.dataSources.db; + if (r.dataSourceName && app.dataSources[r.dataSourceName]) { + ds = app.dataSources[r.dataSourceName]; + } + if (r.filebased) { + return next(); + } + var model = loopback.createModel(r); + ds.attach(model); + app.model(model); + + ModelDefinition.emit('model-' + r.modelName + '-available', { context: ctx, model: model }); + r = instance; + return next(); + }); + + ModelDefinition.observe('before save', (ctx, next) => { + var r = ctx.instance || ctx.currentInstance; + if (!r.mixins) { + r.mixins = {}; + } + if (!r.base) { + r.base = 'BaseEntity'; + } + return next(); + }); +}; diff --git a/common/models/framework/model-definition.json b/common/models/model-definition.json similarity index 59% rename from common/models/framework/model-definition.json rename to common/models/model-definition.json index 863dec2..532d131 100644 --- a/common/models/framework/model-definition.json +++ b/common/models/model-definition.json @@ -6,33 +6,17 @@ "description": "This model stores application models metadata", "options": { "validateUpsert": true, - "isFrameworkModel": true, - "disableManualPersonalization":false, - "queryCacheSize": 20000, - "queryCacheExpiration": 86400000 + "isFrameworkModel": true }, "properties": { "properties": { "type": "object" }, - "readonly": { - "type": "boolean", - "default": false - }, "name": { "type": "string", "required": true, "unique": "ignoreCase", - "max": 100, - "notin": [ - "Model", - "PersistedModel", - "User", - "AccessToken", - "ACL", - "RoleMapping", - "Role" - ], + "max": 200, "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$" }, "description": { @@ -42,17 +26,29 @@ "plural": { "type": "string", "unique": "ignoreCase", - "max": 105, + "max": 200, "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$" }, "base": { "type": "string", - "max": 100 + "max": 200 }, "strict": { "type": "boolean", "default": false }, + "forceId": { + "type": "boolean", + "default": false + }, + "replaceOnPUT": { + "type": "boolean", + "default": true + }, + "trackChanges": { + "type": "boolean", + "default": false + }, "public": { "type": "boolean", "default": true @@ -61,10 +57,17 @@ "type": "boolean", "default": false }, + "isFrameworkModel": { + "type": "boolean", + "default": false + }, "validateUpsert": { "type": "boolean", "default": false }, + "mongodb": { + "type": "string" + }, "validations": { "type": [ "object" @@ -85,90 +88,36 @@ "type": "boolean", "default": false }, - "compositeModels": { - "type": "object" - }, - "CompositeTransaction": { - "type": "boolean", - "default": false - }, "dataSourceName": { "type": "string", "default": "db", - "max": 30 - }, - "autoscope": { - "type": [ - "string" - ] - }, - "hierarchyScope": { - "type": [ - "string" - ] + "max": 200 }, "mixins": { "type": "object" }, - "mongodb": { - "type": "object" - }, - "variantOf": { - "type": "string" - }, - "upward": { - "type": "boolean", - "default": false - }, "options": { "type": "object" }, - "oeValidations": { - "type": "object" - }, - "enableDefaultUI": { - "type": "boolean", - "default": false, - "description": "This flag is for application use to enable ui component/routes etc. for this model" - }, - "enableRemoteProxy": { - "type": "boolean", - "default": true - }, - "clientModelName": { - "type": "string" - }, - "clientPlural": { - "type": "string" - }, - "modelId": { - "type": "string" - }, - "cacheable": { - "type": "boolean", - "default": false - }, - "disableInstanceCache": { - "type": "boolean", - "default": true - }, "hidden": { "type": [ "string" ] + }, + "protected": { + "type": [ + "string" + ] + }, + "createTime": { + "type": "timestamp" } }, "hidden": [ "filebased" ], - "cacheable": true, "validations": [], "relations": {}, "acls": [], - "methods": {}, - "enumList": { - "type": [ - "object" - ] - } -} \ No newline at end of file + "methods": {} +} diff --git a/common/models/framework/ref-code-base.js b/common/models/ref-code-base.js similarity index 100% rename from common/models/framework/ref-code-base.js rename to common/models/ref-code-base.js diff --git a/common/models/framework/ref-code-base.json b/common/models/ref-code-base.json similarity index 100% rename from common/models/framework/ref-code-base.json rename to common/models/ref-code-base.json diff --git a/common/models/ui/designer-element.json b/common/models/ui/designer-element.json deleted file mode 100644 index 0406612..0000000 --- a/common/models/ui/designer-element.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "DesignerElement", - "plural":"DesignerElements", - "base": "BaseEntity", - "idInjection": false, - "options": { - "disableManualPersonalization":false - }, - "properties": { - "name": { - "type": "string", - "required": true - }, - "tag": { - "type": "string" - }, - "icon": { - "type": "string" - }, - "description": { - "type": "string", - "required": true - }, - "content": { - "type": "string" - }, - "category": { - "type": "string", - "required": true - }, - "config":{ - "type" : "object" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/draft-data.json b/common/models/ui/draft-data.json deleted file mode 100644 index db08b61..0000000 --- a/common/models/ui/draft-data.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "DraftData", - "base": "UIBase", - "idInjection": true, - "description" : "Store temporary draft data for model forms", - "properties": { - "formName": { - "type": "string", - "required": true, - "description" : "name of UI component for which the data is drafted" - }, - "modelData":{ - "type": "object", - "description": "Draft data of the form model" - }, - "componentData":{ - "type":"object", - "description":"Data to be set on the component" - }, - "options":{ - "type":"object", - "description":"Parameters for custom filtering while fetching draft data" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/ui/field.js b/common/models/ui/field.js deleted file mode 100644 index 1ceafe1..0000000 --- a/common/models/ui/field.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores the field level metadata for UI. -* This data is auto-referenced by UIMetadata model during rendering phase based on model property-name. -* It can also be explicitly referenced. -* The model has following properties -*
    -* Property   |              Description
    -* -----------|-------------------------------
    -* `key`      | Field key
    -* `uitype`   | decides how this field will be rendered on UI (text, date, number, boolean etc.)
    -* `default`  | default value to be populated in UI on an empty form.
    -* `label`    | label to be displayed in UI.
    -* `required` | set to true if field should be mandatory in UI
    -* `disabled` | set to true if field should be disabled in UI
    -* `hidden`   | set to true if field should be hidden in UI
    -* `class`    | CSS classes to be applied on the rendered control
    -* `minlength`| Minimum length for a valid input (applicable to text input)
    -* `maxlength`| Maximum length for a valid input (applicable to text input)
    -* `min`      | Minimum value for a valid input (applicable to date and number input)
    -* `max`      | Maximum value for a valid input (applicable to date and number input)
    -* `precision`| Decimal precision to be applied in number formatting (applicable to number input)
    -* `pattern`  | Input should match to specified pattern
    -* `listdata` | Array of valid values. When specified, the field is rendered as a dropdown control
    -* `listurl`  | If specified, dropdown control can pull list of valid values by invoking this url.
    -* 
    -* -* @kind class -* @class Field -* @author Rohit Khode -*/ diff --git a/common/models/ui/field.json b/common/models/ui/field.json deleted file mode 100644 index 9712ea2..0000000 --- a/common/models/ui/field.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "name": "Field", - "base": "UIBase", - "idInjection": false, - "description": "Field level metadata for UI", - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "key": { - "type": "string", - "required": true, - "max": 100 - }, - "uitype": { - "type": "string", - "required": true, - "max": 100 - }, - "default": { - "type": "string", - "max": 100 - }, - "label": { - "type": "string", - "max": 100 - }, - "required": { - "type": "any" - }, - "disabled": { - "type": "any" - }, - "hidden": { - "type": "any" - }, - "class": { - "type": "string", - "max": 100 - }, - "minlength": { - "type": "number" - }, - "maxlength": { - "type": "number" - }, - "min": { - "type": "number" - }, - "max": { - "type": "number" - }, - "precision": { - "type": "any" - }, - "pattern": { - "type": "string" - }, - "listdata": { - "type": [ - "string" - ] - }, - "listid": { - "type": "string", - "max": 100 - }, - "listurl": { - "type": "string", - "max": 250 - }, - "bindto": { - "type": "string", - "max": 100 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/gridColumnConfig.js b/common/models/ui/gridColumnConfig.js deleted file mode 100644 index be64e1e..0000000 --- a/common/models/ui/gridColumnConfig.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc Contains a single column definition that is used in GridConfig -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
    FieldDescription
    keyThe string to be shown in column header.
    labelThe key of the row to get data from.
    typeThe type of the content that is shown in the column. For example date, timestamp, number, string.
    uiTypeThe input control that has to be used for inline editing.
    widthWidth of the column in `px`.
    minWidthMin Width of the column in `px`, by default grid level min width will be taken.
    sortSort order of the current column. Takes either `asc` or `desc`.
    firstToSort Whether to sort first by desc or asc, by default it is asc.
    formatter A custom formatting function which returns the value to show in the cell.
    renderer A custom rendering function which returns the element to show in the cell..
    href Takes an express styled path and shows the cell content as a `hyperlink` with the provided path. For example, href="/models/customer/:id".
    firstToSort Whether to sort first by desc or asc, by default it is asc.
    cellClassClass to apply on data table cell
    valueGetterA custom getter function which returns a value for the property specified in the `key`.
    cellClassRulesObject having class name to be applied as key and an expression to evaluate as value
    hiddenColumn will be hidden if it is set to true.
    -* @kind class -* @class GridColumnConfig -* @author Sasivarnan R -*/ diff --git a/common/models/ui/gridColumnConfig.json b/common/models/ui/gridColumnConfig.json deleted file mode 100644 index f20b09f..0000000 --- a/common/models/ui/gridColumnConfig.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "GridColumnConfig", - "base": "UIBase", - "idInjection": false, - "description": "Column metadata that is used in GridConfig", - "options": { - "validateUpsert": true - }, - "properties": { - "key": { - "type": "string", - "required": true, - "max": 30 - }, - "label": { - "type": "string" - }, - "type": { - "type": "string" - }, - "uiType": { - "type": "string" - }, - "width": { - "type": "number" - }, - "minWidth": { - "type": "number" - }, - "sort": { - "type": "string" - }, - "firstToSort": { - "type": "string" - }, - "formatter": { - "type": "string" - }, - "renderer": { - "type": "string" - }, - "href": { - "type": "string" - }, - "cellClass": { - "type": "string" - }, - "cellClassRules": { - "type": "string" - }, - "valueGetter": { - "type": "string" - }, - "hidden": { - "type": "boolean" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/gridColumnDefinition.js b/common/models/ui/gridColumnDefinition.js deleted file mode 100644 index 0a231e3..0000000 --- a/common/models/ui/gridColumnDefinition.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores reference to the field dictionary and if the column is visible or not -* -* -* -* -* -* -* -* -* -* -* -* -* -*
    FieldDescription
    keyThe name of the field, the details of the field will be fetched from field dictionary (Field model)
    visibleWhether the column will be visible or hidden and will be made visible on demand
    -* @kind class -* @class GridColumnDefinition -* @author RSR -*/ diff --git a/common/models/ui/gridColumnDefinition.json b/common/models/ui/gridColumnDefinition.json deleted file mode 100644 index 8ba1256..0000000 --- a/common/models/ui/gridColumnDefinition.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "GridColumnDefinition", - "base": "UIBase", - "idInjection": false, - "options": { - "validateUpsert": true - }, - "properties": { - "key": { - "type": "string", - "required": true, - "max":30 - }, - "visible": { - "type": "boolean" - } - }, - "validations": [], - "relations": {}, - "acls": [ - ], - "methods": {} -} diff --git a/common/models/ui/gridConfig.js b/common/models/ui/gridConfig.js deleted file mode 100644 index 878ba83..0000000 --- a/common/models/ui/gridConfig.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This Model is used to store Grid config used by ev-data-table -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
    FieldDescription
    codeUnique field using which a GridConfig entry is fetched
    labelLabel for the ev-data-table shown in the header of the grid
    editorFormUrlURL of the page to show when a row is added/edited.
    columns Array of column definitions to show in the table.
    -* @kind class -* @class GridConfig -* @author Sasivarnan R -*/ - -module.exports = function gridConfig(GridConfig) { - var camelCaseToLabel = function gridConfigCamelCaseToLabelFn(s) { - return s.split(/(?=[A-Z])/).map(function gridConfigCamelCaseToLabelMapFn(p) { - return p.charAt(0).toUpperCase() + p.slice(1); - }).join(' '); - }; - - GridConfig.config = function config(configCode, options, cb) { - var app = this.app; - var filter; - - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - if (!configCode) { - return cb({ - status: false, - messages: ['missing-model-name'] - }); - } - - filter = { - where: { - code: configCode - } - }; - - GridConfig.findOne(filter, options, function findAndBuildGridConfigCb(err, data) { - if (err) { - return cb(err); - } - - if (data) { - data.label = data.label || camelCaseToLabel(configCode); - return cb(null, data); - } - - app.models.ModelDefinition.extractMeta(configCode, options, function modelExtractMetaCb(err, allModelsInfo) { - if (err) { - return cb(err); - } - - var model = allModelsInfo[configCode]; - - if (!model) { - return cb({ - status: false, - messages: ['missing-model-definition'] - }); - } - - var properties = model.properties; - var config = {}; - var columns = []; - - Object.keys(properties).forEach(function prepareColumnsArray(prop) { - if (prop.charAt(0) !== '_' && prop.toLowerCase().indexOf('scope') === -1 && prop !== 'id') { - var property = properties[prop]; - var col = { - key: prop, - label: camelCaseToLabel(prop), - type: property.type - }; - columns.push(col); - } - }); - - config.code = configCode; - config.columns = columns; - config.label = camelCaseToLabel(configCode); - config.editorFormUrl = '/api/UIComponents/component/' + configCode.toLowerCase() + '-form.html'; - cb(null, config); - }); - }); - }; - - GridConfig.remoteMethod('config', { - description: 'Returns GridConfig of', - accessType: 'READ', - accepts: [{ - arg: 'configCode', - type: 'string', - description: 'config code', - required: true, - http: { - source: 'path' - } - }], - http: { - verb: 'GET', - path: '/config/:configCode' - }, - returns: { - type: 'object', - root: true - } - }); -}; diff --git a/common/models/ui/gridConfig.json b/common/models/ui/gridConfig.json deleted file mode 100644 index 3f72595..0000000 --- a/common/models/ui/gridConfig.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "GridConfig", - "base": "UIBase", - "plural": "GridConfigs", - "idInjection": true, - "description": "This model stores Grid config used by ev-data-table", - "options": { - "validateUpsert": true - }, - "properties": { - "code": { - "type": "string", - "max": 30 - }, - "label": { - "type": "string", - "max": 30 - }, - "editorFormUrl": { - "type": "string", - "max": 500 - } - }, - "validations": [], - "relations": { - "columnConfigs": { - "type": "embedsMany", - "model": "GridColumnConfig", - "property": "columns", - "options": { - "validate": true - } - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/gridMetaData.js b/common/models/ui/gridMetaData.js deleted file mode 100644 index 36d59c5..0000000 --- a/common/models/ui/gridMetaData.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores the metadata of how to render a grid. i.e. what fields to render and in what order. -* The following fields are available in this class. -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -* -*
    FieldDescription
    gridIdentifierThe name / identifier of the grid
    columnFieldsArray of GridColumnDefinition objects which defines the list of column data
    dialogMetaDataThe name of UIMetadata which is used for rendering the form for editing a row/ adding new row for the underlying object in grid
    dialogTemplateUrlThe template file used for rendering the form for editing a row/ adding new row for the underlying object in grid
    -* @kind class -* @class GridMetaData -* @author RSR -*/ - -module.exports = function GridMetadataFn(GridMetaData) { - var camelCaseToLabel = function gridMetadataCamelCaseToLabelFn(s) { - return s.split(/(?=[A-Z])/).map(function gridMetadataCamelCaseToLabelMapFn(p) { - return p.charAt(0).toUpperCase() + p.slice(1); - }).join(' '); - }; - - - var buildColDef = function buildGridColumDefinition(col, colName, visible) { - col.field = colName; - col.headerName = col.label || camelCaseToLabel(colName); - col.uitype = col.uitype || col.type; - if (col.uitype === 'number' && col.numericality === 'integer') { - col.uitype = 'integer'; - } - col.visible = typeof visible === 'undefined' ? true : visible; - col.filter = (col.type === 'string' && col.format) ? col.format : col.type; - delete col.type; - return col; - }; - - var prepareData = function gridMetadataPrepareDataFn(self, data, options, cb) { - var Field = self.app.models.Field; - var fields = data.columnFields.map(function gridMetadataFieldsMapFn(field) { - return field.key; - }); - - var filter = { - where: { - key: { - inq: fields - } - } - }; - Field.find(filter, options, function gridMetadataFieldFindCb(err, fieldData) { - if (err) { - cb(err, null); - } else { - var ret = {}; - ret.columnData = []; - for (var i = 0; i < data.columnFields.length; i++) { - var field = data.columnFields[i]; - for (var j = 0; j < fieldData.length; j++) { - if (fieldData[j].key === field.key) { - ret.columnData.push(buildColDef(fieldData[j], fieldData[j].fieldid || field.key, field.visible)); - break; - } - } - } - ret.dialogMetaData = data.dialogMetaData; - ret.dialogTemplateUrl = data.dialogTemplateUrl; - cb(null, ret); - } - }); - }; - - /** - * This function expands the grid metadata fields into the column properties - * @function - * @name render - * @param {string} id - System internally searches for a record in GridMetaData with gridIdentifier with this name, - * if not found, system assumes it is name of model and expands the field of the model. - * @memberof GridMetaData - * @param {object} req - request object - * @param {object} options - callcontext options - * @param {function} cb - callback function - */ - GridMetaData.render = function gridMetadataRenderFn(id, req, options, cb) { - var filter = { - where: { - gridIdentifier: id - } - }; - var self = this; - - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - GridMetaData.findOne(filter, options, function gridMetadataRenderFindCb(err, data) { - if (err) { - cb(err); - } - if (data) { - prepareData(self, data, options, cb); - } else { - // assume id is possibly the model-name - var modeltype = id; - self.app.models.ModelDefinition.extractMeta(modeltype, options, function gridMetadataRenderExtractMetaCb(err, allModelsInfo) { - if (err) { - cb(err); - } - var modelInfo = allModelsInfo[modeltype]; - if (modelInfo) { - data = { - 'gridIdentifier': modelInfo.id, - 'columnData': [], - 'dialogMetaData': modelInfo.id - }; - - var primitives = ['string', 'text', 'date', 'number', 'boolean']; - var rawProperties = modelInfo.properties; - - for (var prop in rawProperties) { - if (rawProperties.hasOwnProperty(prop)) { - // for internal fields starting with underscore _ , do not show in grid - if (prop.match(/^_|\._/) || prop.toLowerCase() === 'scope') { - continue; - } - var details = rawProperties[prop]; - if (primitives.indexOf(details.type) >= 0) { - data.columnData.push(buildColDef(details, prop)); - } - } - } - - cb(null, data); - } else { - cb({ - message: 'Grid ' + id + ' not found' - }, null); - } - }); - } - }); - }; - - GridMetaData.remoteMethod( - 'render', { - returns: [{ - type: 'object', - root: true, - description: 'return value' - }], - accepts: [{ - arg: 'id', - type: 'string', - http: { - source: 'path' - } - }, { - arg: 'req', - type: 'object', - http: { - source: 'req' - } - }], - http: { - path: '/:id/render', - verb: 'get' - } - } - ); -}; diff --git a/common/models/ui/gridMetaData.json b/common/models/ui/gridMetaData.json deleted file mode 100644 index a42a67b..0000000 --- a/common/models/ui/gridMetaData.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "GridMetaData", - "base": "UIBase", - "plural": "GridMetaData", - "description": "This model stores the metadata of grid", - "strict": false, - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": { - "gridIdentifier": { - "type": "string", - "max": 30 - }, - "columnFields": [ - "object" - ], - "dialogMetaData": { - "type": "string", - "max": 30 - }, - "dialogTemplateUrl": { - "type": "string", - "max": 500 - } - }, - "validations": [], - "relations": { - "columnFields": { - "type": "embedsMany", - "model": "GridColumnDefinition", - "property": "columnFields", - "options": { - "validate": true - } - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/literal.js b/common/models/ui/literal.js deleted file mode 100644 index 87ee44e..0000000 --- a/common/models/ui/literal.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores the literal translations generally (but not limited to) scoped for `locale`. -* The model has following properties -* Property | Description -* ---------|------------------------------- -* `key` | literal key -* `value` | translation value -* -* A custom remote route Literals/render/* returns all the scope applicable records as hash-map (as opposed to array of records) where `key` becomes the hash-map key as well. -* Following two records : -* [ -* {key: "total", "value": "Total"}, -* {key: "username", "value": "User Name"} -* ] -* -* are returned as -* { -* "total": { -* "message": "Total" -* }, -* "username": { -* "message": "User Name" -* } -* } -* -* @kind class -* @class Literal -* @author Rohit Khode -*/ - -module.exports = function Literal(Literal) { - var placeholderRegex = /\$\w+\$/g; - var prepareAndSendData = function prepareAndSendData(data, cb) { - var response = {}; - for (var i = 0; data && i < data.length; i++) { - var item = data[i]; - response[item.key] = { - message: item.value - }; - - var phNames = item.placeholders; - var placeholders = {}; - var idx = 0; - if (phNames) { - for (idx = 0; idx < phNames.length; idx++) { - var ph = phNames[idx]; - placeholders[ph] = { content: '$' + (idx + 1) }; - } - response[item.key].placeholders = placeholders; - } else { - var matches = item.value.match(placeholderRegex); - if (matches) { - for (idx = 0; idx < matches.length; idx++) { - var match = matches[idx]; - placeholders[match.substr(1, match.length - 2)] = { content: '$' + (idx + 1) }; - } - response[item.key].placeholders = placeholders; - } - } - } - cb(null, response); - }; - - /** - * Custom remote method to fetch set of Literals as hash-map. - * @param {string} locale - file name for locale - * @param {object} req - request - * @param {object} options - callcontext options - * @param {function} cb - callback function - */ - Literal.getLocaleData = function getLocaleData(locale, req, options, cb) { - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - if (locale && locale.endsWith('.json')) { - locale = locale.substring(0, locale.length - 5); - } - - var filter = {}; - // set the locale in context for scope-query - options.ctx = options.ctx || {}; - options.ctx.lang = locale; - Literal.find(filter, options, function literalFindCb(err, data) { - if (err) { - cb(err); - } - if (!data) { - data = []; - } - prepareAndSendData(data, cb); - }); - }; - - Literal.remoteMethod( - 'getLocaleData', { - returns: [{ - type: 'object', - root: true, - description: 'return value' - }], - accepts: [{ - arg: 'locale', - type: 'string', - http: { - source: 'path' - } - }, - { - arg: 'req', - type: 'object', - http: { - source: 'req' - } - }], - http: { - path: '/render/:locale', - verb: 'get' - } - } - ); -}; diff --git a/common/models/ui/literal.json b/common/models/ui/literal.json deleted file mode 100644 index b0e7c01..0000000 --- a/common/models/ui/literal.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Literal", - "base": "UIBase", - "plural": "Literals", - "description": "This model stores the literal translations", - "properties": { - "key": { - "type": "string", - "unique": true, - "max": 80 - }, - "value": { - "type": "string", - "max": 250 - }, - "placeholders": { - "type": [ - "string" - ] - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": [], - "unique": [ - "key" - ] -} \ No newline at end of file diff --git a/common/models/ui/modelView.js b/common/models/ui/modelView.js deleted file mode 100644 index 8549d01..0000000 --- a/common/models/ui/modelView.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc One can save a view of related models in Designer tool. -* ModelView model is used to save such views. -* Property | Description -* ---------|------------------------------- -* `Name` | name of the view(given by user). -* `Models` | id of all models present in the view. -* -* @kind class -* @class ModelView -* @author Sourav Kumar -*/ diff --git a/common/models/ui/modelView.json b/common/models/ui/modelView.json deleted file mode 100644 index 4dc3216..0000000 --- a/common/models/ui/modelView.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "ModelView", - "plural": "ModelViews", - "base": "BaseEntity", - "idInjection": true, - "description" : "Used in designer tool to save views of models", - "options": { - "validateUpsert": true, - "disableManualPersonalization":false - }, - "properties": { - "name": { - "type": "string", - "unique": true, - "max":30 - }, - "models": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "validations": [], - "mixins": { - "DataPersonalizationMixin": true - }, - "isFrameworkModel": true, - "autoscope": ["tenantId"], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/ui/navigationLink.js b/common/models/ui/navigationLink.js deleted file mode 100644 index 8261863..0000000 --- a/common/models/ui/navigationLink.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores the data for navigation-links in UI -* The model has following properties -*
    -* Property   |              Description
    -* -----------|-------------------------------
    -* `name`     | link name
    -* `url`      | url to navigate to
    -* `label`    | display label for navigation link. This can also be 'Literal' key.
    -* `icon`     | If specified, an icon is displayed next to label
    -* `topLevel` | Boolean value that specified if this link is a root level link or child of another link
    -* `parent`   | Name of the parent link. UI components can use this hierarchical data to navigate progressively or display a tree structure of navigation link.
    -* 
    -* -* @kind class -* @class NavigationLink -* @author Rohit Khode -*/ diff --git a/common/models/ui/navigationLink.json b/common/models/ui/navigationLink.json deleted file mode 100644 index a637b85..0000000 --- a/common/models/ui/navigationLink.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "NavigationLink", - "base": "UIBase", - "idInjection": false, - "description": "Configuration for navigation-links in UI", - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "name": { - "type": "string", - "required": true, - "max": 80, - "unique": { - "scopedTo": [ - "group" - ] - } - }, - "url": { - "type": "string", - "max": 250 - }, - "label": { - "type": "string", - "required": true, - "max": 80 - }, - "icon": { - "type": "string", - "required": false, - "max": 50 - }, - "group": { - "type": "string", - "max": 50 - }, - "topLevel": { - "type": "boolean", - "default": true - }, - "sequence": { - "type": "number", - "numericality": "integer", - "default": 9999 - } - }, - "validations": [], - "relations": { - "children": { - "type": "hasMany", - "model": "NavigationLink", - "foreignKey": "parent", - "primaryKey": "name" - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/type-mapping.json b/common/models/ui/type-mapping.json deleted file mode 100644 index 8400831..0000000 --- a/common/models/ui/type-mapping.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "TypeMapping", - "base": "BaseEntity", - "idInjection": false, - "description": "Type configuration or mapping to UI Type", - "options": { - "validateUpsert": true, - "disableManualPersonalization": false - }, - "properties": { - "type": { - "type": "string", - "required": true - }, - "uiType": { - "type": "string", - "required": true - }, - "attributes": { - "type": [ - "UIAttribute" - ] - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/ui-attribute.json b/common/models/ui/ui-attribute.json deleted file mode 100644 index c3b51c0..0000000 --- a/common/models/ui/ui-attribute.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "UIAttribute", - "base": "Model", - "idInjection": false, - "options": { - "validateUpsert": true - }, - "properties": { - "name": { - "type": "string", - "required": true, - "max":30 - }, - "value":{ - "type": "any", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [ - ], - "methods": {} -} diff --git a/common/models/ui/ui-base.js b/common/models/ui/ui-base.js deleted file mode 100644 index 871c5bf..0000000 --- a/common/models/ui/ui-base.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This Model is used as a Base Model For all UI Models. -* -* @kind class -* @class UIBase -* @author Praveen Gulati -*/ diff --git a/common/models/ui/ui-base.json b/common/models/ui/ui-base.json deleted file mode 100644 index f429f24..0000000 --- a/common/models/ui/ui-base.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "UIBase", - "plural": "", - "base": "BaseEntity", - "description" : "This is base Model for all UI related models", - "idInjection": false, - "options": { - "validateUpsert": true, - "disableManualPersonalization":false - }, - "validations": [], - "relations": {}, - "acls": [ - ], - "hidden": [ - "tenantId" - ], - "methods": {} -} - diff --git a/common/models/ui/ui-component.js b/common/models/ui/ui-component.js deleted file mode 100644 index 2e3680f..0000000 --- a/common/models/ui/ui-component.js +++ /dev/null @@ -1,734 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * - * @classdesc This Model is used by Meta Polymer - * to inject Element attributes in a Polymer Component - * @kind class - * @class UIElement - * @author Praveen Kumar Gulati - */ -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('UIComponent'); -var loopback = require('loopback'); -var fs = require('fs'); -var path = require('path'); -var glob = require('glob'); - -module.exports = function uiComponent(UIComponent) { - var templateMap = null; - function loadTemplate(template, app, options, callback) { - app.models.AppConfig.findOne({}, options, function AppConfigFindOneCb(err, data) { - if (err) { - callback(err); - return; - } - if (!templateMap) { - templateMap = generateTemplateMap(app); - } - var templatePath = templateMap[template]; - - function fullSearch(template, callback) { - glob(app.locals.apphome + '/../**/' + template, function getClientTemplate(err2, files) { - if (!err2 && files && files.length > 0) { - templatePath = files[0]; - fs.readFile(templatePath, function read(err3, data2) { - if (err3) { - callback(err3, ''); - } else { - templateMap[template] = templatePath; - callback(err3, data2.toString()); - } - }); - } else { - var error = new Error(); - error.message = 'Template ' + template + ' not found'; - error.code = 'TEMPLATE_TYPE_MISSING'; - error.statusCode = 422; - callback(error, ''); - } - }); - } - - - if (typeof templatePath === 'string') { - fs.readFile(templatePath, function read(err, data) { - if (err) { - fullSearch(template, callback); - } else { - callback(err, data.toString()); - } - }); - } else { - fullSearch(template, callback); - } - }); - } - - function mergeAsHTML(html, response, callback) { - var out = '\n'; - out += html; - callback(null, out); - } - - function _getElements(componentName, response, options, done) { - var UIElement = loopback.getModel('UIElement'); - var elementsWhere = { - where: { - component: componentName - } - }; - var elements = {}; - UIElement.find(elementsWhere, options, function uiElementFindCb(err, dbelements) { - dbelements.forEach(function dbElementsForEach(e) { - var elementData = { - label: e.label, - textContent: e.textContent, - uitype: e.uitype - }; - e.attributes && e.attributes.forEach(function attributesFetch(att) { - elementData[att.name] = att.value; - }); - elements[e.field] = elementData; - }); - done(err, elements); - }); - } - - function defaultComponent(modelName, model, templateType) { - var templateName = 'default-' + templateType + '.html'; - var rec = { - name: modelName.toLowerCase() + '-' + templateType, - modelName: modelName, - templateName: templateName - }; - if (templateType === 'list') { - rec.autoInjectFields = false; - rec.gridConfig = {}; - rec.gridConfig.modelGrid = []; - Object.keys(model.definition.rawProperties).forEach(function configFetch(key) { - if (!(key.startsWith('_') || key === 'scope' || key === 'id') && model.definition.rawProperties[key].required) { - rec.gridConfig.modelGrid.push(key); - } - }); - - if (rec.gridConfig.modelGrid.length < 4) { - Object.keys(model.definition.rawProperties).forEach(function removeScopeFilter(key) { - if (key.startsWith('_') || key === 'scope' || key === 'id' || model.definition.rawProperties[key].required) { - return; - } - if (rec.gridConfig.modelGrid.length < 5) { - rec.gridConfig.modelGrid.push(key); - } - }); - } - } else if (templateType === 'form') { - // add autoInjectFields = true to render the form if templateType is form. - rec.autoInjectFields = true; - } - return new UIComponent(rec); - } - - UIComponent.prototype.generateComponent = function generateComponent(fetchAsHtml, options, callback) { - var component = this; - var componentName = component.name; - var tasks = []; - var response = { - componentName: componentName, - elements: {}, - fields: {} - }; - - var html = ''; - - tasks.push(function pushCb(done) { - _getElements(componentName, response, options, function getElementsCb(err, elements) { - response.elements = elements; - done(err); - }); - }); - - response.modelName = component.modelName || ''; - response.modelAlias = component.modelAlias || (component.modelName ? component.modelName.toLowerCase() : 'vm'); - response.fields = component.fields; - response.container = component.container; - response.componentData = component.componentData; - - if (fetchAsHtml && component.content) { - response.content = component.content.replace(/<\/script>/g, '<\\/script>'); - } - - response.metadata = {}; - response.autoInjectFields = component.autoInjectFields; - response.excludeFields = component.excludeFields; - response.resturl = component.resturl; - response.geturl = component.geturl; - response.posturl = component.posturl; - response.puturl = component.puturl; - response.deleteurl = component.deleteurl; - response.options = component.options; - response.polymerConfig = component.polymerConfig; - response.gridConfig = component.gridConfig; - response.oeValidations = component.oeValidations; - - if (fetchAsHtml) { - if (component.filePath) { - tasks.push(function pushCb(done) { - var fp = path.join(UIComponent.app.locals.apphome, component.filePath); - fs.readFile(fp, function read(err, data) { - if (!err) { - html = data.toString(); - } - done(err); - }); - }); - } else if (component.templateName && component.modelName) { - tasks.push(function pushCb(done) { - var modelAlias = component.modelAlias || (component.modelName ? component.modelName.toLowerCase() : 'vm'); - loadTemplate(component.templateName, UIComponent.app, options, function loadTemplateCb(err, template) { - html = replacePlaceHolders(UIComponent.app, componentName, modelAlias, template); - done(err); - }); - }); - } else if (component.templateName) { - tasks.push(function pushCb(done) { - loadTemplate(component.templateName, UIComponent.app, options, function loadTemplateCb(err, template) { - html = template.replace(/:componentName/g, componentName); - done(err); - }); - }); - } else { - html = response.content; - response.content = ''; - } - } - - if (component.modelName) { - tasks.push(function pushCb(done) { - var metaconfig = {}; - UIComponent._modelmeta(component.modelName, metaconfig, options, function modelsMetaCbFn(err, meta) { - if (err) { - done(err); - } - response.metadata = meta.metadata; - done(); - }); - }); - } - - async.parallel(tasks, function finalMergeTask(err, results) { - if (err) { - callback(err); - } else if (fetchAsHtml) { - if (component.importUrls) { - var importLinks = component.importUrls.map(function createLinks(importUrl) { - return ''; - }); - html = importLinks.join('\n') + '\n' + html; - } - mergeAsHTML(html, response, callback); - } else { - callback(null, response); - } - }); - }; - - // name can have .html component name without - // componentName without . - UIComponent._createResponse = function createResponse(fetchAsHtml, name, options, callback) { - var dotIndex = name.lastIndexOf('.') || name.length; - var componentName = dotIndex === -1 ? name : name.substring(0, dotIndex); - var where = { - where: { - name: componentName - } - }; - - - // Prefer find and results[0] instead of findOne - // so that data-personalization is applied effectively. - UIComponent.find(where, options, function findOneCb(err, results) { - if (err) { - log.error(options, 'Error ', err); - return callback(err, null); - } - var component = results ? results[0] : null; - if (!component) { - var modelAndType = componentName.split('-'); - var modelName = UIComponent.app.locals.modelNames[modelAndType[0]]; - var model = loopback.findModel(modelName, options); - var templateType = modelAndType[1]; - if (fetchAsHtml) { - // ex: literal-form Model = modelAndType[0] Type = modelAndType[1] - if (model && templateType) { - component = defaultComponent(modelName, model, templateType); - } else { - var error = new Error(); - if (!model) { - error.message = 'Model Not Found'; - error.code = 'MODEL_NOT_FOUND'; - error.statusCode = 404; - } else if (!templateType) { - error.message = 'Tempalte type is undefined'; - error.code = 'TEMPLATE_TYPE_UNDEFINED'; - error.statusCode = 422; - } else { - error.message = 'Unknown template type ' + modelAndType[1] + ', should be form or list'; - error.code = 'TEMPLATE_TYPE_UNKNOWN'; - error.statusCode = 422; - } - error.retriable = false; - return callback(error, null); - } - } else { - var response = { - componentName: componentName, - modelName: modelName, - elements: {}, - fields: {} - }; - _getElements(componentName, response, options, function getElementsCb(err, elements) { - if (err) { - return callback(err); - } - response.elements = elements; - callback(null, response); - }); - return; - } - } - component.generateComponent(fetchAsHtml, options, callback); - }); - }; - - // name can be in model name in lower case also - UIComponent._modelmeta = function meta(modelName, metaoptions, options, callback) { - if (typeof callback === 'undefined' && typeof options === 'undefined' && typeof metaoptions === 'function') { - callback = metaoptions; - options = {}; - metaoptions = {}; - } else if (typeof callback === 'undefined' && typeof options === 'function') { - callback = options; - options = {}; - } - metaoptions = metaoptions || {}; - var app = this.app; - var response = {}; - response.modelName = modelName; - response.metadata = {}; - options.flatten = typeof metaoptions.flatten === 'undefined' ? false : metaoptions.flatten; - options.dependencies = typeof metaoptions.dependencies === 'undefined' ? true : options.dependencies; - options.skipSystemFields = true; - var model; - app.models.ModelDefinition.extractMeta(modelName, options, function extractMetaCb(err, allmodels) { - if (err) { - callback(err); - } - response.metadata = {}; - response.metadata.models = {}; - var metadata = response.metadata; - var props; - - if (options.flatten) { - metadata.resturl = allmodels.resturl; - metadata.models[modelName] = {}; - metadata.models[modelName].resturl = allmodels.resturl; - props = allmodels.properties || {}; - model = allmodels; - } else { - Object.keys(allmodels).forEach(function extractRefModels(key) { - metadata.models[key] = {}; - var refmodel = metadata.models[key]; - refmodel.resturl = allmodels[key].resturl; - refmodel.properties = allmodels[key].properties; - // TODO we can copy more properties here - }); - model = allmodels[modelName] || {}; - props = model.properties || {}; - metadata.resturl = model.resturl; - // allmodels will not be sent by uicomponent/component - response.allmodels = allmodels; - } - - var subtasks = []; - - Object.keys(props).forEach(function extractPropData(fieldId) { - var field = props[fieldId]; - field.type = field.type || 'string'; - if (field.enumtype || field.refcodetype) { - field.type = 'combo'; - field.displayproperty = 'description'; - field.valueproperty = 'code'; - } else if (field.in) { - field.type = 'combo'; - field.listdata = field.in; - } else if (field.type === 'array') { - if (field.itemtype === 'model') { - field.type = 'grid'; - if (field.modeltype !== modelName && allmodels[field.modeltype]) { - field.subModelMeta = allmodels[field.modeltype].properties; - } - } else { - field.type = 'tags'; - } - } - if (field.refcodetype) { - var refCodeType = field.refcodetype; - subtasks.push(function subTaskPushCb(fetched) { - var refCodeModel = loopback.findModel(refCodeType, options); - if (refCodeModel) { - refCodeModel.find({}, options, function findCb(err, resp) { - if (!err) { - field.listdata = resp; - field.displayproperty = 'description'; - field.valueproperty = 'code'; - } - fetched(err); - }); - } else { - log.error('Invalid refCode model ', refCodeType); - } - }); - } - if (field.validateWhen) { - Object.keys(field.validateWhen).forEach(function (validationRule) { - delete field[validationRule]; - }); - delete field.validateWhen; - } - }); - - var relations = model.relations || {}; - Object.keys(relations).forEach(function extractRelatedModelMeta(relationName) { - var relation = relations[relationName]; - var modelTo = allmodels[relation.modelTo.modelName] || {}; - var fieldId = relation.keyFrom; - var fmeta; - // by default hasMany will not be grid - // however we will support sending meta data - // if it is one of the fields in gridConfig - if (relation.type === 'belongsTo') { - props[fieldId] = props[fieldId] || {}; - fmeta = props[fieldId]; - // unable to get additional properties added in model for relation - // so, add specific model logic instead. - if (modelTo.id === 'DocumentData') { - fmeta.type = 'documentdata'; - fmeta.relationName = relation.name; - } else { - fmeta.type = 'typeahead'; - fmeta.valueproperty = relation.keyTo; - // assume 'name' ?? - var displayProperty = (modelTo.base === 'RefCodeBase' ? 'description' : 'name'); - fmeta.displayproperty = displayProperty; - fmeta.resturl = modelTo.resturl; - fmeta.searchurl = fmeta.resturl + '?filter[where][' + displayProperty + '][regexp]=/^SEARCH_STRING/i&filter[limit]=5'; - fmeta.dataurl = fmeta.resturl + '/VALUE_STRING'; - } - } else if (relation.type === 'embedsMany') { - props[fieldId] = props[fieldId] || {}; - fmeta = props[fieldId]; - fmeta.type = 'grid'; - fmeta.subModelMeta = modelTo.properties; - fmeta.modeltype = modelTo.id; - } else if (relation.type === 'embedsOne' || relation.type === 'hasOne') { - fieldId = props[fieldId] ? fieldId : relationName; - props[fieldId] = props[fieldId] || {}; - fmeta = props[fieldId]; - fmeta.type = 'model'; - fmeta.modeltype = modelTo.id; - } - }); - - response.metadata.properties = props; - - async.parallel(subtasks, function subTaskFinalCb(err, results) { - if (err) { - callback(err); - } - callback(null, response); - }); - }); - }; - - function replacePlaceHolders(app, componentName, modelAlias, template) { - template = template.replace(/:modelAlias/g, modelAlias); - var modelName = app.locals.modelNames[modelAlias] || modelAlias; - var modelObj = app.models[modelName] || {}; - var pluralName = modelObj.pluralModelName || modelName + 's'; - template = template.replace(/:componentName/g, componentName); - template = template.replace(/:modelName/g, modelName); - template = template.replace(/:modelAlias/g, modelAlias); - template = template.replace(/:plural/g, pluralName); - return template; - } - - function generateTemplateMap(app) { - var designerName = 'oe-studio'; - var designer = app.get('designer'); - if (!designer.templatePath || designer.templatePath.length === 0) { - designer.templatePath = ['client/bower_components/' + designerName + '/templates']; - } - var templatesData = {}; - designer.templatePath.forEach(function templatePathForEach(tPath) { - if (fs.existsSync(tPath)) { - var templateFiles = fs.readdirSync(tPath); - templateFiles.forEach(function templateFilesForEach(fileName) { - if (fileName.endsWith('.html')) { - templatesData[fileName] = path.join(tPath, fileName); - } - }); - } - }); - return templatesData; - } - - - UIComponent.component = function fetchComponent(name, options, callback) { - var fetchAsHtml = true; - UIComponent._createResponse(fetchAsHtml, name, options, callback); - }; - - UIComponent.modelmeta = function fetchModelMeta(name, options, callback) { - var fetchAsHtml = false; - UIComponent._createResponse(fetchAsHtml, name, options, callback); - }; - - UIComponent.simulate = function simulateComponent(data, options, callback) { - var component = new UIComponent(data); - var fetchAsHtml = true; - component.generateComponent(fetchAsHtml, options, callback); - }; - - UIComponent.configure = function configure(modelList, options, callback) { - if (typeof callback === 'undefined' && typeof options === 'function') { - callback = options; - options = {}; - } - - if (!Array.isArray(modelList)) { - modelList = []; - } - - var temp = {}; - if (modelList.length > 0 && modelList[0] === '*') { - modelList = []; - Object.keys(UIComponent.app.models).forEach(function modelsKeysForEach(modelName) { - temp[modelName.toLowerCase()] = modelName; - }); - Object.keys(temp).forEach(function createModelList(modelName) { - modelList.push(modelName); - }); - } - - var createComponent = function createComponent(modelName, model, templateType, options, callback) { - var templateName = 'default-' + templateType + '.html'; - var name = modelName.toLowerCase() + '-' + templateType; - var rec = { - name: name, - modelName: modelName, - templateName: templateName - }; - if (templateType === 'list') { - rec.autoInjectFields = false; - rec.gridConfig = {}; - rec.gridConfig.modelGrid = []; - Object.keys(model.definition.rawProperties).forEach(function extractGridMeta(key) { - if (!(key.startsWith('_') || key === 'scope' || key === 'id') && model.definition.rawProperties[key].required) { - rec.gridConfig.modelGrid.push(key); - } - }); - - if (rec.gridConfig.modelGrid.length < 4) { - Object.keys(model.definition.rawProperties).forEach(function removeScopeFilter(key) { - if (key.startsWith('_') || key === 'scope' || key === 'id' || model.definition.rawProperties[key].required) { - return; - } - if (rec.gridConfig.modelGrid.length < 5) { - rec.gridConfig.modelGrid.push(key); - } - }); - } - } - UIComponent.create(rec, options, function createCb(err, component) { - if (err) log.error(options, 'error creating ui component ', name, JSON.stringify(err)); - callback(err, component); - }); - }; - - var tasks = []; - modelList.forEach(function defaultUICreator(modelName) { - modelName = UIComponent.app.locals.modelNames[modelName]; - var model = loopback.findModel(modelName, options); - if (model && model.shared) { - var templates = ['form', 'list']; - templates.forEach(function templateBasedComponentCreator(templateType) { - tasks.push(function pushCb(done) { - var componentName = modelName.toLowerCase() + '-' + templateType; - var filter = { - where: { - name: componentName - } - }; - // Prefer find and results[0] over findOne - // for data-personalization to work effectively - UIComponent.find(filter, options, function findComponentCb(err, results) { - if (err) { - done(err); - } - if (results && results[0]) { - done(null, results[0]); - } else { - createComponent(modelName, model, templateType, options, done); - } - }); - }); - }); - } - }); - - async.series(tasks, function seriesCb(err, results) { - if (err) { - callback(err); - } - callback(null, results); - }); - }; - - UIComponent.remoteMethod('configure', { - description: 'Configures Default UI for given list of model names', - accessType: 'WRITE', - accepts: [{ - arg: 'modelList', - type: 'Object', - description: 'list of model names', - required: true, - http: { - source: 'query' - } - }], - http: { - verb: 'POST', - path: '/configure' - }, - returns: { - type: 'object', - root: true - } - }); - - UIComponent.remoteMethod('simulate', { - description: 'returns an polymer html from given data', - accessType: 'READ', - accepts: { - arg: 'data', - type: 'object', - description: 'Model instance data', - http: { - source: 'body' - } - }, - http: { - verb: 'post', - path: '/simulate' - }, - returns: [{ - arg: 'body', - type: 'string', - root: true - }, - { - arg: 'Content-Type', - type: 'string', - http: { - target: 'header' - } - } - ] - }); - - UIComponent.remoteMethod('modelmeta', { - description: 'Returns Model Meta Data', - accessType: 'READ', - accepts: [{ - arg: 'modelName', - type: 'string', - description: 'model name', - required: true, - http: { - source: 'path' - } - }], - http: { - verb: 'GET', - path: '/modelmeta/:modelName' - }, - returns: { - type: 'object', - root: true - } - }); - - UIComponent.remoteMethod('component', { - description: 'Returns UI component and additional data..', - accessType: 'READ', - accepts: [{ - arg: 'name', - type: 'string', - description: 'name', - required: true, - http: { - source: 'path' - } - }], - http: { - verb: 'GET', - path: '/component/:name' - }, - returns: [{ - arg: 'body', - type: 'string', - root: true - }, - { - arg: 'Content-Type', - type: 'string', - http: { - target: 'header' - } - } - ] - }); - - - UIComponent.afterRemote('ui', function uiComponentUi(context, remoteMethodOutput, next) { - context.res.setHeader('Content-Type', 'text/plain'); - context.res.end(context.result); - }); - - UIComponent.afterRemote('component', function uiComponentComponent(context, remoteMethodOutput, next) { - context.res.setHeader('Content-Type', 'text/html'); - context.res.end(context.result); - }); - - UIComponent.afterRemote('simulate', function uiComponentSimulate(context, remoteMethodOutput, next) { - context.res.setHeader('Content-Type', 'text/html'); - context.res.end(context.result); - }); - - // TODO handle route path and nav link add update / delete - // UIComponent.observe('after save', function(ctx, next){ - // if (ctx.instance) { - // var NavigationLink = loopback.getModelByType('NavigationLink'); - // } - // next(); - // }); -}; diff --git a/common/models/ui/ui-component.json b/common/models/ui/ui-component.json deleted file mode 100644 index eb20970..0000000 --- a/common/models/ui/ui-component.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "name": "UIComponent", - "base": "UIBase", - "idInjection": false, - "description": "Configuration and content for UI components", - "options": { - "validateUpsert": true - }, - "properties": { - "name": { - "type": "string", - "required": true, - "description": "name of UI component, matches Polymer proptotype.is property", - "max": 80, - "unique": true - }, - "category": { - "type": "string", - "description": "Component category" - }, - "templateName": { - "type": "string", - "description": "Based on templateName component is generatred" - }, - "resturl": { - "type": "string", - "description": "url for form data handling" - }, - "geturl": { - "type": "string", - "description": "url for GET" - }, - "posturl": { - "type": "string", - "description": "url for POST" - }, - "puturl": { - "type": "string", - "description": "url for PUT" - }, - "deleteurl": { - "type": "string", - "description": "url for DELETE" - }, - "componentData": { - "type": "object", - "description": "Data to be set on the generated component on attached." - }, - "filePath": { - "type": "string", - "description": "If component is file based, file location" - }, - "content": { - "type": "string", - "max": 10000, - "allowScript": true, - "description": "html content of the component, if not stored it is generated from model metadata" - }, - "routePath": { - "type": "string", - "description": "If this UI component handles a route then route path" - }, - "modelName": { - "type": "string", - "description": "If this component renders default UI for a model then model name" - }, - "modelAlias": { - "type": "string", - "description": "If model Alias is given it is used as primary model for binding, else lower case of modelName is used, for old UI Metadata migration this can be vm" - }, - "fields": { - "type": "object", - "description": "fields of default model to be rendered" - }, - "container": { - "type": "object", - "description": "containers to be added into the template" - }, - "gridConfig": { - "type": "object", - "description": "fields to display in grids" - }, - "options": { - "type": "object", - "description": "Any data which can be used by UI for this component" - }, - "polymerConfig": { - "type": "object", - "description": "Polymer Configuration, used for polymer registration" - }, - "autoInjectFields": { - "type": "boolean", - "description": "Whether remaining model fields should be auto injected or not", - "default": true - }, - "excludeFields": { - "type": "object", - "description": "Fields which are to be excluded from injection" - }, - "oeValidations": { - "type": ["Validation"], - "required": false - }, - "importUrls": { - "type": ["string"], - "description": "Additionals Import links for the component" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/ui/ui-element.json b/common/models/ui/ui-element.json deleted file mode 100644 index a67bdf0..0000000 --- a/common/models/ui/ui-element.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "UIElement", - "base": "UIBase", - "idInjection": false, - "description" : "Field/Control level Personalization of UI components", - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "component": { - "type": "string", - "required": true, - "description": "This is Polymer Component Name (prototype.is)", - "max": 30 - }, - "field": { - "type": "string", - "required": true, - "description": "field id, in case of model field, use dot convention, model.field", - "max": 50 - }, - "textContent": { - "type": "string", - "required": false, - "description": "textContent property of HTML Elements, or localise key", - "max": 500 - }, - "uitype": { - "type": "string", - "required": false, - "description": "control type to render this field", - "max": 50 - }, - "label": { - "type": "string", - "required": false, - "description": "caption/label or its localise key", - "max": 50 - }, - "container": { - "type": "string", - "required": false, - "description": "target container for the field", - "max": 50 - }, - "order": { - "type": "number", - "required": false, - "description": "field order in the container" - }, - "exclude": { - "type": "boolean", - "required": false, - "description": "set to true for removing the field." - } - }, - "validations": [], - "relations": { - "_attributes": { - "type": "embedsMany", - "model": "UIAttribute", - "property": "attributes", - "options": { - "validate": true - } - } - }, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/ui-manager.js b/common/models/ui/ui-manager.js deleted file mode 100644 index 6f08858..0000000 --- a/common/models/ui/ui-manager.js +++ /dev/null @@ -1,148 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('ui-manager'); -var loopback = require('loopback'); - -/** - * @classdesc This non-persisted model provides some utility end-points for ui-admin. - * `generate` (/api/UIManager/generate/:modelname) method, generates a default nav-link, ui-route and ui-metadata to display model form. - * - * @kind class - * @class UIManager - * @author Rohit Khode - */ - -module.exports = function UIManager(UIManager) { - function _findAndCreate(modelDefn, filter, data, response, options, next) { - log.debug(options, '_findAndCreate', modelDefn.definition.name, filter); - - modelDefn.find({ - where: filter - }, options, function modelDefnFindCb(err, records) { - if (err) { - return next(err); - } - if (records && records.length > 0) { - response.messages.push(modelDefn.definition.name + '-already-defined'); - return next(); - } - modelDefn.create(data, options, function modelDefnCreateCb(err, data) { - log.debug(options, 'Model Created', err, data); - if (err) { - response.errors = response.errors || []; - response.errors.push(err); - response.status = false; - return next(response); - } - response.messages.push(modelDefn.definition.name + '-created'); - return next(); - }); - }); - } - - UIManager.generate = function UIManagerUiGen(modelname, req, options, cb) { - var self = this; - - var app = self.app; - - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - if (!modelname) { - cb({ - status: false, - messages: ['missing-model-name'] - }); - return; - } - - var modelDefn = loopback.findModel(modelname, options); - if (modelDefn) { - var newMetadata = { - name: modelname, - modelName: modelDefn.definition.name, - fields: [] - }; - - for (var pName in modelDefn.definition.rawProperties) { - if (pName[0] !== '_' && pName !== 'id') { - newMetadata.fields.push(pName); - } - } - - var navLinkData = { - name: modelname, - label: modelname, - url: '/forms/' + modelname + '-form', - topLevel: true, - group: 'root' - }; - - var uiRouteData = { - name: 'forms', - path: '/forms/:formname', - type: 'elem', - import: '/api/UIComponents/component/:formname' - }; - - var response = { - status: true, - messages: [] - }; - async.series([ - function handleUIComponent(next) { - _findAndCreate(app.models.UIComponent, { - name: modelname - }, newMetadata, response, options, next); - }, - function handleNavigationLink(next) { - _findAndCreate(app.models.NavigationLink, navLinkData, navLinkData, response, options, next); - }, - function handleUIRoute(next) { - _findAndCreate(app.models.UIRoute, uiRouteData, uiRouteData, response, options, next); - } - ], function callbackFn(err) { - cb(err, response); - }); - } else { - cb({ - status: false, - messages: ['invalid-model-name'] - }); - } - }; - - UIManager.remoteMethod('generate', { - returns: [{ - type: 'object', - root: true, - description: 'return value' - }], - accepts: [{ - arg: 'modelname', - type: 'string', - http: { - source: 'path' - } - }, - { - arg: 'req', - type: 'object', - http: { - source: 'req' - } - }], - http: { - path: '/generate/:modelname', - verb: 'post' - } - }); -}; diff --git a/common/models/ui/ui-manager.json b/common/models/ui/ui-manager.json deleted file mode 100644 index 4ee66ff..0000000 --- a/common/models/ui/ui-manager.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "UIManager", - "base": "Model", - "strict": false, - "idInjection": false, - "options": { - "validateUpsert": true - }, - "VersionMixin":true, - "properties": { - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/common/models/ui/uimetadata.js b/common/models/ui/uimetadata.js deleted file mode 100644 index ae6c8f3..0000000 --- a/common/models/ui/uimetadata.js +++ /dev/null @@ -1,571 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var async = require('async'); -var inflection = require('inflection'); -var logger = require('oe-logger'); -var log = logger('uimetadata'); - -/** -* @classdesc This model stores the form definition data. -* The `render` API enriches the form metadata with model properties and property store -* and returns the final metadata definition that can be used by client code to render the form. -* -* Default rendering: -* 1. Property name is taken as field-id. -* 1. Property name like address or userName will have 'Address' and 'User Name' as label respectively. -* 1. Property type 'text' is rendered as uitype:'text' -* 1. If enumtype or reftype properties are also set, additional 'listdata' attribute is set which renders field as combo input. -* 1. Property type 'number' is rendered as uitype:'number'. When numericality is 'integer', uitype is also 'integer'. -* 1. Property type 'date' is rendered as uitype:'date' -* 1. Array of primitives is rendered as uitype:'tags' -* 1. Array of composite types is rendered as grid. -* 1. Default value specified on model properties, are sent under defaultVM object. Boolean type, when no default is specified, assume default:false. -* 1. `belongsTo` relationship results in uitype:typeahead. The typeahead, searches on related model's 'name' property by default. -* 1. `hasMany` relationship results in uitype:grid. -* -* Common recurring fields can also be specified in `Field` model. By default, record corresponding the propertyName is searched and -* all properties specified there are pulled in. -* UIMetadata's controls array can also have inline control attributes. The order of precedence is: -* * Inline control attribute -* * Field Property store -* * Default derived from Model definition -* -* Order of precedence however does not apply to validations where strictest of all is applied. -* A field marked as required in model definition, can not be overriden by setting required:false in control array of property store. -* Model max=1000 and control array max=2000 results in max=1000 -* Model min=100 and control array min=200 results in min=200 -* -* @kind class -* @class UIMetadata -* @author Rohit Khode -*/ - -module.exports = function UIMetadata(UIMetadata) { - // Converts propertyNames to UI label (firstName -> First Name) - function camelCaseToLabel(s) { - // Make the first character uppercase before split/join. - return (s.charAt(0).toUpperCase() + s.slice(1)).split(/(?=[A-Z])/).join(' '); - } - - /** - * Find max of 3 values - * @param {number} v1 - first value - * @param {number} v2 - second value - * @param {number} v3 - third value - * @returns {number} - maximum of 3 values - */ - function max(v1, v2, v3) { - var ret = v1 ? v1 : (v2 ? v2 : v3); - if (v1 && v2 && (v2 > v1)) { - ret = v2; - } - if (v2 && v3 && (v3 > v2)) { - ret = v3; - } - return ret; - } - - /** - * Find min of 3 values - * @param {number} v1 - first value - * @param {number} v2 - second value - * @param {number} v3 - third value - * @returns {number} - minimum of 3 values - */ - function min(v1, v2, v3) { - var ret = v1 ? v1 : (v2 ? v2 : v3); - if (v1 && v2 && (v2 < v1)) { - ret = v2; - } - if (v2 && v3 && (v3 < v2)) { - ret = v3; - } - return ret; - } - - /** - * Sets specified `value` on `target` going levels down if required. - * o:{}, - * setValue(o, "x",5) -> o:{x:5} - * setValue(o, "y.z",6) -> o:{x:5,y:{z:6}} - * setValue(o, "y.k",7) -> o:{x:5,y:{z:6,k:7}} - * @param {object} target - target - * @param {string} field - field - * @param {any} value - value - */ - function setValue(target, field, value) { - if (field) { - var fields = field.split('.'); - var leaf = fields.pop(); - - var currentTarget = target; - fields.forEach(function _forEachCb(field) { - currentTarget[field] = currentTarget[field] || {}; - currentTarget = currentTarget[field]; - }); - currentTarget[leaf] = value; - } - } - - /** - * Attempt to get a sane default rest-url for the returned metadata - * TODO: We can pull hardcoded 'api/' part from configuration - * @param {object} model - model constructor - * @returns {string} - rest URL - */ - function getRestUrl(model) { - return 'api/' + (model.definition.settings.plural || inflection.pluralize(model.definition.name)); - } - - /** - * Initialize appropriate defaults in the controls array. - * 1) Control defined as simple string is translated into Object - * "string" -> {fieldid:"string", source:"string"} - * 2) All "source" properties are cached under `propertyStoreKeys` for fetching from `Field` model. - * 3) Current defined controls are also set on `modelMeta`. This object is later enriched with model-defined-properties. - * Setting defined controls on `modelMeta` here serves later in identifying what model properties are defined in controls already. - * @param {array} controls - list of controls - * @returns {object} - controls with defaults - */ - function initializeAndExtractControls(controls) { - var master = { - // meta defn based on model properties - modelMeta: {}, - // collect all relevant field-sources. - propertyStoreKeys: {} - }; - - if (controls) { - for (var fIdx = 0; fIdx < controls.length; fIdx++) { - var fInfo = controls[fIdx]; - - /* If control-defn is a simple string, make it an appropriate object */ - if (typeof fInfo === 'string') { - controls[fIdx] = { - container: 'others', - fieldid: fInfo, - source: fInfo - }; - fInfo = controls[fIdx]; - } - - fInfo.container = fInfo.container || 'others'; - - fInfo.source = fInfo.source || fInfo.fieldid; - fInfo.name = fInfo.container + '_' + fInfo.fieldid + '_' + fIdx; - - master.modelMeta[fInfo.fieldid] = {}; - master.propertyStoreKeys[fInfo.source] = true; - } - } - - return master; - } - - var prepareData = function uiMetadataPrepareData(app, data, options, cb) { - // populate field metadata defined in controls. - var fieldsMaster = initializeAndExtractControls(data.controls); - - if (data.modeltype) { - var ModelDefn = app.models.ModelDefinition; - options.flatten = true; - /** Get details of specified model along with all the related models. - * allModelsInfo will contain (this, related and embedded)-models and their definition all as key/value pair. - */ - ModelDefn.extractMeta(data.modeltype, options, function extractMetaCb(err, flattenedModel) { - if (err) { - cb(err); - } - if (flattenedModel) { - /** iterate over modelFields to - * 1. add any missing field in controls - * 2. setup model level defaults on `modelFields` - */ - for (var fieldName in flattenedModel.properties) { - if (flattenedModel.properties.hasOwnProperty(fieldName)) { - /* Don't bother adding or enriching if it is private property (starts with _underscore) or excluded explicitly*/ - if (fieldName.match(/^_|\._/) || - (data.exclude && data.exclude.indexOf(fieldName) >= 0) || - (fieldName === 'scope' || fieldName.match(/\.scope$/))) { - continue; - } - - var modelField = flattenedModel.properties[fieldName]; - - // console.log(fieldName, modelField); - - var isDefined = fieldsMaster.modelMeta[fieldName]; - - if (!isDefined && !data.skipMissingProperties) { - var fieldMeta = { - fieldid: fieldName, - source: fieldName, - name: 'others_' + fieldName.replace(/\./g, '_'), - container: 'others' - }; - - fieldsMaster.propertyStoreKeys[fieldName] = true; - data.controls.push(fieldMeta); - } - - /** populate Label and UIType in Model-Meta. - * We can not set on control-meta directly as the values may still come from Field Store, - * which overrides the model-calculated label and uitype. - */ - var dotIdx = fieldName.lastIndexOf('.'); - var label = dotIdx > 0 ? fieldName.substr(dotIdx + 1) : fieldName; - label = camelCaseToLabel(label); - modelField.label = label; - - var uitype = modelField.type; - if (uitype === 'string') { - if (modelField.enumtype) { - var enumModel = app.loopback.findModel(modelField.enumtype, options); - if (enumModel) { - modelField.listdata = enumModel.settings.enumList; - } - } else if (modelField.in) { - modelField.listdata = modelField.in; - } - - if (uitype === 'string') { - uitype = 'text'; - } - } else if (uitype === 'array') { - if (modelField.itemtype === 'model') { - uitype = 'grid'; - modelField.columndefs = []; - modelField.gridIdentifier = app.models[modelField.modeltype].clientModelName; - } else { - // array of primitives of type 'itemtype' - uitype = 'tags'; - } - } else if (uitype === 'number') { - /* If numericality is set, use that as uitype (integer/number) */ - uitype = modelField.numericality ? modelField.numericality : uitype; - } - modelField.uitype = uitype; - - fieldsMaster.modelMeta[fieldName] = modelField; - } - } - - // Work Out Relations - for (var relationName in flattenedModel.relations) { - // ignore explicitly excluded relation fields - if (!(data.exclude && data.exclude.indexOf(relationName) >= 0) && flattenedModel.relations.hasOwnProperty(relationName)) { - var relation = flattenedModel.relations[relationName]; - if (relation.type === 'belongsTo') { - modelField = fieldsMaster.modelMeta[relation.keyFrom]; - modelField.valueproperty = relation.keyTo; - // assume 'name' ?? - modelField.displayproperty = 'name'; - modelField.uitype = 'typeahead'; - var restUrl = getRestUrl(relation.modelTo); - modelField.searchurl = restUrl + '?filter[where][name][regexp]=/^SEARCH_STRING/i&filter[limit]=5'; - modelField.dataurl = restUrl + '/VALUE_STRING'; - modelField.label = camelCaseToLabel(relationName); - } else if (relation.type === 'hasMany') { - isDefined = fieldsMaster.modelMeta[relationName]; - - // add the relation name in includes - // so that on "GET", the related model will be fetched by default - data.includes = data.includes ? data.includes + ',' + relationName : relationName; - - if (!isDefined && !data.skipMissingProperties) { - fieldMeta = { - fieldid: relationName, - source: fieldName, - name: 'others_' + relationName, - dialogmetadata: relation.modelTo.modelName - }; - - fieldsMaster.propertyStoreKeys[relationName] = true; - data.controls.push(fieldMeta); - } - - modelField = { - uitype: 'grid', - fieldid: relationName, - gridIdentifier: relation.modelTo.modelName, - dialogmetadata: relation.modelTo.modelName, - label: camelCaseToLabel(relationName), - rowstatusreqd: true - }; - fieldsMaster.modelMeta[relationName] = modelField; - } else { - // Unprocessed relation - tasks hasMany TestEmployee id TestTask testEmployeeId - log.debug(options, 'Unprocessed relation - ', relation.name, - relation.type, relation.modelFrom.definition.name, - relation.keyFrom, relation.modelTo.definition.name, relation.keyTo); - } - } - } - - data.resturl = data.resturl || flattenedModel.resturl; - } - - // Do not call getRestUrl since that needs model, and we need below restUrl when model is not available. - data.resturl = data.resturl || '/api/' + inflection.pluralize(data.modeltype); - data.title = data.title || data.modeltype; - delete options.flatten; - enrichFromPropertyStore(app, fieldsMaster, data, options, function uiMetadataPrepareDataEnrichCb(err, data) { - if (err) { - cb(err); - } - enrichGridPropertyStore(app, data, options, function uiMetadataPrepareDataGridEnrichCb(err, data1) { - if (err) { - cb(err); - } - getListDataForRefCode(app, data1, options, function uiMetadataPrepareDataRefCodeEnrichCb(err, data2) { - if (err) { - cb(err); - } - cb(err, data2); - }); - }); - }); - }); - } else { - enrichFromPropertyStore(app, fieldsMaster, data, options, function uiMetadataPrepareDataEnrich2Cb(err, data) { - cb(err, data); - }); - } - }; - - function enrichGridPropertyStore(app, data, options, cb) { - var gridControls = data.controls.filter(function enrichGridPropertyStoreFilter(control) { - return control.uitype === 'grid' && control.gridIdentifier; - }); - - for (var i = 0; i < data.controls.length; i++) { - var control = data.controls[i]; - if (control.uitype === 'grid') { - control.container = control.container || 'grid-container'; - } - } - - async.forEachOf(gridControls, function enrichGridPropertyStoreAsyncCb(val, key, callback) { - app.models.GridMetaData.render(val.gridIdentifier, null, options, function enrichGridPropertyStoreForEachGridRenderCb(err, resp) { - if (err) { - log.error(options, err); - } else { - val.columndefs = resp.columnData; - resp.dialogTemplateUrl && (val.dialogtemplateurl = resp.dialogTemplateUrl); - val.dialogmetadata = resp.dialogMetaData; - - delete val.gridIdentifier; - } - callback(); - }); - }, function enrichGridPropertyStoreAsyncFinalCb(err) { - if (err) { - log.error(options, err); - cb(err, null); - } - - cb(null, data); - }); - } - - function getListDataForRefCode(app, data, options, cb) { - var comboControls = data.controls.filter(function getListDataForRefCodeFilterCb(control) { - return control.refcodetype; - }); - - async.forEachOf(comboControls, function getListDataForRefCodeAsyncCb(val, key, callback) { - var refCodeModel = app.models[val.refcodetype]; - refCodeModel.find({}, options, function getListDataForRefCodeAsyncRefCodeFindCb(err, resp) { - if (!err) { - val.listdata = resp; - val.displayproperty = 'description'; - val.valueproperty = 'code'; - } - callback(); - }); - }, function getListDataForRefCodeAsyncFinalCb(err) { - if (err) { - log.error(options, err); - cb(err, null); - } - - cb(null, data); - }); - } - - function enrichFromPropertyStore(app, fieldsMaster, data, options, cb) { - app.models.Field.find({ - key: { - inq: Object.keys(fieldsMaster.propertyStoreKeys) - } - }, options, function enrichFromPropertyStoreFindCb(err, results) { - if (err) { - cb(err); - } - var defaultVM = {}; - // all the Fields that we've found, lets enrich fieldsMaster.fields with it. - var resultsObj = {}; - for (var rIdx = 0; results && rIdx < results.length; rIdx++) { - var fieldMeta = results[rIdx]; - resultsObj[fieldMeta.key] = fieldMeta; - } - - var textInputTypes = ['text', 'string', 'textarea']; - for (var idx = 0; idx < data.controls.length; idx++) { - var ctrlMeta = data.controls[idx]; - var metaSource = ctrlMeta.source || ctrlMeta.fieldid; - - var modelMeta = fieldsMaster.modelMeta[ctrlMeta.fieldid] || {}; - fieldMeta = resultsObj[metaSource] || {}; - - // When datatype is boolean, we always set the default value. - // If not defined, set it to 'false' explicitly. - - setValue(defaultVM, ctrlMeta.fieldid, (ctrlMeta.default || fieldMeta.default || (modelMeta.type === 'boolean' && - typeof modelMeta.default === 'undefined' ? false : modelMeta.default))); - - // no need to send default-value at control-metadata level. - delete ctrlMeta.default; - - if (ctrlMeta.enumtype || fieldMeta.enumtype || modelMeta.enumtype) { - var enumname = ctrlMeta.enumtype || fieldMeta.enumtype || modelMeta.enumtype; - var enumModel = app.loopback.findModel(enumname, options); - if (enumModel) { - // enumtype is pointing to model - ctrlMeta.listdata = enumModel.settings.enumList; - } else { - // enumtype is not pointing to model - log.error(options, 'error finding enumtype ', enumname); - } - } - - if (ctrlMeta.refcodetype || fieldMeta.refcodetype || modelMeta.refcodetype) { - var type = ctrlMeta.refcodetype || fieldMeta.refcodetype || modelMeta.refcodetype; - var refCodeModel = app.models[type]; - if (refCodeModel) { - // refcodetype is pointing to model - ctrlMeta.refcodetype = type; - } else { - // refcodetype is not pointing to model - log.error(options, 'error finding refcodetype'); - } - } - - ctrlMeta.required = ctrlMeta.required || fieldMeta.required || modelMeta.required; - ctrlMeta.uitype = ctrlMeta.uitype || fieldMeta.uitype || modelMeta.uitype; - ctrlMeta.label = ctrlMeta.label || fieldMeta.label || modelMeta.label; - ctrlMeta.listdata = ctrlMeta.listdata || fieldMeta.listdata || modelMeta.listdata; - ctrlMeta.listid = ctrlMeta.listid || fieldMeta.listid; - ctrlMeta.listurl = ctrlMeta.listurl || fieldMeta.listurl || modelMeta.listurl; - ctrlMeta.bindto = ctrlMeta.bindto || fieldMeta.bindto; - ctrlMeta.class = ctrlMeta.class || fieldMeta.class; - ctrlMeta.hidden = ctrlMeta.hidden || fieldMeta.hidden; - - ctrlMeta.pattern = ctrlMeta.pattern || fieldMeta.pattern || modelMeta.pattern; - - if (textInputTypes.indexOf(ctrlMeta.uitype) >= 0) { - ctrlMeta.maxlength = min(ctrlMeta.max || ctrlMeta.maxlength, fieldMeta.max || fieldMeta.maxlength, modelMeta.max); - ctrlMeta.minlength = max(ctrlMeta.min || ctrlMeta.minlength, fieldMeta.min || fieldMeta.minlength, modelMeta.min); - ctrlMeta.max = null; - ctrlMeta.min = null; - } else { - ctrlMeta.max = min(ctrlMeta.max, fieldMeta.max, modelMeta.max); - ctrlMeta.min = max(ctrlMeta.min, fieldMeta.min, modelMeta.min); - ctrlMeta.maxlength = null; - ctrlMeta.minlength = null; - } - - ctrlMeta.gridIdentifier = ctrlMeta.gridIdentifier || fieldMeta.gridIdentifier || modelMeta.gridIdentifier; - ctrlMeta.columndefs = ctrlMeta.columndefs || fieldMeta.columndefs || modelMeta.columndefs; - ctrlMeta.itemtype = ctrlMeta.itemtype || fieldMeta.itemtype || modelMeta.itemtype; - - ctrlMeta.displayproperty = ctrlMeta.displayproperty || fieldMeta.displayproperty || modelMeta.displayproperty; - ctrlMeta.valueproperty = ctrlMeta.valueproperty || fieldMeta.valueproperty || modelMeta.valueproperty; - ctrlMeta.selectionBinding = ctrlMeta.selectionBinding || fieldMeta.selectionBinding || modelMeta.selectionBinding; - ctrlMeta.searchurl = ctrlMeta.searchurl || fieldMeta.searchurl || modelMeta.searchurl; - ctrlMeta.dataurl = ctrlMeta.dataurl || fieldMeta.dataurl || modelMeta.dataurl; - ctrlMeta.dialogmetadata = ctrlMeta.dialogmetadata || fieldMeta.dialogmetadata || modelMeta.dialogmetadata; - ctrlMeta.rowstatusreqd = ctrlMeta.rowstatusreqd || fieldMeta.rowstatusreqd || modelMeta.rowstatusreqd; - } - data.defaultVM = defaultVM; - - cb(null, data); - }); - } - - /** - * Custom remote method finds the `UIMetadata` with specified `code`, enriches the control definitions with - * (a) Field store - * (b) Model Definition - * If UIMetadata is not found, it assumes `code` to be the Model Name and builds default for this model (enriching from Field store) - * Control specifications in UIMetadata take precedence over those specified in Field Store - * Field store specifications take precedence over those derived from Model properties. - * Precedence rule do not apply to validations, where strictest of all rule is applied. - * Control as required=false will still return required:true if Model definition mandates it. - * @param {string} code - code of uimetadata entry - * @param {object} req - request - * @param {object} options - callcontext options - * @param {function} cb - callback function - */ - UIMetadata.render = function uiMetadataRender(code, req, options, cb) { - var self = this; - - var filter = { - where: { - 'code': code - } - }; - - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - UIMetadata.findOne(filter, options, function uiMetadataRenderFindCb(err, data) { - if (err) { - cb(err); - } - if (!data) { - data = { - code: code, - modeltype: code, - description: 'default ' + code, - controls: [] - }; - } - prepareData(self.app, data, options, cb); - }); - }; - - UIMetadata.remoteMethod( - 'render', { - returns: [{ - type: 'object', - root: true, - description: 'return value' - }], - accepts: [{ - arg: 'code', - type: 'string', - http: { - source: 'path' - } - }, - { - arg: 'req', - type: 'object', - http: { - source: 'req' - } - }], - http: { - path: '/:code/render', - verb: 'get' - } - } - ); -}; diff --git a/common/models/ui/uimetadata.json b/common/models/ui/uimetadata.json deleted file mode 100644 index b2a68b1..0000000 --- a/common/models/ui/uimetadata.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "UIMetadata", - "base": "UIBase", - "description": "Stores screen or form metadata", - "strict": true, - "plural": "UIMetadata", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "code": { - "type": "String", - "required": true, - "max": 100 - }, - "description": { - "type": "string", - "required": false, - "max": 500 - }, - "modeltype": { - "type": "string", - "required": false, - "max": 100 - }, - "skipMissingProperties": { - "type": "boolean" - }, - "showTitleInNavbar": { - "type": "boolean" - }, - "resturl": { - "type": "string", - "required": false, - "max": 250 - }, - "title": { - "type": "string" - }, - "controls": { - "type": [ - "any" - ], - "required": true - }, - "exclude": { - "type": [ "string" ] - }, - "properties": { - "type": "object" - }, - "functions": { - "type": "object" - }, - "behaviors": { - "type": [ "string" ] - }, - "oeValidations": { - "type": [ - "Validation" - ], - "required": false - } - }, - "validations": [], - "relations": {}, - "acls": [ - ], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/uiresource.js b/common/models/ui/uiresource.js deleted file mode 100644 index 9386362..0000000 --- a/common/models/ui/uiresource.js +++ /dev/null @@ -1,84 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/** -* @classdesc This model stores the User-Interface Resources and allows them to be pulled by UI based on scope. -* name - name of the ui-resource, unique within scope. Browsers/Client will request for resource based on this name. -* type - 'Content-Type' of the resource -* content - the resource data -* A custom remote route UIResources/content/ extracts the `content` from the -* given record and returns. `type` property on record decides the 'Content-Type' http header. -* -* @kind class -* @class UIResource -* @author Rohit Khode -*/ - -module.exports = function UIResourceFn(UIResource) { - /** - * Extracts the `content` from the given record and returns. - * `type` property on record decides the 'Content-Type' http header. - * @param {string} name name. - * @param {function} options options. - * @param {function} cb callback. - * @memberof UIResource - * @name content - */ - UIResource.content = function uiResourceContentFn(name, options, cb) { - // var self = this; - - var filter = { - where: { - name: name - } - }; - - if (!cb && typeof options === 'function') { - cb = options; - options = {}; - } - - UIResource.find(filter, options, function uiResourceContentFindCb(err, results) { - if (err) { - cb(err); - } - if (results && results[0]) { - cb(null, results[0]); - } else { - cb({ - status: 404, - message: 'Resource ' + name + ' not found.' - }, null); - } - }); - }; - - UIResource.remoteMethod( - 'content', { - returns: [{ - type: 'object', - root: true, - description: 'resource content' - }], - accepts: [{ - arg: 'name', - type: 'string', - http: { - source: 'path' - } - }], - http: { - path: '/content/:name', - verb: 'get' - } - } - ); - - UIResource.afterRemote('content', function UIResourceContent(context, remoteMethodOutput, next) { - context.res.setHeader('Content-Type', context.result.type); - context.res.end(context.result.content); - }); -}; diff --git a/common/models/ui/uiresource.json b/common/models/ui/uiresource.json deleted file mode 100644 index 1464dd1..0000000 --- a/common/models/ui/uiresource.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "UIResource", - "base": "UIBase", - "plural": "UIResources", - "description": "This model stores the User-Interface Resources", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "name": { - "type": "string", - "required": true, - "unique": true, - "max": 30 - }, - "masterPage": { - "type": "string", - "max": 30 - }, - "type": { - "type": "string", - "required": true, - "max": 30 - }, - "content": { - "type": "string", - "required": true, - "max": 10000, - "allowScript": true - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/uiroute.js b/common/models/ui/uiroute.js deleted file mode 100644 index e441598..0000000 --- a/common/models/ui/uiroute.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This model stores the data to auto configure, application level client side routing using page.js -* The model has following properties -* Property | Description -* ---------|------------------------------- -* `name` | route name -* `path` | relative url along with placeholders e.g. /customer/:id -* `type` |
    • page -> html data from "import" is fetched and added as innerHtml of target.
    • meta -> ui-metadata from "import" is fetched, component is generated and added inside target
    • list -> with relevant "import" is added inside target
    • elem -> If element with 'name' is NOT registered yet, 'import' is href-imported and 'name' element is added.
    -* `import` | as explained above -* `target` | element-query-selection for target element, non-mandatory the default taken from specified global. -* -* e.g. -* -* [{ -* "type": "page", -* "name": "receipts", -* "path": "/receipts", -* "import": "receipts-partial.html" -* }, -* { -* "type": "elem", -* "name": "cfe-loan-details", -* "path": "/loan", -* "import": "../business/cfe-loan-details.html" -* }] -* -* Note: If you specify `path` as "@default", the current location.pathname is configured to execute that route. -* This then also becomes the default 404 handler. -* -* -* @kind class -* @class UIRoute -* @author Rohit Khode -*/ - -/* -* this method can be overriden in application in boot script -* to have different behaviour -*/ - -module.exports = function uiRoute(UIRoute) { - var routes = {}; - var app = require('../../../server/server').app; - var subPath = app.get('subPath'); - if (subPath) { - UIRoute.observe('after accesss', addSubPath); - } - UIRoute.prototype.redirectHandler = function redirectHandler(app) { - if (!routes[this.path]) { - app.get(this.path, function getPath(req, res) { - res.redirect('/?redirectTo=' + req.originalUrl); - }); - } - routes[this.path] = true; - }; - - function addSubPath(ctx, next) { - var result = ctx.accdata; - result && result.forEach(function (route) { - route.path = '/' + subPath + route.path; - route.import = subPath + '/' + route.import; - }); - next(); - } -}; diff --git a/common/models/ui/uiroute.json b/common/models/ui/uiroute.json deleted file mode 100644 index b20e1a5..0000000 --- a/common/models/ui/uiroute.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "UIRoute", - "base": "UIBase", - "idInjection": false, - "plural": "UIRoutes", - "description": "Application level client side routing", - "options": { - "validateUpsert": true - }, - "VersionMixin": true, - "properties": { - "type": { - "type": "string", - "required": true, - "document": "page/elem/meta/list", - "max": 30, - "description": "type can be elem, page, meta" - }, - "name": { - "type": "string", - "required": true, - "max": 80, - "description": "name of element. It is used by ev-ui-router, ev-app-route uses actual name from imported document instead of this property" - }, - "path": { - "type": "string", - "required": true, - "max": 250, - "description": "Express style URL pattern to be handled by this route", - "unique": { - "scopedTo": [ - "group" - ] - } - }, - "import": { - "type": "string", - "max": 250, - "description": "component or file to be imported for this route for lazy loading" - }, - "group": { - "type": "string", - "max": 30, - "description": "different html files should be able to load different routes based on this, TODO functionaility in router", - "default": "" - }, - "targetElement": { - "type": "string", - "max": 30, - "description": "used by ev-ui-router, not used by ev-app-route" - }, - "retainInstance": { - "type": "boolean", - "default": false, - "description": "whether ev-app-route should retain the state when you switch the route" - }, - "transitions": { - "type": "object", - "description": "Specifies the event based route transitions when application is at this route" - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/common/models/ui/validation.js b/common/models/ui/validation.js deleted file mode 100644 index 8f6ea80..0000000 --- a/common/models/ui/validation.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* @classdesc This Model is used to store UI Level Validations. -* -* Property | Description -* ---------|------------------------------- -* `type` | Type of the Validation. -* `fields` | Field on which Validation needs to be applied. -* `error` | Error in case of Validation failure. -* -* @kind class -* @class Validation -* @author Rohit Khode -*/ diff --git a/common/models/ui/validation.json b/common/models/ui/validation.json deleted file mode 100644 index 5ce96ca..0000000 --- a/common/models/ui/validation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Validation", - "base": "UIBase", - "idInjection": false, - "description": "This Model is used to store UI Level Validations", - "options": { - "validateUpsert": true - }, - "properties": { - "type": { - "type": "string", - "required": true, - "max": 100 - }, - "fields": { - "type": [ "string" ], - "required": false - }, - "error": { - "type": "string", - "required": false, - "max": 100 - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} \ No newline at end of file diff --git a/docker-compose.env.yml b/docker-compose.env.yml deleted file mode 100644 index f177eb0..0000000 --- a/docker-compose.env.yml +++ /dev/null @@ -1,53 +0,0 @@ -# ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -#Bangalore, India. All Rights Reserved. - -version: "3" -services: - web: - image: ${REGISTRY}/${APP_IMAGE_NAME}:latest - depends_on: - - mongo - - postgres - environment: - NODE_ENV: ${NODE_ENV} - POSTGRES_HOST: "postgres" - MONGO_HOST: "mongo" - CONFIG: ${CONFIG} - DATASOURCES: ${DATASOURCES} - PROVIDERS: ${PROVIDERS} - NODE_TLS_REJECT_UNAUTHORIZED: "0" - VIRTUAL_HOST: "https://${NODE_ENV}-${APP_IMAGE_NAME}.${DOMAIN_NAME},${NODE_ENV}-${APP_IMAGE_NAME}.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - deploy: - mode: replicated - replicas: 1 - update_config: - delay: 30s - resources: - limits: - cpus: '0.25' - memory: 512M - restart_policy: - condition: on-failure - delay: 5s - max_attempts: 5 - window: 180s - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:10.73.96.137" - networks: - - router_network - - ${NETWORK_NAME} - postgres: - image: ${REGISTRY}/alpine-postgres:latest - networks: - - ${NETWORK_NAME} - mongo: - image: ${REGISTRY}/alpine-mongo:latest - networks: - - ${NETWORK_NAME} - -networks: - $NETWORK_NAME: - router_network: - external: true \ No newline at end of file diff --git a/docker-compose.test.env.yml b/docker-compose.test.env.yml deleted file mode 100644 index 32345a7..0000000 --- a/docker-compose.test.env.yml +++ /dev/null @@ -1,28 +0,0 @@ -# ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -#Bangalore, India. All Rights Reserved. -version: "3" -services: - web: - entrypoint: ${STARTUP_CMD} - image: ${REGISTRY}/${APP_IMAGE_NAME}:${APP_TAG} - environment: - NODE_ENV: ${NODE_ENV} - POSTGRES_HOST: ${POSTGRES_HOST} - MONGO_HOST: ${MONGO_HOST} - DB_NAME: ${DB_NAME} - CONFIG: ${CONFIG} - DATASOURCES: ${DATASOURCES} - PROVIDERS: ${PROVIDERS} - NODE_TLS_REJECT_UNAUTHORIZED: "0" - ORACLE_HOST: ${ORACLE_HOST} - ORACLE_SID: ${ORACLE_SID} - ORACLE_USERNAME: ${ORACLE_USERNAME} - ORACLE_PASSWORD: ${ORACLE_PASSWORD} - deploy: - restart_policy: - condition: none - networks: - - oecloud_ci_test -networks: - oecloud_ci_test: - external: true \ No newline at end of file diff --git a/drop.js b/drop.js new file mode 100644 index 0000000..cfef849 --- /dev/null +++ b/drop.js @@ -0,0 +1,6 @@ +use oe-cloud-test; +db.dropDatabase(); + +use oe-cloud-test-newdb; +db.dropDatabase(); + diff --git a/index.js b/index.js new file mode 100644 index 0000000..d864477 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +/** + * + * 2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + +// Author : Atul +module.exports = require('./lib/load'); diff --git a/lib/actor-pool.js b/lib/actor-pool.js deleted file mode 100644 index 27e02a6..0000000 --- a/lib/actor-pool.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var loopback = require('loopback'); -var async = require('async'); -var LRU = require('lru-cache'); -var config = require('../server/config.js'); -var MEMORY_POOL_SIZE = config.memoryPoolSize; -var log = require('oe-logger')('memory-pool'); -var actorsChunkSize = config.memoryPoolChunk; - -var actorEnvelopes = new LRU({ - max: MEMORY_POOL_SIZE, - dispose: function (key, value) { - log.info(log.defaultContext(), 'removing actor: ', key, ' from LRU'); - } -}); - -var PROCESS_QUEUE_INTERVAL; - -function getOrCreate(context, options, initCb) { - var modelName; - var id; - var idx; - - modelName = context.activity.modelName; - id = '' + context.activity.entityId; - idx = modelName + id; - var actorEntity = context.actorEntity; - if (!actorEnvelopes.get(idx)) { - var key = actorEntity._version; - actorEntity.constructor.instanceLocker().acquire(actorEntity, options, key, function (cb) { - if (!actorEnvelopes.get(idx)) { - var envelope = {}; - if (context.doNotDelete) { - envelope.doNotDelete = 1; - } else { - envelope.doNotDelete = 0; - } - envelope.actorId = id; - envelope.modelName = modelName; - if (options.ctx.noInstanceCache && actorEntity.constructor.settings.noBackgroundProcess) { - envelope.noCacheTime = Date.now(); - } - actorEntity.performStartOperation(context.journalEntityId, options, envelope, function (err) { - if (err) { - actorEnvelopes.del(idx); - return cb(err); - } - envelope.options = options; - actorEnvelopes.set(idx, envelope); - context.envelope = envelope; - return cb(); - }); - } else { - var envl = actorEnvelopes.get(idx); - if (context.doNotDelete) { - envl.doNotDelete++; - } - context.envelope = envl; - return cb(null, context); - } - }, function (err, ret) { - if (err) { - return initCb(err, context); - } - return initCb(null, context); - }); - } else { - var envelope = actorEnvelopes.get(idx); - if (context.doNotDelete) { - envelope.doNotDelete++; - } - if (envelope.noCacheTime && actorEntity.constructor.settings.noBackgroundProcess) { - if (Date.now() - envelope.noCacheTime < actorEntity.constructor.settings.noCacheTime) { - delete envelope.updatedActor; - envelope.msg_queue.forEach(message => {message.isProcessed = true;}); - envelope.msg_queue = []; - options.ctx.noInstanceCache = true; - } else { - delete envelope.noCacheTime; - delete options.ctx.noInstanceCache; - } - } else if (options.ctx.noInstanceCache && actorEntity.constructor.settings.noBackgroundProcess) { - envelope.noCacheTime = Date.now(); - delete envelope.updatedActor; - envelope.msg_queue.forEach(message => {message.isProcessed = true;}); - envelope.msg_queue = []; - } - context.envelope = envelope; - return initCb(null, context); - } -} - -function destroy(modelName, id) { - log.debug(log.defaultContext(), 'removing actor envelope from memory\n', 'model: ' + modelName, '\n', 'id: ', id); - var idx = modelName + id; - if (typeof actorEnvelopes.get(idx) !== 'undefined') { - actorEnvelopes.del(idx); - } -} - -function processMemoryMessages() { - var tasks = []; - - var currentEnvelopes = actorEnvelopes.values(); - - if (currentEnvelopes.length === 0) { - setTimeout(processMemoryMessages, PROCESS_QUEUE_INTERVAL); - return; - } - async.each(currentEnvelopes, function (actorEnvelope, cb) { - if (actorEnvelope.isCurrentlyProcessing === false && actorEnvelope.msg_queue.length > 0) { - tasks.push(function (asyncCB) { - if (actorEnvelope.isCurrentlyProcessing === true || actorEnvelope.msg_queue.length === 0) { - return asyncCB(); - } - actorEnvelope.isCurrentlyProcessing = true; - if (typeof actorEnvelope.actor !== 'undefined') { - actorEnvelope.actor.processMessagesBackground(actorEnvelope, actorEnvelope.options, function () { - actorEnvelope.isCurrentlyProcessing = false; - return asyncCB(); - }); - } else { - var actorModel = loopback.getModel(actorEnvelope.modelName, actorEnvelope.options); - var query = { where: { id: actorEnvelope.actorId }, limit: 1 }; - actorModel.find(query, actorEnvelope.options, function (err, result) { - if (err) { - logError(err); - actorEnvelope.isCurrentlyProcessing = false; - return asyncCB(err); - } else if (!result[0]) { - actorEnvelopes.del(actorEnvelope.modelName + actorEnvelope.actorId); - return asyncCB(); - } - var actor = result[0]; - actorEnvelope.actor = actor; - actor.processMessagesBackground(actorEnvelope, actorEnvelope.options, function () { - actorEnvelope.isCurrentlyProcessing = false; - return asyncCB(); - }); - }); - } - }); - } - return cb(); - }); - - async.parallelLimit(tasks, actorsChunkSize, function (err, results) { - logError(err); - setTimeout(processMemoryMessages, PROCESS_QUEUE_INTERVAL); - }); -} - - -function logError(err) { - if (err) { - log.error(log.defaultContext(), err); - } -} - - -function initWithCustomInterval(app) { - PROCESS_QUEUE_INTERVAL = app.get('memoryInterval') || config.memoryInterval; - setTimeout(processMemoryMessages, PROCESS_QUEUE_INTERVAL); -} - -module.exports.getOrCreateInstance = getOrCreate; -module.exports.destroy = destroy; - -module.exports.initPool = function initPool(app) { - initWithCustomInterval(app); -}; - -module.exports.getEnvelope = function getEnvelope(modelName, id) { - var envelope = actorEnvelopes.get(modelName + id); - return envelope; -}; - -module.exports.getEnvelopeAndReserve = function getEnvelope(modelName, id) { - var envelope = actorEnvelopes.get(modelName + id); - if (envelope !== null && typeof envelope !== 'undefined') { - envelope.doNotDelete++; - } - return envelope; -}; diff --git a/lib/auto-fields.js b/lib/auto-fields.js deleted file mode 100644 index fa22ebb..0000000 --- a/lib/auto-fields.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This module defines and exports a function called 'autofields' - * which takes two parameters - a string pattern and a callback function. - * The function retrieves the a value based on the specified pattern - * and returns this via the callback. - * - * This function is used in base-entity.js to automatically - * populate model fields declared to be auto-populated. - * @module EV Auto Fields - * @author Ajith Vasudevan - */ - -// var debug = require('debug')('auto-fields'); -var tfs = require('./tenant-util').tenantfns; -var logger = require('oe-logger'); -var log = logger('auto-fields'); - -var autofields = function AutoFields(p, options, cb) { - log.debug('Entered autofields', p); - if (p && p.pattern) { - var source; - var key; - var dotIndex = p.pattern.indexOf('.'); - if (dotIndex > -1) { - source = p.pattern.substring(0, dotIndex); - key = p.pattern.substring(dotIndex + 1); - } else { - source = p.pattern; - key = ''; - } - log.debug(options, 'source, key', source, key); - if (source && typeof key !== 'undefined' && key !== null) { - // We are not storing options.req any more as it may be costly - // so if you need anything from req - // probably best is to put in callContext - // and then use from callContext - // May be we can have a generic middleware to set CallContext - // from req - tfs[source](source, key, options, options.req, cb); - } - } -}; - -module.exports = autofields; diff --git a/lib/batchJob-runner.js b/lib/batchJob-runner.js deleted file mode 100644 index 7565d9e..0000000 --- a/lib/batchJob-runner.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('scheduler-runner'); -var request = require('request'); -var jwtGenerator = require('oe-jwt-generator'); -var server = require('../server/server'); -var schedulerHost = process.env.SCHEDULER_HOST || 'localhost'; -var schedulerPort = process.env.SCHEDULER_PORT || '3001'; -var schedulerPath = 'http://' + schedulerHost + ':' + schedulerPort + '/api/'; - -module.exports.processMsg = function processMsg(msg, callback) { - var batchJob = new BatchJob(msg, callback); - return batchJob.preRun(); -}; - -var BatchJob = function BatchJob(_msg, _callback) { - var message = _msg; - var finalCB = _callback; - - this.preRun = function preRun() { - var data = { - status: 'Processing', - runnerStartTime: new Date() - }; - updateJobMonitoring(message.monitoringId, data, () => runJob.bind(this)()); - }; - - var runJob = function runJob() { - var jobModel = message.jobModelName; - var Model = loopback.getModel(jobModel, message.options); - var jobFn = message.jobFnName; - var jobFnParams = message.jobFnParams; - jobFnParams.push(message.options); - // jobFnParams.push(msg.monitoringId); - // jobFnParams.push('null'); - jobFnParams.push(this.postJob.bind(this)); - try { - return Model.prototype[jobFn].apply(this, jobFnParams); - } catch (err) { - return this.postJob(err); - } - }; - - this.postJob = function finish(err) { - var data; - if (err) { - log.error(log.defaultContext, 'Batch job failed with error: ', err); - data = { - status: 'Failed Processing', - errorMsg: err.message, - runnerEndTime: new Date() - }; - } else { - log.info(log.defaultContext, 'Bath job finished.'); - data = { - status: 'Finished Processing', - runnerEndTime: new Date() - }; - } - updateJobMonitoring(message.monitoringId, data, finalCB); - }; - - var updateJobMonitoring = function (monitoringId, data, callback) { - jwtGenerator({}, server.app, (err, token) => { - if (err) { - return log.error(log.defaultContext(), 'error when trying to get access token for batch job scheduler, with error: ', err); - } - acquireMonitorVersion(token); - }); - - var acquireMonitorVersion = (token) => { - var requestOptions = { - url: schedulerPath + 'Monitorings/' + monitoringId, headers: {'x-jwt-assertion': token}, method: 'GET', json: true, body: {} - }; - request(requestOptions, (err, response, body) => { - if (err || body.error) { - log.debug(log.defaultContext(), 'Error in requesting monitor instance. error: ', err || body.error); - } - data._version = body ? body._version : null; - sendMonitoringUpdate(data, token); - }); - }; - - var sendMonitoringUpdate = (data, token) => { - var requestOptions = { - url: schedulerPath + 'Monitorings/' + monitoringId, headers: {'x-jwt-assertion': token}, method: 'PUT', json: true, body: data - }; - return request(requestOptions, (err, response, body) => { - if (err || body.error) { - log.debug(log.defaultContext(), 'Error in sending data to monitoring service. error: ', err || body.error); - } - callback(); - }); - }; - }; -}; diff --git a/lib/client-sdk.template b/lib/client-sdk.template deleted file mode 100644 index c316108..0000000 --- a/lib/client-sdk.template +++ /dev/null @@ -1,164 +0,0 @@ -var ClientSDK = (function () { - - var headers = {}; - - var routes = __ROUTES__; - - function getModel(modelName) { - var modelMethods = routes[modelName]; - if (!modelMethods) { - return; - } - var modelCtor = {}; - Object.keys(modelMethods).forEach(function (method) { - var url = modelMethods[method].url; - var verb = modelMethods[method].verb; - var functionArgs = __getFunctionArgs(modelMethods[method]); - modelCtor[method] = Function.apply(null, functionArgs); - }); - return modelCtor; - } - - function setHeader(key, value) { - headers[key] = value; - } - - function removeHeader(key) { - delete headers[key]; - } - - function __getQueryString(params) { - var queryParts = []; - var param; - var value; - - Object.keys(params).forEach(function (param) { - value = params[param]; - param = encodeURIComponent(param); - - if (Array.isArray(value)) { - for (var i = 0; i < value.length; i++) { - queryParts.push(param + '=' + encodeURIComponent(value[i])); - } - } else if (value !== null) { - queryParts.push(param + '=' + encodeURIComponent(value)); - } else { - queryParts.push(param); - } - }); - - return queryParts.join('&'); - } - - function __getProcessedArgs(fnArgs, optionalParams, argsList) { - var obj = {}; - var args = Array.prototype.slice.call(fnArgs); - obj.cb = args[args.length - 1]; - var requiredFieldsLength = argsList.length - optionalParams.length - 1; - var optionalArgsProvided = args.splice(requiredFieldsLength); - optionalArgsProvided.pop(); - optionalParams.forEach(function (arg, index) { - obj[arg] = optionalArgsProvided[index]; - }); - return obj; - } - - - function __getFunctionArgs(method) { - var funcTemplate = "var url = __URL__;\nvar queryStr = \"\";"; - var argsCheck = "var processed = ClientSDK.__getProcessedArgs(arguments, optionalParams, args)"; - var filterTmpl = "\nif(filter){\n\tqueryStr=ClientSDK.__getQueryString({\"filter\":JSON.stringify(filter)})\n}"; - var whereTmpl = "\nif(where){\n\tqueryStr=ClientSDK.__getQueryString({\"where\":JSON.stringify(where)})\n}"; - var serverCallTmpl = "\nif(queryStr){\n\turl = url + '?' + queryStr\n}\nClientSDK.makeAjaxCall(url,__METHOD__,__BODY__,cb);"; - - var accepts = method.accepts; - - funcTemplate = funcTemplate.replace('__URL__', __getURL(method.url)); - serverCallTmpl = serverCallTmpl.replace('__METHOD__', '"' + method.verb + '"'); - - var pathParams = method.accepts.filter(obj => obj.http && obj.http.source === 'path').map(obj => obj.arg); - var queryParams = accepts.filter(obj => !obj.http).map(obj => obj.arg); - var bodyParams = accepts.filter(obj => obj.http && obj.http.source === 'body').map(obj => obj.arg); - - if (bodyParams.length === 0) { - serverCallTmpl = serverCallTmpl.replace('__BODY__', 'null'); - } else { - serverCallTmpl = serverCallTmpl.replace('__BODY__', bodyParams[0]); - } - - var argsArr = [].concat(pathParams).concat(bodyParams).concat(queryParams); - argsArr.push('cb'); - - funcTemplate += '\nvar optionalParams = ' + JSON.stringify(queryParams) + ';\nvar args = ' + JSON.stringify(argsArr) + ';\n'; - funcTemplate += argsCheck; - - queryParams.forEach(function (param) { - funcTemplate += "\n" + param + "= processed['" + param + "'];\n"; - }); - funcTemplate += "\ncb = processed['cb'];\n" - - if (queryParams.length > 0) { - if (queryParams.indexOf('filter') != -1) - funcTemplate += filterTmpl; - if (queryParams.indexOf('where') != -1) - funcTemplate += whereTmpl; - } - - funcTemplate += serverCallTmpl; - - argsArr.push(funcTemplate); - //console.log('----------------------------'); - //console.log(funcTemplate); - //console.log('----------------------------'); - return argsArr; - } - - function __getURL(url) { - var funcUrl = '"' + url + '"'; - funcUrl = funcUrl.replace(/:(\w+)/g, function (x, y) { - return '"+' + y + '+"' - }); - return funcUrl; - } - - function makeAjaxCall(url, method, body, cb) { - method = method.toLowerCase(); - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function () { - if (xhr.status === 200 && xhr.readyState == 4 && xhr.response.length > 0) { - cb(null, xhr.response) - } - } - xhr.onerror = function () { - cb(err) - }; - xhr.open(method, url); - if (headers) { - headers["content-type"] = headers["content-type"] || "application/json"; - Object.keys(headers).forEach(function (k) { - var val = headers[k]; - if (Array.isArray(val)) { - xhr.setRequestHeader(k, val[0]); - } else { - xhr.setRequestHeader(k, val); - } - }) - } - if (method === 'get') { - xhr.send() - } else { - xhr.send(JSON.stringify(body)); - } - - } - - return { - getModel: getModel, - setHeader: setHeader, - removeHeader: removeHeader, - makeAjaxCall: makeAjaxCall, - __getQueryString: __getQueryString, - __getProcessedArgs: __getProcessedArgs - } - -})() \ No newline at end of file diff --git a/lib/common/broadcaster-client.js b/lib/common/broadcaster-client.js deleted file mode 100644 index 0a36894..0000000 --- a/lib/common/broadcaster-client.js +++ /dev/null @@ -1,261 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ - -var WebSocket = require('ws'); -var process = require('process'); -var dns = require('dns'); - -var logger = require('oe-logger'); -var log = logger('broadcaster-client'); -var EventEmitter = require('events'); - -// var clientId = os.hostname() + '.' + process.pid; -var emitter = new EventEmitter(); - -module.exports = { - init: init, - publish: publish, - subscribe: subscribe, - eventEmitter: emitter, - getUseBroadcaster: getUseBroadcaster -}; - -var readyState = {}; -readyState.CONNECTING = 0; -readyState.OPEN = 1; -readyState.CLOSING = 2; -readyState.CLOSED = 3; - -const REGISTER_NODE = 'A'; -const PUBLISH = 'B'; -// const ROUTER_SERVICE_REGISTER = 'C'; used in router Service - -const HEART_BEAT_INTERVAL = process.env.HEART_BEAT_INTERVAL || 6000; -var timer; - -var pendingData = []; -var ws; - -var broadcasterHost = process.env.BROADCASTER_HOST || 'localhost'; -var portNumber = process.env.BROADCASTER_PORT || 2345; - -var useBroadcaster = null; -var orchestrator = process.env.ORCHESTRATOR; - -function getUseBroadcaster() { - if (useBroadcaster !== null) return useBroadcaster; - - useBroadcaster = false; - var str = process.env.USE_BROADCASTER; - if (orchestrator || str) { - useBroadcaster = true; - } - - if (str === 'false') { - useBroadcaster = false; - } - - if (useBroadcaster) { - log.info(log.defaultContext(), 'Using broadcaster host ', broadcasterHost, ' port ', portNumber); - } - return useBroadcaster; -} - - -function trySend() { - if (ws && ws.readyState === readyState.OPEN) { - if (pendingData.length > 0) { - var data = pendingData.shift(); - try { - ws.send(data, function () { - if (pendingData.length > 0) { - process.nextTick(function () { - trySend(); - }); - } - }); - } catch (ex) { - pendingData.unshft(data); - setTimeout(trySend, 3000); - } - } - } else if (!ws) { - if (getUseBroadcaster()) { - init(); - } - } -} - -function setPingTimer() { - if (timer) { - clearTimeout(timer); - } - timer = setTimeout(function () { - if (ws) { - ws.terminate(); - } else { - log.info(log.defaultContext(), ' No WebSocket to terminate'); - } - ws = null; - log.info(log.defaultContext(), 'heart beat not recieved, trying to reconnect!'); - }, HEART_BEAT_INTERVAL); -} - -function init() { - if (ws || !getUseBroadcaster()) { - return; - } - if (timer) { - clearTimeout(timer); - } - ws = new WebSocket('ws://' + broadcasterHost + ':' + portNumber, { - perMessageDeflate: false - }); - - ws.on('open', function open() { - log.info(log.defaultContext(), 'connection opened to broadcaster call register node'); - registerNode(); - setPingTimer(); - }); - - ws.on('close', function () { - log.info(log.defaultContext(), 'connection closed to broadcaster'); - // if broadcaster itself goes down, no need to check ping - if (timer) { - clearTimeout(timer); - } - ws = null; - if (getUseBroadcaster()) { - setTimeout(init, 3000); - } - }); - - ws.on('message', function (buf) { - var data = JSON.parse(buf); - process.nextTick(function () { - emitter.emit(data.topic, data.msg, data.context); - }); - }); - - ws.on('error', function (e) { - if (e.code === 'ECONNREFUSED') { - log.error(log.defaultContext(), 'could not connect to broadcaster service'); - } else { - log.error(log.defaultContext(), 'broadcaster client error ', e); - } - if (timer) { - clearTimeout(timer); - } - if (ws) { - ws.close(); - ws = null; - } - if (getUseBroadcaster()) { - setTimeout(init, 3000); - } - }); - - ws.on('ping', function () { - log.debug(log.defaultContext(), 'ping recieved from server'); - setPingTimer(); - }); -} - -function publish(topic, msg, context) { - if (!ws) { - init(); - } - var data = { - topic: topic, - msg: msg, - context: context - }; - var bufferString = PUBLISH + JSON.stringify(data); - var buf = Buffer.from(bufferString); - pendingData.push(buf); - trySend(); -} - -function subscribe(topic, cb) { - emitter.on(topic, function (message, context) { - cb(message, context); - }); -} - -function registerNode() { - var nodeDetails = {}; - - switch (orchestrator) { - case 'PCF': break; - case 'OpenShift': break; - case 'dockerSwarm': - // docker swarm - nodeDetails.port = process.env.SERVICE_PORT || process.env.PORT; - nodeDetails.hostname = process.env.HOSTNAME; - nodeDetails.serviceName = process.env.SERVICE_NAME; - log.info(log.defaultContext(), 'trying to get IP'); - getIP(function (err, ip) { - if (err) { - log.error(log.defaultContext(), 'error in broadcast client getIP ', err); - trySend(); - } - nodeDetails.ip = ip; - var bufferString = REGISTER_NODE + JSON.stringify(nodeDetails); - var buf = Buffer.from(bufferString); - log.info(log.defaultContext(), 'broadcaster register details: ', nodeDetails); - pendingData.push(buf); - trySend(); - }); - break; - default : - // for dev testing use 127.0.0.1 - nodeDetails.port = process.env.SERVICE_PORT || process.env.PORT; - nodeDetails.hostname = process.env.HOSTNAME; - nodeDetails.serviceName = process.env.SERVICE_NAME; - nodeDetails.ip = '127.0.0.1'; - var bufferString = REGISTER_NODE + JSON.stringify(nodeDetails); - var buf = Buffer.from(bufferString); - log.info(log.defaultContext(), 'broadcaster register details: ', nodeDetails); - pendingData.push(buf); - trySend(); - break; - } -} - -function getIP(cb) { - var os = require('os'); - var routerHost = process.env.ROUTER_HOST || 'router'; - var error; - dns.lookup(routerHost, {}, function (err, routerIP, family) { - if (err) { - cb(err); - } - if (family === 6) { - error = new Error('router service discovery does not support IPV6 yet'); - return cb(error); - } - var ipArray = routerIP.split('.'); - ipArray.splice(ipArray.length - 1, 1, '[0-9]{1,3}'); - var matchIP = new RegExp(ipArray.join('\\.')); - var interfaces = os.networkInterfaces(); - for (var key in interfaces) { - if (interfaces.hasOwnProperty(key)) { - var networkInterface = interfaces[key]; - for (var i = 0; i < networkInterface.length; i++) { - var networkAddress = networkInterface[i]; - var ip = networkAddress.address; - if (networkAddress.family === 'IPv4' && networkAddress.netmask !== '255.255.255.255' && matchIP.exec(ip) ) { - return cb(null, ip); - } - } - } - } - error = new Error('Router network interface not found'); - cb(error); - }); -} - diff --git a/lib/common/ev-global-messaging.js b/lib/common/ev-global-messaging.js deleted file mode 100644 index be6358e..0000000 --- a/lib/common/ev-global-messaging.js +++ /dev/null @@ -1,15 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ - -// There will be 3 implementations of messaging, using mqtt, multicast, and simple broadcast using web sockets, but all should be optional and not part of ev-foundation module -var globalMessaging = require('./broadcaster-client'); -globalMessaging.init(); - -module.exports = { - publish: globalMessaging.publish, - subscribe: globalMessaging.subscribe -}; diff --git a/lib/common/global-messaging.js b/lib/common/global-messaging.js deleted file mode 100644 index 592cac7..0000000 --- a/lib/common/global-messaging.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -// There will be 3 implementations of messaging, using mqtt, multicast, and simple broadcast using web sockets, but all should be optional and not part of framework module -var globalMessaging = require('./broadcaster-client'); - -globalMessaging.init(); - -module.exports = { - publish: globalMessaging.publish, - subscribe: globalMessaging.subscribe -}; - diff --git a/lib/common/property-expression-utils.js b/lib/common/property-expression-utils.js deleted file mode 100644 index f675585..0000000 --- a/lib/common/property-expression-utils.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * - * - * @module EV Property expression utils - */ - -var logger = require('oe-logger'); -var log = logger('property-expression-utils'); -module.exports = { - propertyExpressions: propertyExpressions -}; - - -function propertyExpressions(model) { - var propertyExpressionsArr = []; - var properties = model.definition.properties; - log.debug(log.defaultContext(), 'in property experssion util:', model.definition.name); - Object.keys(properties).forEach(function propertiesForEachCb(propertyName) { - Object.keys(properties[propertyName]).forEach(function propertyNameForEachCb(key) { - // check if model property has key propExpression and add to array for mixin evaluation - if (key === 'propExpression') { - propertyExpressionsArr.push({ - propExpression: properties[propertyName].propExpression, - name: propertyName - }); - } - }); - }); - return propertyExpressionsArr; -} diff --git a/lib/common/util.js b/lib/common/util.js index d1f6bda..08ed503 100644 --- a/lib/common/util.js +++ b/lib/common/util.js @@ -1,771 +1,214 @@ /** * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * �2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), * Bangalore, India. All Rights Reserved. * */ -/** - * @module oeCloud Utils - * @author Ramesh Choudhary, Atul - */ - -var _ = require('lodash'); -var loopback = require('loopback'); -var logger = require('oe-logger'); -var log = logger('util'); -var async = require('async'); -var validationUtils = require('./validation-utils'); -var applicableValidations = validationUtils.applicableValidations; -var validationExpressionMapper = validationUtils.validationExpressionMapper; -var exprLang = require('../expression-language/expression-language.js'); -var getValidationError = require('./error-utils').getValidationError; -var Mustache = require('mustache'); -var invalidPropNames = ['isvalid']; -var app = require('../../server/server.js').app; -var uuidv4 = require('uuid/v4'); -var inflection = require('inflection'); -module.exports.version = require('../../package.json').version; - -module.exports.bootContext = function bootContext() { - return { - ignoreAutoScope: true, - bootContext: true, - ctx: { - remoteUser: 'system' - } - }; -}; - -module.exports.ignoreScopeContext = function bootContext() { - return { fetchAllScopes: true, ctx: { tenantId: 'default' } }; -}; - -function getApp() { - if (app) return app; - app = require('../../server/server.js').app; - if (app) return app; - return setTimeout(getApp, 1000); -} - -// Atul : This is very bad way to find actual model with plural. -// Once it is proven, i will check on better logic -/** - * - * - * This function returns overriden model or in other words personalized model - * As of now this function is written in switch-datasource-mixin.js file because logic to find overriden model can be easily reused. - * As of now this function is async and requires callback. When logic of data source personalization is reused, it can be made with sync call - * this will be changed in future till all scenarios are tested. there could be some overhead as of now. - * @param {string} model - options - * @function - */ -var attachOverrideModelFunction = module.exports.attachOverrideModelFunction = function attachOverrideModelFunctionFn(model) { - model.getOverridenModel = function getOverridenModelFn(modelName, options) { - var verifyModel = true; - if (!(modelName && typeof (modelName) === 'string')) { - options = modelName; - modelName = this.modelName; - verifyModel = false; - } - if (!options) { - options = {}; - } - var model = loopback.findModel(modelName, options); - if (model) { - return model; - } - if (verifyModel && !model) { - return null; +// Author : Atul +// This file contains many utility functions that are either created by/for oe-cloud or they are overriden/copied from +// loopback code. all functions are small and self-explanatory +const utils = require('loopback-datasource-juggler/lib/utils'); +module.exports.isBaseEntity = function (model) { + return model.settings.isBaseEntity === true; +}; + +function mergeRecursive(obj1, obj2) { + // var o = Object.assign({}, obj1); + var keys = Object.keys(obj2); + keys.forEach( (key, index) => { + if (!obj1.hasOwnProperty(key) || typeof obj1[key] !== 'object') { + if (typeof obj2[key] === 'object') { + if (!Array.isArray(obj2[key])) {obj1[key] = Object.assign({}, obj2[key]);} else {obj1[key] = obj2[key].slice();} + } else { + obj1[key] = obj2[key]; + } + } else if (!Array.isArray(obj1[key])) { + mergeRecursive(obj1[key], obj2[key]); + } else { + obj1[key] = obj1[key].concat(obj2[key]); } - return this; - }; -}; - -module.exports.checkModelWithPlural = function utilCheckModelWithPlural(server, plural) { - var models = server.models(); - var res = models.find(function (ele) { - return (ele.clientPlural && ele.clientPlural === plural) || ele.pluralModelName === plural; }); +} - return res ? res.clientPlural ? res.clientModelName : res.modelName : null; -}; - - -// Create the Model in Loopback corresponding to the specified ModelDefinition ID -module.exports.createModel = function utilCreateModel(app, modeldefinition, options, cb) { - // Create the model - - // Removing null fields to make compactable with mongo - var jsonifyModel = JSON.parse(JSON.stringify(modeldefinition)); - for (var i in jsonifyModel) { - if (jsonifyModel[i] === null) { - delete jsonifyModel[i]; - } - } - var modelDefinition = loopback.getModel('ModelDefinition', options); - var autoscopeFields = modelDefinition.definition.settings.autoscope; - var ctxStr = ''; - // jsonifyModel.variantOf = jsonifyModel.variantOf || jsonifyModel.name; - - var actualContext = options; - if (options.bootContext) { - const modelCtx = modeldefinition._autoScope; - ctxStr = createContextString(autoscopeFields, modelCtx); - actualContext = { - ctx: modelCtx - }; - } else { - ctxStr = createContextString(autoscopeFields, options.ctx); - } - - if (jsonifyModel.base) { - let baseModel = loopback.getModel(jsonifyModel.base, actualContext); - jsonifyModel.base = baseModel.modelName; +module.exports.mergeObjects = function (obj1, obj2) { + if (typeof obj1 === 'undefined') { + obj1 = Object.assign({}, obj2); + return obj1; } + mergeRecursive(obj1, obj2); + return obj1; +}; - - if (!getApp().personalizedModels[jsonifyModel.name]) { - getApp().personalizedModels[jsonifyModel.name] = {}; - } - if (ctxStr === createDefaultContextString(autoscopeFields)) { - getApp().personalizedModels[jsonifyModel.name][ctxStr] = { - 'modelId': jsonifyModel.name, - 'context': ctxStr - }; - // console.log("***", jsonifyModel.clientModelName) - } else { - getApp().personalizedModels[jsonifyModel.name][ctxStr] = { - 'modelId': jsonifyModel.modelId, - 'context': ctxStr - }; - jsonifyModel.name = jsonifyModel.modelId; - // console.log("***", jsonifyModel.clientModelName) - jsonifyModel.plural = createPlural(jsonifyModel.name); +/* eslint-disable no-loop-func */ +module.exports.checkDependency = function (app, modules) { + var appList = app.appList || app.applist; + if (!appList) { + return new Error('Dependency not met. Found applist null. Check app-list.json'); } - - if (ctxStr !== createDefaultContextString(autoscopeFields)) { - if (jsonifyModel.relations) { - Object.keys(jsonifyModel.relations).forEach(function (r) { - jsonifyModel.relations[r].clientModel = jsonifyModel.relations[r].model; - if (!getApp().models[jsonifyModel.relations[r].clientModel] && createContextString(autoscopeFields, actualContext.ctx) !== createDefaultContextString(autoscopeFields)) { - jsonifyModel.relations[r].model = jsonifyModel.relations[r].model + '-' + createContextString(autoscopeFields, actualContext.ctx); - } - }); - } - let typeList = ['string', 'String', 'number', 'Number', 'date', 'Date', 'DateString', 'boolean', 'Boolean', 'object', 'Object', 'email', 'Email', 'timestamp', 'Timestamp', 'buffer', 'GeoPoint', 'any', 'array', null]; - if (jsonifyModel.properties) { - Object.keys(jsonifyModel.properties).forEach(function (p) { - if (typeList.indexOf(jsonifyModel.properties[p].type) === -1 && typeof (jsonifyModel.properties[p].type) === 'string') { - var embeddedModelName = jsonifyModel.properties[p].type; - if (!getApp().models[embeddedModelName]) { - jsonifyModel.properties[p].type = jsonifyModel.properties[p].type + '-' + createContextString(autoscopeFields, actualContext.ctx); - } - } else if (typeList.indexOf(jsonifyModel.properties[p].type) === -1 && Array.isArray(jsonifyModel.properties[p].type)) { - var embeddedType = jsonifyModel.properties[p].type[0]; - if (typeList.indexOf(embeddedType) === -1 && !getApp().models[embeddedType]) { - jsonifyModel.properties[p].type[0] = jsonifyModel.properties[p].type[0] + '-' + createContextString(autoscopeFields, actualContext.ctx); - } - } + if (Array.isArray(modules)) { + for (var i = 0; i < modules.length; ++i) { + var n = appList.find((a) => { + if (a.path.toLowerCase() === modules[i]) {return true;} + return false; }); + if (!n) {return false;} } + return true; } - - var model = loopback.createModel(jsonifyModel); - model.updateId = uuidv4(); - model.variantOf = jsonifyModel.variantOf; - model.clientPlural = jsonifyModel.clientPlural; - model.clientModelName = jsonifyModel.clientModelName; - attachOverrideModelFunction(model); - - // Setting dynamic model set to true - model.settings = model.settings || {}; - model.settings._dynamicModel = true; - - // var model = loopback.createModel(JSON.parse(JSON.stringify(modeldefinition))); - // console.log("************", jsonifyModel.clientModelName); - getApp().locals.modelNames[model.clientModelName.toLowerCase()] = model.clientModelName; - getApp().locals.modelNames[model.clientModelName] = model.clientModelName; - // Attach it to the datasource - - // Will return the datasource attached to the parent of the model - // as no mixins have been attached yet - var ds = model.getDataSource(options); - if (ds) { - // Mixins get attached at this step - ds.attach(model); - } else { - ds = jsonifyModel.datasource ? getApp().dataSources[jsonifyModel.datasource] : getApp().dataSources.db; - ds.attach(model); - } - - - // disable change-stream - model.disableRemoteMethod('createChangeStream', true); - // Enable REST API - getApp().model(model); - - log.debug(options, 'DEBUG: lib/common/util.js: Model loaded from database : ', - modeldefinition.name); - - var datasources = getApp().datasources; - - var relationalDataSources = {}; - Object.keys(datasources).forEach(function fnRelDS(dsId) { - var ds = datasources[dsId]; - if (ds && ds.isRelational()) { - relationalDataSources[ds.settings.name] = ds; - } + var m = appList.find((a) => { + if (a.path.toLowerCase() === modules) {return true;} + return false; }); + return m; +}; +/* eslint-enable no-loop-func */ - var dsModelMapping = []; +function idName(m) { + return m.definition.idName() || 'id'; +} +function getIdValue(m, data) { + return data && data[idName(m)]; +} - // Will return the actual dataSource as the mixins have been attached now - var mds = model.getDataSource(options); - if (mds) { - var dsName = mds.settings.name; - if (relationalDataSources.hasOwnProperty(dsName)) { - dsModelMapping.push({ - modelName: model.modelName, - ds: relationalDataSources[dsName] - }); - } +const BASE_ENTITY = 'BaseEntity'; +// utility function to climb up inheritance tree +// and execute callback on each hit. This will hit +// all models except BaseEntity +const inheritanceTraverse = function (model, ctx, cb) { + if (model.base.modelName !== BASE_ENTITY && cb(model.base)) { + // do nothing + return; + } else if (model.base.modelName === BASE_ENTITY) { + return; } - cb(); -}; -// Delete the LB DataSource corresponding to the specified DataSourceDefinition -module.exports.deleteDatasource = function utilDeleteDataSource(app, dsdefinition) { - // getApp().datasources[dsdefinition.name] = undefined; - // if we make this undefined, models are still attached to this - // and this will crash system, why we need to allow datasource defn - // may be soft delete and do not pick next time when system starts - log.debug(log.defaultContext(), 'delete data source may be we need to close connection'); + inheritanceTraverse(model.base, ctx, cb); }; -// create datasource -module.exports.createDataSource = function createDataSource(app, dsdefinition, options) { - // Create the LoopBack datasource using the details in the dsdefinition - try { - var dataSource = loopback.createDataSource(dsdefinition); - getApp().datasources[dsdefinition.id] = dataSource; - log.debug(log.defaultContext(), 'created real ds ', dsdefinition.name); - getApp().locals.dataSources = getApp().locals.dataSources || {}; - if (getApp().locals.dataSources[dsdefinition.name]) { - delete getApp().locals.dataSources[dsdefinition.name]; - } - } catch (err) { - log.error(options, err); +module.exports.traverseInheritanceTree = inheritanceTraverse; +const _ = require('lodash'); +function idFound(idField, p) { + if (!p || _.isEmpty(p)) { + return false; } -}; - -module.exports.createPromiseCallback = function createPromiseCallback() { - var cb; - - if (!global.Promise) { - cb = function emptyFn() { }; - cb.promise = {}; - Object.defineProperty(cb.promise, 'then', { - get: throwPromiseNotDefined - }); - Object.defineProperty(cb.promise, 'catch', { - get: throwPromiseNotDefined - }); - return cb; + if (p[idField]) { + return p[idField]; } + return false; +} - var promise = new Promise(function promiseFn(resolve, reject) { - cb = function cbFn(err, data) { - if (err) { - return reject(err); +function checkAndClause(idField, p) { + if (p && p.and && Array.isArray(p.and)) { + var i; + var v; + var f; + for (i = 0; i < p.and.length; ++i) { + v = p.and[i]; + f = idFound(idField, v); + if (f !== false) { + return true; } - return resolve(data); - }; - }); - cb.promise = promise; - return cb; -}; - -// checks the validity of a model -module.exports.isModelDefinitionValid = function isModelDefinitionValid(modeldefinition, options, cb) { - var fnArr = []; - // aggregate all the functions to evaluate the validity of a model - fnArr = fnArr.concat(fnArr, getPropertyValidityFns(modeldefinition, options), getOeValidationValidityFns(modeldefinition, options)); - // execute all the functions paralelly which will evaluate the validity of the model - async.parallel(fnArr, function modelDefinitionValidationCb(err, results) { - var errArr = []; - if (err) { - errArr.push(err); - return cb(errArr); - } - // results = [].concat.apply([], results); - errArr = results.filter(function errorFilterCb(d) { - return d !== null && typeof d !== 'undefined'; - }); - // if model validity falis the passs the error - if (errArr && errArr.length > 0) { - cb(errArr); - } else { - cb(); } - }); -}; - -function throwPromiseNotDefined() { - var err = new Error( - 'Your Node runtime does support ES6 Promises. ' + - 'Set "global.Promise" to your preferred implementation of promises.'); - err.retriable = false; - throw err; -} - -/** - * - * Aggregation of expressions(mustache queries and expression language expressions) attached to all the oeValidations object of the model - * @param {Object} modeldefinition - model definition for which expressions are to be aggregated - * @param {Object} options - call context options. - * @returns {Object[]} Array containing all the functions that will evaluate the expressions present in oeValidations - */ -function getOeValidationValidityFns(modeldefinition, options) { - var expr = {}; - var evRulesFn = []; - var validations = modeldefinition.oeValidations || {}; - log.debug(options, 'aggregating oeValidation validation rules for : ', modeldefinition.name); - Object.keys(validations).forEach(function modelDefinitionValidationsForEachCb(validationName) { - var validation = validations[validationName]; - var path = modeldefinition.name + '->' + validationName + '->'; - // pick the respective validation function according to the type of rule e.g. 'reference' or 'custom' - var expression = validationExpressionMapper.oeValidation(validation.type); - if (expression) { - var validateWhenRule = null; - if (validation.validateWhen) { - // validateWhen takes a string in case of ev validations if validateWhen is an object then nake the rule null - if (typeof validation.validateWhen === 'object') { - validateWhenRule = null; - } else { - // pick the validateWhen condition if present for the rule - validateWhenRule = validation.validateWhen; - expr = { - rule: validateWhenRule, - type: 'validateWhen', - path: path + 'validateWhen' - }; - // this wrapper prepares an array of functions to evaluate the validity of a model - evRulesFn.push(async.apply(expressionCheck, expr, options)); + for (i = 0; i < p.and.length; ++i) { + v = p.and[i]; + if (v && v.and && Array.isArray(v.and)) { + f = checkAndClause(idField, v); + if (f !== false) { + return true; } } - // pick the validation rule i.e. refWhere in case of reference type rule or expression in case of custom type rule - expr = { - rule: validation.expression || validation.refWhere, - type: validation.type, - path: path + 'rule' - }; - // this wrapper prepares an array of functions to evaluate the validity of a model - evRulesFn.push(async.apply(expressionCheck, expr, options)); } - }); - return evRulesFn; + } + return false; } -/** - * - * Aggregation of expressions attached to all the properties of the model - * @param {Object} modeldefinition - modeldefinition for which expressions are to be aggregated - * @param {Object} options - call context options. - * @returns {Object[]} Array containing all the functions that will evaluate the expressions present at property level of the model and all the property names - */ -function getPropertyValidityFns(modeldefinition, options) { - var expr = {}; - var propRulesFn = []; - var properties = modeldefinition.properties || {}; - log.debug(options, 'aggregating property level rules for : ', modeldefinition.name); - Object.keys(properties).forEach(function propertiesForEachCb(propertyName) { - var path = modeldefinition.name + '->' + propertyName; - expr = { - rule: propertyName, - type: 'invalidName', - path: path - }; - // this wrapper prepares an array of functions to evaluate the validity of a model - propRulesFn.push(async.apply(propertyNameCheck, expr, options)); - var propertyType = properties[propertyName].type; - var type = 'default'; - if (propertyType instanceof Function && propertyType.sharedClass) { - type = 'object'; - } else if (propertyType instanceof Array && propertyType[0] && propertyType[0].sharedClass) { - type = 'array'; - } else if (Object.keys(validationUtils.applicableValidations).indexOf(propertyType) > 0) { - type = propertyType.toLowerCase(); - } - Object.keys(properties[propertyName]).forEach(function propertyNameForEachCb(key) { - if (applicableValidations[type].indexOf(key) >= 0) { - // pick the respective validation function according to the type of rule e.g. 'min', 'max', 'unique', etc. - var expression = validationExpressionMapper[key]; - if (typeof expression === 'object') { - expression = expression[properties[propertyName][key].toString()]; - } - if (expression) { - var validateWhenRule = null; - if (properties[propertyName].validateWhen && properties[propertyName].validateWhen[key]) { - // pick the validateWhen condition if present for the rule - validateWhenRule = properties[propertyName].validateWhen[key]; - var expr = { - rule: validateWhenRule, - type: 'validateWhen', - path: path + '->' + 'validateWhen' + '->' + key - }; - // this wrapper prepares an array of functions to evaluate the validity of a model - propRulesFn.push(async.apply(expressionCheck, expr, options)); - } - } - } - }); - }); - return propRulesFn; -} - -/** - * - * Checks the name of a property, if it is a valid name or not - * @param {object} propObj - it contains name of the property, type and path - * @param {Object} options - call context options. - * @param {function} cb - callback function - */ -function propertyNameCheck(propObj, options, cb) { - // rule is the property name - if (!isPropertyNameValid(propObj.rule)) { - var obj = {}; - obj.path = propObj.path; - obj.options = options; - getValidationError(getErrorCode(propObj.type), obj, function propertyNameErrorCb(error) { - cb(null, error); - }); - } else { - cb(null, null); +const loopback = require('loopback'); +function isInstanceQuery(Model, where) { + if (typeof Model === 'string') { + Model = loopback.findModel(Model); } -} - -/** - * - * Compares the name of the property with invalid values - * @param {string}propertyName - name of the property - * @returns {boolean} true if name of the model is valid else false - */ -function isPropertyNameValid(propertyName) { - var propertyNameValidity = true; - if (invalidPropNames.indexOf(propertyName.toLowerCase()) > -1) { - propertyNameValidity = false; + var idField = idName(Model); + if (!where || _.isEmpty(where)) { + return false; } - return propertyNameValidity; -} - -/** - * - * Checks the expression attached to the model, if it is valid or not - * @param {object} expression - it contains expression to be checked, its type and path - * @param {Object} options - call context options. - * @param {function} cb - callback function - */ -function expressionCheck(expression, options, cb) { - if (!isExpressionValid(expression.rule, expression.type, expression.path)) { - var obj = {}; - obj.path = expression.path; - obj.options = options; - getValidationError(getErrorCode(expression.type), obj, function expressionErrorCb(error) { - cb(null, error); - }); - } else { - cb(null, null); + var p = where; + if (p.where) { + p = p.where; } -} - -/** - * - * Compares the name of the property with restricted value - * @param {string} queryExpression - expression to be checked - * @param {string} type - type of the expression(validateWhen, expression, mustache query) - * @param {string}path - path where the expression is defined in the model definition - * @returns {boolean} true if expression attached to the model is valid else false - */ -function isExpressionValid(queryExpression, type, path) { - var expressionValidity = true; - switch (type) { - case 'reference': - try { - var refWhere = Mustache.render(queryExpression, {}); - var refArray = refWhere.split(';'); - if (refArray[refArray.length - 1] === '') { - refArray = refArray.slice(0, -1); - } - refArray.forEach(function referenceForEachCb(ref) { - JSON.parse(ref); - }); - } catch (e) { - log.error(log.defaultContext(), 'checking validity of mustache query passed in Validation : ', path); - expressionValidity = false; - } - break; - case 'validateWhen': - case 'custom': - try { - exprLang.createAST(queryExpression); - } catch (e) { - log.error(log.defaultContext(), 'checking validity of expression language query passed in Validation : ', path); - expressionValidity = false; - } - break; - default: + if (idFound(idField, p)) { + return true; } - return expressionValidity; -} -/** - * - * get the error code depending on the type of validation/rule - * @param {String} type - type of validation rule - * @returns {String} error code - */ -function getErrorCode(type) { - // default error details - var errDetails = getApp().errorDetails; - var err = errDetails.filter(function getErrorCodeCb(d) { - return d.type === type; - }); - return err[0].code; -} + var temp = checkAndClause(idField, p); + if ( temp !== false ) { + return temp; + } -/** - * This function returns the plural form of specified input word - * - * @param {string} name - The word whose plural is to be returned - * @return {string} The plural of the specified word - */ -function createPlural(name) { - return inflection.pluralize(name); + return false; } -var createContextString = (autoscope = [], context) => { - var ctxStr = []; - autoscope.forEach(function (item, index) { - var ctxVal = context[item]; - if (!ctxVal) { - ctxVal = 'default'; - } - ctxStr.push(ctxVal); - }); - return ctxStr.join('-'); -}; - -var createDefaultContextString = (autoscope = []) => { - const defaultStr = []; - autoscope.forEach(function (item) { - defaultStr.push('default'); - }); - return defaultStr.join('-'); -}; - -var createModelId = (modelName, contextString, autoscopeFields) => { - if (contextString === createDefaultContextString(autoscopeFields)) { - return modelName; +function _stillConnecting(dataSource, obj, args) { + if (typeof args[args.length - 1] === 'function') { + return dataSource.ready(obj, args); } - return modelName + '-' + contextString; -}; - -module.exports.createContextString = createContextString; -module.exports.createDefaultContextString = createDefaultContextString; -module.exports.createModelId = createModelId; - - -var getFileBasedModelSettings = (model) => { - var modelDefinitionObject = JSON.parse(JSON.stringify(model.definition.settings)); - modelDefinitionObject.filebased = true; - // _ownDefinition is set in juggler - var ownDefinition = model._ownDefinition || {}; - modelDefinitionObject.properties = !_.isEmpty(ownDefinition) ? ownDefinition.properties : {}; - - return modelDefinitionObject; -}; - -module.exports.getFileBasedModelSettings = getFileBasedModelSettings; - -// This function returns all application routes and their configuration to craete client side sdk. -module.exports.getRoutes = function getRoutes(server) { - var remotes = server.remotes(); - var adapter = remotes.handler('rest').adapter; - var routes = adapter.allRoutes(); - var classes = remotes.classes(); - var restApiRoot = server.get('restApiRoot'); - - var result = {}; + // promise variant + var promiseArgs = Array.prototype.slice.call(args); + promiseArgs.callee = args.callee; + var cb = utils.createPromiseCallback(); + promiseArgs.push(cb); + if (dataSource.ready(obj, promiseArgs)) { + return cb.promise; + } + return false; +} - routes.forEach(function routesMapFn(route) { - if (!route.documented) { - return; +function _invokeConnectorMethod(connector, method, Model, args, options, cb) { + var dataSource = Model.getDataSource(); + // If the DataSource is a transaction and no transaction object is provide in + // the options yet, add it to the options, see: DataSource#transaction() + var opts = dataSource.isTransaction && !options.transaction ? Object.assign( + options, {transaction: dataSource.currentTransaction} + ) : options; + var optionsSupported = connector[method].length >= args.length + 3; + var transaction = opts.transaction; + if (transaction) { + if (!optionsSupported) { + return process.nextTick(function () { + cb(new Error( + 'The connector does not support {{method}} within a transaction', method)); + }); } - - // Get the class definition matching this route. - var className = route.method.split('.')[0]; - var classDef = classes.filter(function clasesFilter(item) { - return item.name === className; - })[0]; - - if (!classDef) { + // transaction isn't always a Transaction instance. Some tests provide a + // string to test if options get passed through, so check for ensureActive: + if (transaction.ensureActive && !transaction.ensureActive(cb)) { return; } - var accepts = route.accepts || []; - var split = route.method.split('.'); - - if (classDef && classDef.sharedCtor && - classDef.sharedCtor.accepts && split.length > 2) { - accepts = accepts.concat(classDef.sharedCtor.accepts); - } - - // Filter out parameters that are generated from the incoming request, - // or generated by functions that use those resources. - accepts = accepts.filter(function acceptsFilter(arg) { - if (!arg.http) { - return true; - } - // Don't show derived arguments. - if (typeof arg.http === 'function') { - return false; - } - // Don't show arguments set to the incoming http request. - // Please note that body needs to be shown, such as User.create(). - if (arg.http.source === 'req' || - arg.http.source === 'res' || - arg.http.source === 'context') { - return false; - } - return true; - }); - - var methodName = route.method; - methodName = methodName.substring(methodName.indexOf('.') + 1); - - methodName = methodName.replace(/^prototype\.__([a-z]+)([a-zA-Z]{0,})__([a-zA-Z0-9_]+)/, function (g1, g2, g3, g4) { - return g2 + capitalizeFirstLetter(g4) + g3; - }); - - methodName = methodName.replace(/prototype./, ''); - - route.accepts = accepts; - route.verb = route.verb; - - var output = { - url: restApiRoot + route.path, - verb: route.verb, - description: route.description, - accepts: route.accepts - }; - - result[className] = result[className] || {}; - result[className][methodName] = output; - }); - return result; -}; - -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -/** - * Checking the Model base chain is a PersistedModel or not. - * @param {*} model - Loopback Model. - * @param {*} options - Options to be passed to find the model. - * @returns {boolean} - Returns 'true' or 'false' - */ -const isBaseChainPersistedModel = (model, options) => { - if (model && model.base && model.base.modelName && model.base.modelName !== 'Model') { - // Checking the Model is 'PersistedModel' or not. - if (model.base.modelName === 'PersistedModel') { - return true; - } - // Get the new Model from the base. - let newModel = loopback.findModel(model.base.modelName, options); - return isBaseChainPersistedModel(newModel, options); } - return false; -}; - -module.exports.isBaseChainPersistedModel = isBaseChainPersistedModel; - -/** - * Function to Check and Perform(call) datasource.autoupdate for creating indexes. - * @param {*} model - Loopback Model. - * @param {*} options - Options to be passed to find the model. - * @param {*} callback - Optional Callback if need to be called with error first - */ -const checkAndDoAutoUpdate = (model, options, callback) => { - var ds = model.getDataSource(options); - log.debug(options, 'Performing autoupdate on model "', model.modelName, '"'); - if (ds) { - ds.autoupdate(model.modelName, function (err, result) { - if (err) { - log.error(options, 'ds.autoupdate for model="', model.modelName, '" Error: ', err); - return callback && callback(err); - } - // Checking for history model - if (model.definition.settings.mixins && model.definition.settings.mixins.HistoryMixin && model._historyModel) { - var historyModel = model._historyModel; - var histDs = historyModel.getDataSource(options); - if (histDs) { - histDs.autoupdate(historyModel.modelName, function (err, result) { - if (err) { - log.error(options, 'ds.autoupdate for history model="', historyModel.modelName, '" Error: ', err); - return callback && callback(err); - } - callback && callback(); - }); - } else { - log.warn(options, 'Unable to get datasource for history model - ', historyModel.name); - callback && callback(); - } - } else { - log.debug(options, 'No history model for model "', model.modelName, '"'); - callback && callback(); - } - }); + var modelName = Model.modelName; + var fullArgs; + if (!optionsSupported && method === 'count') { + // NOTE: The old count() signature is irregular, with `where` coming last: + // [modelName, cb, where] + var where = args[0]; + fullArgs = [modelName, cb, where]; } else { - log.warn(options, 'Unable to get datasource for model - ', model.modelName); - callback && callback(); - } -}; - -module.exports.checkAndDoAutoUpdate = checkAndDoAutoUpdate; - - -const BASE_ENTITY = 'BaseEntity'; -// utility function to climb up inheritance tree -// and execute callback on each hit. This will hit -// all models except BaseEntity -const inheritanceTraverse = function (model, ctx, cb) { - // var m = model; - - // if (m.modelName === BASE_ENTITY) { - // return; //do nothing - // } - // else { - // if (cb(m.base)) { - // return; - // } - // else { - // inheritanceTraverse(m.base, ctx, cb); - // } - // } - - if (model.base.modelName !== BASE_ENTITY && cb(model.base)) { - // do nothing - return; - } else if (model.base.modelName === BASE_ENTITY) { - return; + // Standard signature: [modelName, ...args, (opts, ) cb] + fullArgs = [modelName].concat(args); + if (optionsSupported) { + fullArgs.push(opts); + } + fullArgs.push(cb); } + connector[method].apply(connector, fullArgs); +} - inheritanceTraverse(model.base, ctx, cb); -}; - -module.exports.traverseInheritanceTree = inheritanceTraverse; +module.exports.invokeConnectorMethod = _invokeConnectorMethod; +module.exports.stillConnecting = _stillConnecting; +module.exports.isInstanceQuery = isInstanceQuery; +module.exports.idName = idName; +module.exports.getIdValue = getIdValue; diff --git a/lib/common/validation-builder.js b/lib/common/validation-builder.js deleted file mode 100644 index ea666de..0000000 --- a/lib/common/validation-builder.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This class provides utility methods to collect all the different types - * of validations defined on a model. - * Each of the method takes 'model' object as a parameter and - * extracts the validation rules attached to the model from it. - * - * @module Validation Builder - * @author Pragyan Das - */ - -var logger = require('oe-logger'); -var log = logger('validation-builder'); -var validationUtils = require('./validation-utils'); -var applicableValidations = validationUtils.applicableValidations; -var validationExpressionMapper = validationUtils.validationExpressionMapper; - -module.exports = { - buildValidations: buildValidations -}; - -/** - * - * Aggregate all the different types of validations - * @param {Object} model - model constructor for which validation rules are to be aggregated - * @returns {Object[]} Multi dimenssional array containing all the validation rules - */ -function buildValidations(model) { - var validations = []; - validations = validations.concat(validations, - getPropValidations(model), - getRelationValidations(model), - getOeValidations(model)); - return validations; -} - -/** - * - * Aggregation of validations attached to all the properties of the model - * @param {Object} model - model constructor for which validation rules are to be aggregated - * @returns {Object[]} Array containing all the property level validation rules - */ -function getPropValidations(model) { - var propertyValidations = []; - var properties = model.definition.properties; - log.debug(log.defaultContext(), 'building property level validation rules for : ', model.modelName); - Object.keys(properties).forEach(function propertiesForEachCb(propertyName) { - var propertyType = properties[propertyName].type; - var type = 'default'; - var typeName = properties[propertyName].type.name && properties[propertyName].type.name.toLowerCase(); - if (propertyType instanceof Function && propertyType.sharedClass) { - type = 'object'; - } else if (propertyType instanceof Array && propertyType[0] && propertyType[0].sharedClass) { - type = 'array'; - } else if (Object.keys(validationUtils.applicableValidations).indexOf(typeName) > 0) { - type = properties[propertyName].type.name.toLowerCase(); - } - - // Prevent script injection , for example, in a string field, unless allowScript - // is given, one should not be able to inject a ', 'utf-8'); -!fs.existsSync('client/bower_components/oe-studio/templates/default-list.html') && fs.writeFileSync('client/bower_components/oe-studio/templates/default-list.html', '', 'utf-8'); -!fs.existsSync('client/bower_components/oe-studio/templates/default-page.html') && fs.writeFileSync('client/bower_components/oe-studio/templates/default-page.html', '
    oeCloud.io Page
    ', 'utf-8'); -!fs.existsSync('client/bower_components/oe-studio/styles/default-theme.css') && fs.writeFileSync('client/bower_components/oe-studio/styles/default-theme.css', 'some css stuff', 'utf-8'); -!fs.existsSync('client/bower_components/images/background.jpg') && fs.writeFileSync('client/bower_components/images/background.jpg', 'dummay asset data', 'utf-8'); -!fs.existsSync('client/bower_components/images/audio.mp3') && fs.writeFileSync('client/bower_components/images/audio.mp3', 'dummay asset data', 'utf-8'); -!fs.existsSync('client/bower_components/images/video.mp4') && fs.writeFileSync('client/bower_components/images/video.mp4', 'dummay asset data', 'utf-8'); -!fs.existsSync('client/bower_components/sample-element/sample-element.html') && fs.writeFileSync('client/bower_components/sample-element/sample-element.html', '', 'utf-8'); -!fs.existsSync('client/bower_components/sample-element/default-tpl.html') && fs.writeFileSync('client/bower_components/sample-element/default-tpl.html', '', 'utf-8'); - -var createTestUser = function(user, rolename, cb) { - var User = loopback.getModelByType('BaseUser'); - var Role = loopback.getModelByType('BaseRole'); - var RoleMapping = loopback.getModelByType('BaseRoleMapping'); - - var dbRole; - var dbUser; - - async.series([ - function(done) { - Role.findOne({ - where: { - name: rolename - } - }, defaultContext, function(err, res) { - if (res) { - dbRole = res; - done(); - } else { - var role = { - name: rolename - }; - Role.create(role, defaultContext, function(err, res) { - dbRole = res; - done(); - }); - } - }); - }, - function(done) { - User.findOne({ - where: { - username: user.username - } - }, defaultContext, function(err, res) { - if (res) { - dbUser = res; - done(); - } else { - User.create(user, defaultContext, function(err2, res) { - //console.log('created user ', err2, res); - dbUser = res; - done(); - }); - } - }); - }, - function(done) { - var UserProfile = loopback.getModelByType('UserProfile'); - UserProfile.findOne({ - where: { - userId: dbUser.id - } - }, defaultContext, function(err, res) { - if (res) { - done(); - } else { - var profile = { - firstName: dbUser.username, - lastName: 'J.', - department: 'finance', - userId: dbUser.id - }; - UserProfile.create(profile, defaultContext, function(err2, res) { - done(); - }); - } - }); - }, - function(done) { - RoleMapping.findOne({ - where: { - principalId: dbUser.id, - principalType: 'USER', - roleId: dbRole.id - } - }, defaultContext, function(err, res) { - if (err) { - throw (new Error(err)); - } - if (res) { - done(); - } else { - var mapping = {}; - mapping.principalId = dbUser.id; - mapping.principalType = 'USER'; - mapping.roleId = dbRole.id; - RoleMapping.create(mapping, defaultContext, function(err, res) { - //console.log('created mapping ', err, res); - done(); - }); - } - }); - }, - function() { - cb(); - } - ]); -}; - -var createAdminUser = function(done) { - - var adminUserContext = { - ctx: { - tenantId: 'default', - remoteUser: 'system' - } - }; - - async.series([function(cb) { - var Tenant = loopback.getModelByType('Tenant'); - Tenant.create({ - tenantId: 'default', - tenantName: 'default', - id: '9fab3286-442a-11e6-beb8-9e71128cae77' - }, adminUserContext, function(err, res) { - if (err) { - if (err.code === 11000) { - return cb(); - } - cb(err); - } else { - cb(); - } - }); - }, function(cb) { - var adminUser = { - username: 'admin', - password: 'admin', - email: 'admin@mycompany.com', - emailVerified: true, - id: 'admin' - } - var BaseUser = loopback.getModelByType('BaseUser'); - BaseUser.create(adminUser, adminUserContext, function(err, res) { - if (err) { - if (err.code === 11000) { - return cb(); - } - cb(err); - } else { - cb(); - } - }); - }, function(cb) { - var UserProfile = loopback.getModelByType('UserProfile'); - UserProfile.create({ - firstName: 'Super', - lastName: 'Administrator', - department: 'adminstration', - userId: 'admin', - id: 'fcd1a724-442a-11e6-beb8-9e71128cae77' - }, adminUserContext, function(err, res) { - if (err) { - if (err.code === 11000) { - return cb(); - } - cb(err); - } else { - cb(); - } - }); - }, - function(cb) { - var Role = loopback.getModelByType('BaseRole'); - Role.create({ - id: 'admin', - name: 'admin', - description: 'Admin' - }, adminUserContext, function(err, res) { - if (err) { - if (err.code === 11000) { - return cb(); - } - cb(err); - } else { - cb(); - } - }); - }, - function(cb) { - var RoleMapping = loopback.getModelByType('BaseRoleMapping'); - RoleMapping.create({ - id: 'admin', - principalType: 'USER', - principalId: 'admin', - roleId: 'admin' - }, adminUserContext, function(err, res) { - if (err) { - if (err.code === 11000) { - return cb(); - } - cb(err); - } else { - cb(); - } - }); - } - ], - function() { - done(); - } - ); -}; - - -function login(credentials, cb) { - var postData = null; - if (typeof credentials === 'function') { - postData = { - 'username': defaultUser.username, - 'password': defaultUser.password - }; - cb = credentials; - } else { - postData = credentials; - } - var postUrl = basePath + '/BaseUsers/login'; - var api = defaults(supertest(app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .end(function(err, response) { - expect(response.body.id).to.be.defined; - var accessToken = response.body.id; - cb(accessToken); - }); -} - - - -module.exports = { - chai: chai, - app: app, - appRoot: appRoot, - basePath: basePath, - models: app.models, - api: api, - options: options, - createAccessToken: createAccessToken, - createJWToken: createJWToken, - createTestUser: createTestUser, - login: login -}; - -var createACLsforTest = function(done) { - var acls = [{ - "model": "dev", - "principalType": "USER", - "principalId": "admin", - "permission": "ALLOW", - "accessType": "*" - }]; - var BaseACL = loopback.getModelByType('BaseACL'); - BaseACL.create(acls, defaultContext, function(err, recs) { - //console.log(recs); - done(); - }); -}; - -Object.defineProperty(module.exports, "defaultContext", { - get: function() { - var callContext = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - } - }; - return callContext; - } -}); - -// done(err||(new Error(res.body.error.details.messages.name[0]))) - -describe(chalk.blue('bootstrap test'), function() { - - this.timeout(80000); - - before('wait for boot scripts to complete', function(done) { - app.on('EVstarted', function() { - done(); - }); - }); - - it('waiting for boot scripts to finish', function(done) { - createAdminUser(function() { - createTestUser(defaultUser, 'admin', function() { - createACLsforTest(done); - }); - }); - }); - -}); diff --git a/test/business-rule-data/ApplicantRiskRating.xlsx b/test/business-rule-data/ApplicantRiskRating.xlsx deleted file mode 100644 index 37547d0f580456f3751b145c5c7dbb2f43cc5219..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10557 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 z 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 diff --git a/test/business-rule-data/DecisionTable.json b/test/business-rule-data/DecisionTable.json deleted file mode 100644 index e1e6d50..0000000 --- a/test/business-rule-data/DecisionTable.json +++ /dev/null @@ -1,106 +0,0 @@ -[{ - "name": "ApplicantRiskRating", - "document": { - "documentName": "ApplicantRiskRating.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "RoutingRulesOutput", - "document": { - "documentName": "RoutingRulesOutput.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "RoutingRules", - "document": { - "documentName": "RoutingRules.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "ElectricityBill", - "document": { - "documentName": "ElectricityBill.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "HolidaysCount", - "document": { - "documentName": "HolidaysCount.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "HolidaysMax", - "document": { - "documentName": "HolidaysMax.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "HolidaysMin", - "document": { - "documentName": "HolidaysMin.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "LoanEligibilityCount", - "document": { - "documentName": "LoanEligibilityCount.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "LoanEligibilityMax", - "document": { - "documentName": "LoanEligibilityMax.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "LoanEligibilityMin", - "document": { - "documentName": "LoanEligibilityMin.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "LoanEligibilitySum", - "document": { - "documentName": "LoanEligibilitySum.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "MembershipCount", - "document": { - "documentName": "MembershipCount.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "MembershipMax", - "document": { - "documentName": "MembershipMax.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "MembershipMin", - "document": { - "documentName": "MembershipMin.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }, - { - "name": "MembershipSum", - "document": { - "documentName": "MembershipSum.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - } -] \ No newline at end of file diff --git a/test/business-rule-data/ElectricityBill.xlsx b/test/business-rule-data/ElectricityBill.xlsx deleted file mode 100644 index 437a4efdaac5b1d1e326f1f72cefe9212567def1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10789 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≤pK_RXJ3G)R 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 diff --git a/test/business-rule-data/HolidaysMax.xlsx b/test/business-rule-data/HolidaysMax.xlsx deleted file mode 100644 index 64e662c64ceb011e8d854e12fc4377799ef20c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10629 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 diff --git a/test/business-rule-data/HolidaysMin.xlsx b/test/business-rule-data/HolidaysMin.xlsx deleted file mode 100644 index 82e8d85fa53f92faea4ae42cd767ea3061f01036..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10629 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Ƌ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 diff --git a/test/business-rule-data/LoanEligibilityCount.xlsx b/test/business-rule-data/LoanEligibilityCount.xlsx deleted file mode 100644 index e37432456957aa565c8d8f5c16cce1dba992728c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9764 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

    4ar)->b-JcmCiDUk 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 diff --git a/test/business-rule-data/LoanEligibilityMax.xlsx b/test/business-rule-data/LoanEligibilityMax.xlsx deleted file mode 100644 index e1c028f46d7594df998f9eaeb3fe4e8a4bbc01a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9763 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 diff --git a/test/business-rule-data/LoanEligibilitySum.xlsx b/test/business-rule-data/LoanEligibilitySum.xlsx deleted file mode 100644 index c54e76fe7d22b632bb9233067dbd5452bb41244c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9753 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 R000H@p@X>b3e}I+{{UYqpY;F$ diff --git a/test/business-rule-data/MembershipCount.xlsx b/test/business-rule-data/MembershipCount.xlsx deleted file mode 100644 index 6425389ec1c8c892f68460f9efb54a578ab2eaaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9750 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 diff --git a/test/business-rule-data/RoutingRules.xlsx b/test/business-rule-data/RoutingRules.xlsx deleted file mode 100644 index aba481122ac8025263fd8c43a38cfb874ffd0d5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10853 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 diff --git a/test/business-rule-data/RoutingRulesOutput.xlsx b/test/business-rule-data/RoutingRulesOutput.xlsx deleted file mode 100644 index 7b77c4ae374fd4fc03b705a0a92f1317fd29bdd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10855 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 HX#)NSJfZzF diff --git a/test/business-rule-hit-policy-test.js b/test/business-rule-hit-policy-test.js deleted file mode 100644 index 9d677d6..0000000 --- a/test/business-rule-hit-policy-test.js +++ /dev/null @@ -1,291 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -var fs = require('fs'); -var path = require('path'); -var async = require('async'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var models = bootstrap.models; -var decisionTableRules = ['RoutingRules', 'ElectricityBill', 'Holidays', 'Membership', 'LoanEligibility']; -var decision_table = {}; - -describe(chalk.blue('Decision table evaluation'), function () { - this.timeout(60000); - before('Create DecisionTables', function (done) { - var datatext = fs.readFile(path.join(__dirname, 'business-rule-data', 'DecisionTable.json'), function (err, data){ - if (err) { - done(err) - } - else { - DTData = JSON.parse(data); - async.each(DTData, function (decisionTable, callback) { - models.DecisionTable.create(decisionTable, bootstrap.defaultContext, function (err, data) { - if (err) { - // console.log(err); - callback(err); - } else { - callback(); - } - }); - }, function (err) { - if (err) { - // console.log(err); - done(err); - } else { - done(); - } - }); - } - - }); - }); - - it('Priority hit policy', function (done) { - var payload = { "Applicant Age" : 70, "Medical History" : "bad" }; - models.DecisionTable.exec("ApplicantRiskRating", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result['Applicant Risk Rating']).to.equal("High"); - done(); - } - }); - }); - - it('Output hit policy', function (done) { - var payload = { "Age" : 18, "Risk category" : "High", "Debt review" : false }; - // debugger; - models.DecisionTable.exec("RoutingRulesOutput", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result.Routing.length).to.be.equal(2); - expect(result.Routing[1]).to.contain('Accept'); - expect(result.Routing[0]).to.contain('Refer'); - - expect(result['Review level'].length).to.be.equal(2); - expect(result['Review level'][1]).to.contain('None'); - expect(result['Review level'][0]).to.contain('Level1'); - - expect(result.Reason.length).to.be.equal(2); - expect(result.Reason[1]).to.contain('Acceptable'); - expect(result.Reason[0]).to.contain('High risk application'); - - done(); - } - }); - }); - - it('Collect hit policy without any operator', function (done) { - var payload = { "Age" : 18, "Risk category" : "High", "Debt review" : false }; - models.DecisionTable.exec("RoutingRules", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result.Routing.length).to.be.equal(2); - expect(result.Routing).to.contain('Accept'); - expect(result.Routing).to.contain('Refer'); - - expect(result['Review level'].length).to.be.equal(2); - expect(result['Review level']).to.contain('None'); - expect(result['Review level']).to.contain('Level1'); - - expect(result.Reason.length).to.be.equal(2); - expect(result.Reason).to.contain('Acceptable'); - expect(result.Reason).to.contain('High risk application'); - - done(); - } - }); - }); - - it('Collect hit policy with + operator for numbers', function (done) { - var payload = { "State" : "Karnataka", "Units" : 150 }; - models.DecisionTable.exec("ElectricityBill", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result.Amount).to.equal(693); - done(); - } - }); - }); - - it('Collect hit policy with < operator for numbers', function (done) { - var payload = { "Age" : 100, "Years of Service" : 200 }; - models.DecisionTable.exec("HolidaysMin", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result.Holidays).to.equal(3); - done(); - } - }); - }); - - it('Collect hit policy with > operator for numbers', function (done) { - var payload = { "Age" : 100, "Years of Service" : 200 }; - models.DecisionTable.exec("HolidaysMax", payload, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err) - } - else { - expect(result.Holidays).to.equal(22); - done(); - } - }); - }); - - it('Collect hit policy with # operator for numbers', function (done) { - var payload = { "Age" : 100, "Years of Service" : 200 }; - models.DecisionTable.exec("HolidaysCount", payload, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err) - } - else { - expect(result.Holidays).to.equal(3); - done(); - } - }); - }); - - it('Collect hit policy with + operator for strings', function (done) { - var payload = { "loanAmount" : 2000, "salary" : 20000 }; - models.DecisionTable.exec("MembershipSum", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result.membership).to.equal("SILVER GENERAL GENERAL"); - done(); - } - }); - }); - - it('Collect hit policy with < operator for strings', function (done) { - var payload = { "loanAmount" : 30000, "salary" : 60000 }; - models.DecisionTable.exec("MembershipMin", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result.membership).to.equal("GENERAL"); - done(); - } - }); - }); - - it('Collect hit policy with > operator for strings', function (done) { - var payload = { "loanAmount" : 12000, "salary" : 110000 }; - models.DecisionTable.exec("MembershipMax", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } - else { - expect(result.membership).to.equal("SILVER"); - done(); - } - }); - }); - - it('Collect hit policy with # operator for strings', function (done) { - var payload = { "loanAmount" : 1000, "salary" : 200000 }; - models.DecisionTable.exec("MembershipCount", payload, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err) - } - else { - expect(result.membership).to.equal(4); - done(); - } - }); - }); - - it('Collect hit policy with + operator for boolean', function (done) { - var payload1 = { "age" : 40, "salary" : 55000 };//T, T - var payload2 = { "age" : 18, "salary" : 6000 };//F, F - var payload3 = { "age" : 30, "salary" : 55000 };//T, T, F - models.DecisionTable.exec("LoanEligibilitySum", payload3, bootstrap.defaultContext, function (err3, result3) { - if(err3) { done(err3) ; return } - expect(result3.LoanEligibility).to.equal(false); - models.DecisionTable.exec("LoanEligibilitySum", payload2, bootstrap.defaultContext, function (err2, result2) { - if (err2) { done(err2); return; } - expect(result2.LoanEligibility).to.equal(false); - models.DecisionTable.exec("LoanEligibilitySum", payload1, bootstrap.defaultContext, function (err1, result1) { - if (err1) { done(err1); return; } - expect(result1.LoanEligibility).to.equal(true); - done(); - }); - }); - }); - }); - - it('Collect hit policy with < operator for boolean', function (done) { - var payload1 = { "age" : 40, "salary" : 55000 };//T, T - var payload2 = { "age" : 18, "salary" : 6000 };//F, F - var payload3 = { "age" : 30, "salary" : 55000 };//T, T, F - models.DecisionTable.exec("LoanEligibilityMin", payload3, bootstrap.defaultContext, function (err3, result3) { - if (err3) { return done(err3); } - expect(result3.LoanEligibility).to.equal(0); - models.DecisionTable.exec("LoanEligibilityMin", payload2, bootstrap.defaultContext, function (err2, result2) { - if (err2) { return done(err2); } - expect(result2.LoanEligibility).to.equal(0); - models.DecisionTable.exec("LoanEligibilityMin", payload1, bootstrap.defaultContext, function (err1, result1) { - if (err1) { return done(err1); } - expect(result1.LoanEligibility).to.equal(1); - done(); - }); - }); - }); - }); - - it('Collect hit policy with > operator for boolean', function (done) { - var payload1 = { "age" : 40, "salary" : 55000 };//T, T - var payload2 = { "age" : 18, "salary" : 6000 };//F, F - var payload3 = { "age" : 30, "salary" : 55000 };//T, T, F - models.DecisionTable.exec("LoanEligibilityMax", payload3, bootstrap.defaultContext, function (err3, result3) { - if (err3) { return done(err3); } - expect(result3.LoanEligibility).to.equal(1); - models.DecisionTable.exec("LoanEligibilityMax", payload2, bootstrap.defaultContext, function (err2, result2) { - if (err2) { return done(err2); } - expect(result2.LoanEligibility).to.equal(0); - models.DecisionTable.exec("LoanEligibilityMax", payload1, bootstrap.defaultContext, function (err1, result1) { - if (err1) { return done(err1); } - expect(result1.LoanEligibility).to.equal(1); - done(); - }); - }); - }); - }); - - it('Collect hit policy with # operator for boolean', function (done) { - var payload1 = { "age" : 40, "salary" : 55000 };//T, T - var payload2 = { "age" : 18, "salary" : 6000 };//F, F - var payload3 = { "age" : 30, "salary" : 55000 };//T, T, F - models.DecisionTable.exec("LoanEligibilityCount", payload3, bootstrap.defaultContext, function (err3, result3) { - if (err3) { return done(err3); } - expect(result3.LoanEligibility).to.equal(2); - models.DecisionTable.exec("LoanEligibilityCount", payload2, bootstrap.defaultContext, function (err2, result2) { - if (err2) { return done(err2); } - expect(result2.LoanEligibility).to.equal(1); - models.DecisionTable.exec("LoanEligibilityCount", payload1, bootstrap.defaultContext, function (err1, result1) { - if (err1) { return done(err1); } - expect(result1.LoanEligibility).to.equal(1); - done(); - }); - }); - }); - }); - -}); diff --git a/test/business-rule-mixin-test.js b/test/business-rule-mixin-test.js deleted file mode 100644 index 539496d..0000000 --- a/test/business-rule-mixin-test.js +++ /dev/null @@ -1,309 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - -var modelName = 'LoanApplication'; -var bussinessRuleModel = 'BusinessRule'; - -var api = bootstrap.api; -var url = bootstrap.basePath; -var accessToken; - -describe(chalk.blue('Business Rule Mixin test'), function () { - - this.timeout(20000); - - before('setup test data', function (done) { - bootstrap.createAccessToken(bootstrap.defaultContext.ctx.remoteUser.username, - function fnAccessToken(err, token) { - accessToken = token; - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var myModel = loopback.getModel(bussinessRuleModel, bootstrap.defaultContext); - var data = [ - { - 'modelName': modelName, - 'expression': 'if(@i.AccountDetails.salary <= 20){return @i.loanAmount <= @i.AccountDetails.salary * 2}', - 'verb': [ - 'post' - ], - 'category': 'LoanEligibility', - 'code': 'err-salary-01', - 'description': 'if salary is less than 20lacs, maximum loan amount allowed is twice of salary' - }, - { - 'modelName': modelName, - 'expression': 'if(@i.AccountDetails.salary > 20){return @i.loanAmount <= @i.AccountDetails.salary * 3}', - 'verb': [ - 'post' - ], - 'category': 'LoanEligibility', - 'code': 'err-salary-02', - 'description': 'if salary is greater than 20lacs, maximum loan amount allowed is thrice of salary' - } - ]; - - var loanModel = loopback.getModel(modelName, bootstrap.defaultContext); - loanModel.remoteMethod( - 'getLoanEligibility', { - description: 'Check the eligibility of the loan application.', - accepts: [ - { - arg: 'req', - type: 'object', - http: { - source: 'req' - } - }, - { - arg: 'data', - type: 'object', - required: true, - http: { - source: 'body' - } - } - ], - returns: { - arg: 'result', - type: 'object', - root: true - }, - http: { - verb: 'post' - } - } - ); - - loanModel.getLoanEligibility = function (data, req, options, fn) { - - - var loanModel = loopback.getModel(modelName, bootstrap.defaultContext); - var businessModel = loopback.getModel(bussinessRuleModel, bootstrap.defaultContext); - - if (fn === undefined && typeof options === 'function') { - fn = options; - options = {}; - } - - var where = {}; - var filter = {}; - where['modelName'] = modelName; - where['category'] = 'LoanEligibility'; - filter['where'] = where; - - var inst = req; - - businessModel.find(filter, options, function (err, rules) { - if (err) { - fn(null, null); - } else { - loanModel.prototype.processBusinessRules(rules, inst, options, function (errCode) { - var response = null; - - if (errCode && errCode.length > 0) { - response = {}; - response.message = errCode[0]; - fn(null, response); - } else { - fn(null, response); - } - }); - } - }); - - }; - - - myModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - models.ModelDefinition.create({ - 'name': modelName, - 'base': 'BaseEntity', - 'strict': false, - 'plural': modelName + 's', - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'loanAmount': { - 'type': 'number', - 'required': true - }, - 'DOB': { - 'type': 'date', - 'required': true - }, - 'employmentType': { - 'type': 'string', - 'required': true - }, - 'currentLocation': { - 'type': 'string', - 'required': true - }, - 'status': { - 'type': 'number' - - }, - 'hasExistingLoan': { - 'type': 'boolean' - }, - 'existingLoanType': { - 'type': 'string' - }, - 'existingLoanTenure': { - 'type': 'number' - }, - 'existingLoanEmi': { - 'type': 'number' - } - }, - 'relations': { - 'salariedAccount': { - 'type': 'embedsOne', - 'model': 'SalariedAccount', - 'property': 'AccountDetails', - 'options': { - 'validate': true - } - } - }, - 'validations': [], - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.null; - }); - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: modelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - - it('Validation Test - Should fail to insert data', function (done) { - var URL = url + '/LoanApplications/getLoanEligibility?accessToken=' + accessToken; - - var postData = { - 'AccountDetails': { - 'salary': 20 - }, - 'loanAmount': 50 - }; - - api.post(URL) - .set('Accept', 'application/json') - .set('TENANT_ID', bootstrap.defaultContext.ctx.tenantId) - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } else { - var response = JSON.parse(resp.text); - expect(response.message).to.equal('err-salary-01'); - done(); - } - }); - }); - - it('Validation Test - Should sucessfully insert data', function (done) { - var URL = url + '/LoanApplications/getLoanEligibility?accessToken=' + accessToken; - - var postData = { - 'AccountDetails': { - 'salary': 20 - }, - 'loanAmount': 40 - }; - - api.post(URL) - .set('Accept', 'application/json') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } else { - expect(resp.text).to.equal('null'); - done(); - } - }); - - }); - - it('Validation Test - Should fail to insert data', function (done) { - var URL = url + '/LoanApplications/getLoanEligibility?accessToken=' + accessToken; - - var postData = { - 'AccountDetails': { - 'salary': 21 - }, - 'loanAmount': 64 - }; - - api.post(URL) - .set('Accept', 'application/json') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } else { - var response = JSON.parse(resp.text); - expect(response.message).to.equal('err-salary-02'); - done(); - } - }); - - }); - - it('Validation Test - Should sucessfully insert data', function (done) { - var URL = url + '/LoanApplications/getLoanEligibility?accessToken=' + accessToken; - var postData = { - 'AccountDetails': { - 'salary': 21 - }, - 'loanAmount': 62 - }; - - api.post(URL) - .set('Accept', 'application/json') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } else { - expect(resp.text).to.equal('null'); - done(); - } - }); - - }); -}); \ No newline at end of file diff --git a/test/caching-test.js b/test/caching-test.js deleted file mode 100644 index 7efd8ec..0000000 --- a/test/caching-test.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This test is for unit-testing the query result caching feature in the framework. - * The test involves creating a test model, inserting a record into it, fetching the - * record (so that it caches), deleting the record from the database by directly accessing - * the DB (bypassing the framework, so that cache is not evicted), fetching the - * record again to see that the records are still fetched (from cache). - * - * Author: Ajith Vasudevan - */ - - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var uuidv4 = require('uuid/v4'); -var expect = chai.expect; -var app = bootstrap.app; -var models = bootstrap.models; -var api = bootstrap.api; -var loopback = require('loopback'); -var debug = require('debug')('caching-test'); -var config = require('../server/config'); -var MongoClient = require('mongodb').MongoClient; -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var postgresHost = process.env.POSTGRES_HOST || 'localhost'; -var accessToken = null; -var oracleHost = process.env.ORACLE_HOST || 'localhost'; -var oraclePort = process.env.ORACLE_PORT || 1521; -var oracleService = process.env.ORACLE_SID || 'orclpdb.ad.infosys.com'; -var oracleUser = process.env.ORACLE_USERNAME || 'oeadmin'; -var oraclePassword = process.env.ORACLE_PASSWORD || 'oeadmin'; - -describe('Caching Test', function () { - this.timeout(20000); - var modelName = 'CachingTest'; - var TestModel = null; - var dsname = 'db'; - var dbName = process.env.DB_NAME || dsname; - var result1, result2 = null; - var id, dataSource; - var i = 1; - - - function apiPostRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res, done); - } - }); - } - - function apiGetRequest(url, callback, done) { - var version = uuidv4(); - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + url + '?access_token=' + accessToken) - .send() - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res, done); - } - }); - } - - // Create a record in TestModel with caching enabled and - // fetch the inserted record using standard framework API, - // so that it gets cached - function stage1_creat(done) { - id = uuidv4(); - apiPostRequest('/' + modelName + 's/', { "name": "Ajith" + i++, "id": id }, stage2_find, done); - } - - function stage2_find(result, done) { - apiGetRequest('/' + modelName + 's/' + id, stage3_updateDB, done); - } - - function stage3_updateDB(result, done) { - result1 = result; - if (dataSource.name === 'mongodb') { - MongoClient.connect('mongodb://'+mongoHost+':27017/' + dbName, function (err, db) { - if (err) return done(err); - else { - db.collection(modelName).update({ "_id": id }, { $set: { name: "value2" } }, { upsert: true }, function (err) { - if (err) return done(err); - else stage4_find(result, done); - }); - } - }); - } else if (dataSource.name === 'oe-connector-oracle') { - var oracledb = require('oracledb'); - oracledb.autoCommit = true; - let loopbackModelNoCache = loopback.getModel(modelName); - let idFieldName = loopbackModelNoCache.definition.idName(); - oracledb.getConnection({ - "password": oraclePassword, - "user": oracleUser, - "connectString": oracleHost + ":" + oraclePort + "/" + oracleService - }, function (err, connection) { - if (err) { - return done(err); - } - connection.execute( - "UPDATE " + modelName.toLowerCase() + " SET name = 'value2' WHERE " + idFieldName + " = '" + id + "'", - function (error, result) { - if (error) { - return done(error); - } - debug("Number of records removed " + result.rowsAffected); - stage4_find(result, done); - }); - }); - } else { - var loopbackModelNoCache = loopback.getModel(modelName); - var idFieldName = loopbackModelNoCache.definition.idName(); - var connectionString = "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbName; - var pg = require('pg'); - var client = new pg.Client(connectionString); - client.connect(function (err) { - if (err) done(err); - else { - var query = client.query("UPDATE " + modelName.toLowerCase() + " SET name = 'value2' WHERE " + idFieldName + " = '" + id + "'", function (err, result) { - if (err) { - return done(err); - } - debug("Number of records removed " + result.rowCount); - stage4_find(result, done); - }); - } - }); - } - } - - function stage4_find(result, done) { - apiGetRequest('/' + modelName + 's/' + id, stage5_saveResult, done); - } - - function stage5_saveResult(result, done) { - result2 = result; - done(); - } - - before('Create Test Model and do cache test', function (done) { - // Temporarily enable Caching (will be disabled in the "after()" function - config.disablecaching = false; - // Get a datasource - dataSource = app.datasources[dsname]; - - var TestModelSchema = { - 'name': { - 'type': 'string', - 'required': true - } - }; - var opts = { - strict: true, - plural: modelName + "s", - base: 'BaseEntity', - cacheable: true, - queryCacheExpiration : 5000, - }; - - // Create a TestModel and attache it to the dataSource - TestModel = loopback.createModel(modelName, TestModelSchema, opts); - TestModel.clientModelName = modelName; - TestModel.clientPlural = modelName + 's'; - app.model(TestModel, { dataSource: dsname }); - TestModel.attachTo(dataSource); - - // Delete all records in the table associated with this TestModel - TestModel.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - console.log('caching test clean up ', err, info); - } - done(); - }); - }); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - describe('Caching Test - when dblock off', function () { - before('Run test', function (done) { - api.set('x-evproxy-db-lock', '0'); - stage1_creat(done); - }); - - xit('Should cache the TestModel when cacheable is set to "true"', function (done) { - expect(models[modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1.body).to.deep.equal(result2.body); - done(); - }); - - }); - - describe('Caching Test - when dblock on', function () { - - before('set the dbLock header', function (done) { - id = result1 = result2 = null; - api.set('x-evproxy-db-lock', '1'); - stage1_creat(done); - }); - - it('Should not use cache when when dblock on', function (done) { - if (result1.body.name !== result2.body.name) return done(); - else return done(new Error("The query was cached although dblock is on")); - }); - - after('unset the dbLock header', function (done) { - api.set('x-evproxy-db-lock', '0'); - return done(); - }); - }); - - after('Cleanup', function (done) { - config.disablecaching = true; - TestModel.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - console.log(err, info); - } - done(); - }); - }); - -}); diff --git a/test/client-sdk-test.js b/test/client-sdk-test.js deleted file mode 100644 index f43770b..0000000 --- a/test/client-sdk-test.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var util = require('../lib/common/util.js'); - -describe(chalk.blue('client-sdk-test'), function (done) { - - it('get routes for client sdk', function (done) { - var routes = util.getRoutes(bootstrap.app); - - // checking for some random models that should be present in routes - expect(routes).to.include.keys('NavigationLink','BaseUser'); - - // checking some random api's of model. (checking for random model NavigationLink) - expect(routes.NavigationLink).to.include.keys('count','countChildren','create','createChildren','findById','findChildrenById'); - - // checking accepts property of model api's - expect(routes.NavigationLink.findChildrenById.accepts.length).to.be.equal(2); - expect(routes.NavigationLink.findChildrenById.accepts[0].arg).to.be.equal('fk'); - expect(routes.NavigationLink.findChildrenById.accepts[1].arg).to.be.equal('id'); - - done(); - }); - -}); diff --git a/test/common/mixins/new-mixin.js b/test/common/mixins/new-mixin.js new file mode 100644 index 0000000..d7cdea4 --- /dev/null +++ b/test/common/mixins/new-mixin.js @@ -0,0 +1,3 @@ +module.exports = function(Model){ + console.log('Came in new mixin - ', Model.modelName); +} diff --git a/test/common/mixins/test-mixin.js b/test/common/mixins/test-mixin.js new file mode 100644 index 0000000..4f7dfc0 --- /dev/null +++ b/test/common/mixins/test-mixin.js @@ -0,0 +1,2 @@ +module.exports = function(Model){ +} diff --git a/test/common/models/Customer.json b/test/common/models/Customer.json new file mode 100644 index 0000000..2898a5e --- /dev/null +++ b/test/common/models/Customer.json @@ -0,0 +1,24 @@ +{ + "name": "Customer", + "base": "BaseEntity", + "idInjection": true, + "properties": { + "name": { + "type": "string", + "unique": true + }, + "age": { + "type": "number" + } + }, + "validations": [], + "acls": [], + "methods": {}, + "relations": { + "spouseRel": { + "type": "hasOne", + "model": "Spouse", + "foreignKey" : "customerId" + } + } +} diff --git a/test/common/models/Spouse.json b/test/common/models/Spouse.json new file mode 100644 index 0000000..b43c46e --- /dev/null +++ b/test/common/models/Spouse.json @@ -0,0 +1,17 @@ +{ + "name": "Spouse", + "base": "BaseEntity", + "idInjection": true, + "properties": { + "name": { + "type": "string", + "unique": true + }, + "age": { + "type": "number" + } + }, + "validations": [], + "acls": [], + "methods": {} +} diff --git a/test/common/models/base-entity-test.js b/test/common/models/base-entity-test.js new file mode 100644 index 0000000..a42fe0e --- /dev/null +++ b/test/common/models/base-entity-test.js @@ -0,0 +1,4 @@ +module.exports = function (Model) { + console.log("Came in base-entity.js of customization"); +} + diff --git a/test/component-config.json b/test/component-config.json new file mode 100644 index 0000000..f36959a --- /dev/null +++ b/test/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} diff --git a/test/composite-model-test.js b/test/composite-model-test.js deleted file mode 100755 index 7a938a2..0000000 --- a/test/composite-model-test.js +++ /dev/null @@ -1,924 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/*global - require,before,after,it,describe -*/ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; - -// This test case is for composite model functionality -// 1. Creates Customer, CustomerAddress and UpcomingEvent models and creates additional CompositeModel which combines Customer and UpcomingEvent models. -// 2. Create record by posting into CompositeModel - this should in turn create records in Customer and CustomerAddress and unrelated UpcomingEvent model. Explicit row_status is given against record. -// 3. Tests the records being created by querying these tables separately -// 4. Update selective records by doing again POST operation on Composite model -// 5. Test the update by separately querying data. -// 6. Implicit composite test - posting data to Customer along with CustomerAddress with relation -// 7. Test to perform validations to see if duplicate record fails - create CustomerAddress duplicate - -describe(chalk.blue('Composite Model test'), function () { - this.timeout(60000); - var callContext = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - } - }; - - before('setup test data', function (done) { - models.ModelDefinition.create({ - 'name': 'Customer', - 'idInjection': false, - 'base': 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'contact': { - 'type': 'object' - } - }, - 'relations': { - 'address': { - 'type': 'hasMany', - 'model': 'CustomerAddress', - 'foreignKey': 'customerId' - } - }, - 'filebased': false - }, callContext, function (err, model) { - console.log(err); - expect(err).to.be.not.ok; - models.ModelDefinition.create({ - name: 'UpcomingEvent', - 'idInjection': false, - base: 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - properties: { - 'eventName': { - 'type': 'string', - 'required': true - }, - 'eventDescription': { - 'type': 'string' - }, - 'activeFlag': { - 'type': 'boolean' - } - }, - relations: {}, - filebased: false - }, callContext, function (err, upcomingEventrcd) { - expect(err).to.be.null; - models.ModelDefinition.create({ - name: 'CustomerAddress', - 'idInjection': false, - base: 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - properties: { - 'city': { - 'type': 'string', - 'required': true - }, - 'state': { - 'type': 'object' - } - }, - relations: { - 'address': { - 'type': 'belongsTo', - 'model': 'Customer', - 'foreignKey': 'customerId' - - } - }, - filebased: false - }, callContext, function (err2, model2) { - expect(err2).to.be.null; - models.ModelDefinition.create({ - name: 'CompositeModel', - base: 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - strict: false, - properties: {}, - filebased: false, - CompositeTransaction: true, - compositeModels: { - 'Customer': {}, - 'UpcomingEvent': {} - } - }, callContext, function (err3, model3) { - expect(err3).to.be.not.ok; - models.ModelDefinition.create({ - name: 'HOUser', - 'idInjection': false, - base: 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - properties: { - name: { - type: 'string' - } - }, - relations: { - preferences: { - type: 'hasOne', - model: 'HOPreferences', - foreignKey: 'userId' - } - } - }, callContext, function(err4, model4){ - models.ModelDefinition.create({ - name: 'HOPreferences', - 'idInjection': false, - base: 'BaseEntity', - 'mixins': { - 'VersionMixin': false, - 'IdempotentMixin': false, - }, - properties: { - choice1: { - type: 'string' - }, - flag1: { - type: 'boolean' - } - }, - relations: { - preferences: { - type: 'hasOne', - model: 'HOPreferences', - foreignKey: 'userId' - } - } - }, callContext, function(err5, model5){ - expect(err5).to.be.not.ok; - return done(err5); - }); - }) - }); - }); - }); - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: 'HOPreferences' - }, callContext, function () { }); - models.ModelDefinition.destroyAll({ - name: 'HOUser' - }, callContext, function () { }); - models.ModelDefinition.destroyAll({ - name: 'Customer' - }, callContext, function () { }); - models.ModelDefinition.destroyAll({ - name: 'UpcomingEvent' - }, callContext, function () { }); - models.ModelDefinition.destroyAll({ - name: 'CustomerAddress' - }, callContext, function () { }); - loopback.findModel("HOPreferences", callContext).destroyAll({}, callContext, function () { }); - loopback.findModel("HOUser", callContext).destroyAll({}, callContext, function () { }); - loopback.findModel("Customer", callContext).destroyAll({}, callContext, function () { }); - loopback.findModel("CustomerAddress", callContext).destroyAll({}, callContext, function () { }); - loopback.findModel("UpcomingEvent", callContext).destroyAll({}, callContext, function () { }); - models.ModelDefinition.destroyAll({ - name: 'CompositeModel' - }, callContext, function () { - done(); - }); - }); - - it('Composite Model test - should create nested 1 record in customer & 2 record in address and 1 record in UpcomingEvent model ', function (done) { - var compositeModel = loopback.getModel('CompositeModel', callContext); - console.log('composit model customer create'); - compositeModel.create({ - 'Customer': [{ - 'name': 'Smith', - 'id': 1, - '__row_status': 'added', - 'address': [{ - 'city': 'Delhi', - 'id': 11, - '__row_status': 'added' - }, { - 'id': 12, - 'city': 'Mumbai', - '__row_status': 'added' - }] - }], - 'UpcomingEvent': [{ - 'eventName': 'A.R. Raheman concert', - 'eventDescription': 'Concert is free for all Icici bank users', - 'activeFlag': true, - '__row_status': 'added', - 'id': 1 - }] - - }, callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results).to.have.property('Customer'); - expect(results.Customer[0]).to.have.property('name'); - expect(results.Customer[0]).to.have.property('address'); - expect(results.Customer[0].name).to.equal('Smith'); - expect(results.Customer[0].address[0].city).to.equal('Delhi'); - expect(results.Customer[0].address[1].city).to.equal('Mumbai'); - expect(results).to.have.property('UpcomingEvent'); - expect(results.UpcomingEvent[0]).to.have.property('eventName'); - expect(results.UpcomingEvent[0].eventName).to.equal('A.R. Raheman concert'); - - done(); - }); - }); - - it('Composite Model test - should create nested 2 record in customer & 4 record in address ', function (done) { - var compositeModel = loopback.getModel('CompositeModel', callContext); - compositeModel.create({ - 'Customer': [{ - 'name': 'Williams', - 'id': 2, - '__row_status': 'added', - 'address': [{ - 'city': 'Hyderabad', - 'id': 13, - '__row_status': 'added' - }, { - 'id': 14, - 'city': 'Secunderabad', - '__row_status': 'added' - }] - }, { - 'name': 'John', - 'id': 3, - '__row_status': 'added', - 'address': [{ - 'city': 'Bangalore', - 'id': 15, - '__row_status': 'added' - }, { - 'id': 16, - 'city': 'Chennai', - '__row_status': 'added' - }] - }], - 'UpcomingEvent': [{ - 'eventName': 'India vs Australia match', - 'eventDescription': '50% discount for all Icici bank users', - 'activeFlag': true, - '__row_status': 'added', - 'id': 2 - }, - { - 'eventName': 'New year celebration', - 'eventDescription': '50% discount for all Icici bank users', - 'activeFlag': true, - '__row_status': 'added', - 'id': 3 - } - ] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results).to.have.property('Customer'); - expect(results.Customer[0]).to.have.property('name'); - expect(results.Customer[0]).to.have.property('address'); - expect(results.Customer[0].name).to.equal('Williams'); - expect(results.Customer[0].address[0].city).to.equal('Hyderabad'); - expect(results.Customer[0].address[1].city).to.equal('Secunderabad'); - expect(results.Customer[1].name).to.equal('John'); - expect(results.Customer[1].address[0].city).to.equal('Bangalore'); - expect(results.Customer[1].address[1].city).to.equal('Chennai'); - - expect(results).to.have.property('UpcomingEvent'); - expect(results.UpcomingEvent[0]).to.have.property('eventName'); - expect(results.UpcomingEvent[0].eventName).to.equal('India vs Australia match'); - expect(results.UpcomingEvent[1]).to.have.property('eventName'); - expect(results.UpcomingEvent[1].eventName).to.equal('New year celebration'); - done(); - }); - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Smith' - } - }, - callContext, - function (err, results) { - expect(results[0].name).to.equal('Smith'); - done(); - }); - - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Williams' - } - }, - callContext, - function (err, results) { - expect(results[0].name).to.equal('Williams'); - done(); - }); - - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'John' - } - }, - callContext, - function (err, results) { - expect(results[0].name).to.equal('John'); - done(); - }); - - }); - - it('should get the CustomerAddress based on where condition', function (done) { - var customerAddress = loopback.getModel('CustomerAddress', callContext); - customerAddress.find({ - where: { - 'city': 'Delhi' - } - }, - callContext, - function (err, results) { - expect(results[0].city).to.equal('Delhi'); - expect(results[0].customerId === "1" || results[0].customerId === 1).to.be.ok; - done(); - }); - }); - - - - it('Composite Model test - 1 customer record should be updated, 1 address recourd should be updated', function (done) { - var compositeModel = loopback.getModel('CompositeModel', callContext); - compositeModel.create({ - 'Customer': [{ - 'name': 'Smith_Changed', - 'id': 1, - '__row_status': 'modified', - 'address': [{ - 'city': 'DELHI_CAPITAL', - 'id': 11, - '__row_status': 'modified' - }] - }], - 'UpcomingEvent': [{ - 'eventName': 'India vs Australia match - Expired', - 'activeFlag': false, - '__row_status': 'modified', - 'id': 2 - }, - { - 'eventName': 'New year celebration', - 'eventDescription': '50% discount for all Icici bank users', - 'activeFlag': false, - '__row_status': 'deleted', - 'id': 3 - }] - }, callContext, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - expect(results).to.have.property('Customer'); - expect(results.Customer[0]).to.have.property('name'); - expect(results.Customer[0]).to.have.property('address'); - expect(results.Customer[0].name).to.equal('Smith_Changed'); - expect(results.Customer[0].address[0].city).to.equal('DELHI_CAPITAL'); - expect(results).to.have.property('UpcomingEvent'); - expect(results.UpcomingEvent[0]).to.have.property('eventName'); - expect(results.UpcomingEvent[0].eventName).to.equal('India vs Australia match - Expired'); - done(); - }); - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Smith_Changed' - } - }, callContext, - function (err, results) { - expect(results[0].name).to.equal('Smith_Changed'); - done(); - }); - - }); - - it('should get the CustomerAddress based on where condition', function (done) { - var customerAddress = loopback.getModel('CustomerAddress', callContext); - customerAddress.find({ - where: { - 'city': 'DELHI_CAPITAL' - } - }, callContext, - function (err, results) { - expect(results[0].city).to.equal('DELHI_CAPITAL'); - expect(results[0].customerId === "1" || results[0].customerId === 1).to.be.ok; - done(); - }); - }); - - it('should not get the UpcomingEvent record as record is deleted', function (done) { - var upcomingEvent = loopback.getModel('UpcomingEvent', callContext); - upcomingEvent.find({ - where: { - 'id': 3 - } - }, callContext, - function (err, results) { - expect(results.length).to.equal(0); - done(); - }); - }); - - - - it('Implicit Composite Model test - 1 customer record should be created and 2 address records should be created', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.create({ - 'name': 'Michael', - 'id': 4, - 'address': [{ - 'city': 'San Jose', - 'id': 22 - }, { - 'id': 23, - 'city': 'New York' - }] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results).to.have.property('name'); - expect(results).to.have.property('id'); - expect(results.__data).to.have.property('address'); - expect(results.name).to.equal('Michael'); - expect(results.__data.address[0]).to.have.property('city'); - expect(results.__data.address[0].city).to.equal('San Jose'); - expect(results.__data.address[1].city).to.equal('New York'); - done(); - }); - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Michael' - } - }, callContext, - function (err, results) { - expect(results[0].__data.name).to.equal('Michael'); - done(); - }); - }); - - it('should get the CustomerAddress based on where condition', function (done) { - var customerAddress = loopback.getModel('CustomerAddress', callContext); - customerAddress.find({ - where: { - 'city': 'San Jose' - } - }, callContext, - function (err, results) { - expect(results[0].__data.city).to.equal('San Jose'); - expect(results[0].__data.customerId === "4" || results[0].__data.customerId === 4).to.be.ok; - done(); - }); - }); - - it('Implicit Composite Model test - 1 customer record should be created and 2 address records should be created', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.create([{ - 'name': 'Tom', - 'id': 5, - 'address': [{ - 'city': 'Denver', - 'id': 24, - }, { - 'id': 25, - 'city': 'Frankfort' - }] - }, { - 'name': 'Harry', - 'id': 6, - 'address': [{ - 'city': 'London', - 'id': 26 - }, { - 'id': 27, - 'city': 'Paris' - }] - }], callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0]).to.have.property('address'); - expect(results[0].name).to.equal('Tom'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Denver'); - expect(results[0].__data.address[1].city).to.equal('Frankfort'); - expect(results[1]).to.have.property('name'); - expect(results[1]).to.have.property('id'); - expect(results[1]).to.have.property('address'); - expect(results[1].name).to.equal('Harry'); - expect(results[1].__data.address[0]).to.have.property('city'); - expect(results[1].__data.address[0].city).to.equal('London'); - expect(results[1].__data.address[1].city).to.equal('Paris'); - done(); - }); - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Tom' - } - }, callContext, - function (err, results) { - expect(results[0].__data.name).to.equal('Tom'); - done(); - }); - - }); - - it('should get the customer based on where condition', function (done) { - var customer = loopback.getModel('Customer', callContext); - customer.find({ - where: { - 'name': 'Harry' - } - }, callContext, - function (err, results) { - expect(results[0].__data.name).to.equal('Harry'); - done(); - }); - - }); - - - it('should get the CustomerAddress based on where condition', function (done) { - var customerAddress = loopback.getModel('CustomerAddress', callContext); - customerAddress.find({ - where: { - 'city': 'Frankfort' - } - }, callContext, - function (err, results) { - expect(results[0].__data.city).to.equal('Frankfort'); - expect(results[0].__data.customerId === "5" || results[0].__data.customerId === 5).to.be.ok; - done(); - }); - }); - - it('should get the CustomerAddress based on where condition', function (done) { - var customerAddress = loopback.getModel('CustomerAddress', callContext); - customerAddress.find({ - where: { - 'city': 'London' - } - }, callContext, - function (err, results) { - expect(results[0].__data.city).to.equal('London'); - expect(results[0].__data.customerId === "6" || results[0].__data.customerId === 6).to.be.ok; - done(); - }); - }); - - - it('Composite Model test - should create 1 record in customer without address ', function (done) { - var compositeModel = loopback.getModel('CompositeModel', callContext); - compositeModel.create({ - 'Customer': [{ - 'name': 'Jim', - 'id': 10, - '__row_status': 'added' - }] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results).to.have.property('Customer'); - expect(results.Customer[0]).to.have.property('name'); - expect(results.Customer[0].name).to.equal('Jim'); - done(); - }); - }); - - it('Implicit Composite Model test - should create 1 record in customer even though customerId not present as belongs to is ignored in transaction ', function (done) { - var compositeModel = loopback.getModel('Customer', callContext); - compositeModel.create({ - 'name': 'Bala', - 'id': 11, - '__row_status': 'added', - 'address': [{ - 'city': 'Brisbon', - 'id': 31 - }, { - 'id': 32, - 'city': 'Vatican' - }] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - expect(results).to.have.property('name'); - expect(results).to.have.property('id'); - expect(results).to.have.property('address'); - expect(results.name).to.equal('Bala'); - expect(results.__data.address[0]).to.have.property('city'); - expect(results.__data.address[0].city).to.equal('Brisbon'); - expect(results.__data.address[1].city).to.equal('Vatican'); - done(); - }); - }); - - it('Implicit Composite Model test - should fail to create 1 record in customer as one addressId not valid ', function (done) { - var compositeModel = loopback.getModel('Customer', callContext); - compositeModel.create({ - 'name': 'Raj', - 'id': 12, - '__row_status': 'added', - 'address': [{ - 'city': 'Kolkata', - 'id': 31 - }, { - 'id': 32, - 'city': 'Katak' - }] - }, callContext, function (err, results) { - expect(err).not.null; - done(); - }); - }); - - it('implicit composit post should fail and throw the error when improper parent data is passed to parent model', function (done) { - var parentModel = loopback.getModel('Customer', callContext); - parentModel.observe('before save', function (ctx, next) { - var data = ctx.instance || ctx.data; - if (data.contact.mobile) - return next(); - return next(); - }); - - var postData = { - 'name': 'Kirito', - 'id': 'K7D3', - 'address': [{ - 'city': 'MoonLand', - 'id': 'X22' - }] - }; - parentModel.create(postData, callContext, function (err, res) { - if (err) { - done(); - } else { - done('should fail and throw the error when improper parent data is passed on parent model'); - } - }); - - }); - - it('implicit composit post should fail and throw the error when improper child data is passed to parent model', function (done) { - var parentModel = loopback.getModel('Customer', callContext); - var childModel = loopback.getModel('CustomerAddress', callContext); - childModel.observe('before save', function (ctx, next) { - var data = ctx.instance || ctx.data; - if (data.state.capital) - return next(); - return next(); - }); - - var postData = { - 'name': 'Asuna', - 'contact': { 'mobile': 'HIDDEN_NUMBER' }, - 'id': 'K7D4', - 'address': [{ - 'city': 'SkyWalk', - 'id': 'X42' - }] - }; - parentModel.create(postData, callContext, function (err, res) { - if (err) { - done(); - } else { - done('should fail and throw the error when improper child data is passed on parent model'); - } - }); - - }); -/************************* */ -/************************* */ - it('hasOne Child posted as [{}], record is created and returned in array', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 1001, - preferences: [{ - choice1: 'Delhi', - flag1: true - }] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.have.property('preferences'); - expect(response.preferences).to.be.an('array'); - expect(response.preferences[0]).to.have.property('choice1'); - expect(response.preferences[0]).to.have.property('flag1'); - done(); - }); - }); - - it('hasOne Child posted as [{}], can be added later as part of PUT', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 1002 - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.not.have.property('preferences'); - response.preferences = [{__row_status:'added',choice1: 'Bengaluru', flag1: false}]; - - customer.upsert(response, callContext, function(err2, results2){ - if (err2) { - return done(err2); - } - - var response2 = results2.__data; - expect(response2).to.have.property('preferences'); - expect(response2.preferences).to.be.an('array'); - expect(response2.preferences[0]).to.have.property('choice1'); - expect(response2.preferences[0]).to.have.property('flag1'); - done(); - }); - }); - }); - - it('hasOne Child posted as [{}], record can be deleted', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 1003, - preferences: [{ - choice1: 'Delhi', - flag1: true - }] - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.have.property('preferences'); - expect(response.preferences).to.be.an('array'); - - response.preferences[0].__row_status = 'deleted'; - customer.upsert(response, callContext, function(err2, results2){ - if (err2) { - return done(err2); - } - - var response2 = results2.__data; - expect(response2).to.not.have.property('preferences'); - done(); - }); - - }); - }); - - it('hasOne Child posted as {}, record is created and returned as object', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 2001, - preferences: { - choice1: 'Delhi', - flag1: true - } - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.have.property('preferences'); - expect(response.preferences).to.be.ok; - expect(response.preferences).to.not.be.an('array'); - expect(response.preferences).to.have.property('choice1'); - expect(response.preferences).to.have.property('flag1'); - done(); - }); - }); - - it('hasOne Child posted as {}, can be added later as part of PUT', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 2002 - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.not.have.property('preferences'); - response.preferences = {__row_status:'added',choice1: 'Bengaluru', flag1: false}; - - customer.upsert(response, callContext, function(err2, results2){ - if (err2) { - return done(err2); - } - - var response2 = results2.__data; - expect(response2).to.have.property('preferences'); - expect(response2.preferences).to.be.ok; - expect(response2.preferences).to.not.be.an('array'); - expect(response2.preferences).to.have.property('choice1'); - expect(response2.preferences).to.have.property('flag1'); - done(); - }); - }); - }); - - it('hasOne Child posted as {}, record can be deleted', function (done) { - var customer = loopback.getModel('HOUser', callContext); - customer.create({ - name: 'Smith', - id: 2003, - preferences: { - choice1: 'Delhi', - flag1: true - } - }, callContext, function (err, results) { - if (err) { - return done(err); - } - var response = results.__data; - expect(response).to.have.property('name'); - expect(response).to.have.property('preferences'); - expect(response.preferences).to.be.ok; - - response.preferences.__row_status = 'deleted'; - customer.upsert(response, callContext, function(err2, results2){ - if (err2) { - return done(err2); - } - - var response2 = results2.__data; - expect(response2).to.not.have.property('preferences'); - done(); - }); - - }); - }); - -}); diff --git a/test/concurrency-test.js b/test/concurrency-test.js deleted file mode 100644 index 380e71e..0000000 --- a/test/concurrency-test.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var log = require('oe-logger')('concurrency-test'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var loopback = require('loopback'); - -describe(chalk.blue('concurrency-test'), function () { - this.timeout(20000); - var testModelName = 'MyConcurrentModel'; - var url = bootstrap.basePath + '/' + testModelName + 's'; - var testModelDetails = { - name: testModelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - before('create model', function (done) { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - if (model) { - done(); - } else { - ModelDefinition.create(testModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', err); - done(err); - } else { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - model.evObserve('before save', function testBeforeSaveFnConc(ctx, next) { - process.nextTick(function () { - next(); - }); - }); - done(); - } - }); - } - }); - var user1 = { - 'username': 'foo', - 'password': 'bar', - 'email': 'foo@gmail.com', - 'tenantId': 'test-tenant' - }; - before('create Test User', function (done) { - bootstrap.createTestUser(user1, 'admin', done); - }); - - var accessToken = ''; - - it('login', function (done) { - var postData = { - 'username': user1.username, - 'password': user1.password - }; - var postUrl = bootstrap.basePath + '/BaseUsers/login?some_param=abcd'; - - // without jwt token - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - accessToken = response.body.id; - done(); - }); - }); - - var savedInstance; - var data = { - 'name': 'Name1', - 'description': 'OK' - }; - - - it('create and find data ', function (done) { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) { - model.create(data, bootstrap.defaultContext, function (err, res) { - model.find({ - 'where': { - 'name': 'Name1' - } - }, bootstrap.defaultContext, function (err, res) { - savedInstance = res[0]; - log.debug(bootstrap.defaultContext, 'verify data ', err, res); - expect(res[0].description).to.be.equal('OK'); - res[0].reload(bootstrap.defaultContext, function (err, reload) { - expect(reload.description).to.be.equal('OK'); - done(); - }); - }); - }); - }); - }); - - it('concurrency with url', function (done) { - var count = 0; - var errorCount = 0; - var concurrentData1 = JSON.parse(JSON.stringify(savedInstance)); - concurrentData1.name = 'Name2'; - var concurrentData2 = JSON.parse(JSON.stringify(savedInstance)); - concurrentData2.name = 'Name3'; - var api = defaults(supertest(bootstrap.app)); - - var postUrl = url + '?access_token=' + accessToken; - - api.set('Accept', 'application/json') - .put(postUrl) - .send(concurrentData1) - .end(function (err, resp) { - count++; - if (err || resp.status !== 200) { - errorCount++; - } else { - savedInstance = resp.body; - } - if (count === 2) { - if (errorCount === 1) { - done(); - } else { - done(new Error('concurrent updates are overwriting each other')); - } - } - - }); - - api.set('Accept', 'application/json') - .put(postUrl) - .send(concurrentData2) - .end(function (err, resp) { - count++; - if (err || resp.status !== 200) { - errorCount++; - } else { - savedInstance = resp.body; - } - if (count === 2) { - if (errorCount === 1) { - done(); - } else { - done(new Error('concurrent updates are overwriting each other')); - } - } - }); - }); - - it('concurrency with js', function (done) { - var count = 0; - var errorCount = 0; - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - var concurrentData1 = JSON.parse(JSON.stringify(savedInstance)); - concurrentData1.name = 'Name2'; - var concurrentData2 = JSON.parse(JSON.stringify(savedInstance)); - concurrentData2.name = 'Name3'; - - model.upsert(concurrentData1, bootstrap.defaultContext, function (err, data) { - if (err) { - errorCount++; - } - count++; - if (count === 2) { - if (errorCount === 1) { - done(); - } else { - done(new Error('concurrent updates are overwriting each other')); - } - } - }); - model.upsert(concurrentData2, bootstrap.defaultContext, function (err, data) { - if (err) { - errorCount++; - } - count++; - if (count === 2) { - if (errorCount === 1) { - done(); - } else { - done(new Error('concurrent updates are overwriting each other')); - } - } - }); - }); - -}); diff --git a/test/config-merge-test.js b/test/config-merge-test.js deleted file mode 100644 index 601fc17..0000000 --- a/test/config-merge-test.js +++ /dev/null @@ -1,476 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var path = require('path'); -var _ = require('lodash'); -chai.use(require('chai-things')); -var mergeUtil = require('../lib/merge-util'); -var expect = chai.expect; -var options = bootstrap.options; - -describe(chalk.blue('Config files merge test'), function () { - xit('Test - merge config ', function (done) { - var serverConfig = { - 'restApiRoot': '/api', - 'host': '0.0.0.0', - 'port': 3000, - 'cookieSecret': 'cookie-secret', - 'remoting': { - 'context': { - 'enableHttpContext': true - }, - 'rest': { - 'normalizeHttpPath': false, - 'xml': false - }, - 'json': { - 'strict': false, - 'limit': '2048kb' - }, - 'urlencoded': { - 'extended': true, - 'limit': '2048kb' - }, - 'cors': false, - 'errorHandler': { - 'disableStackTrace': false - } - }, - 'legacyExplorer': false, - 'log': { - 'type': 'rotating-file', - 'path': './fw-log.log', - 'period': '1d', - 'count': 3 - }, - 'frameworkdsname': 'ev_db', - 'systemId': 'temporaryId', - 'disablecaching': false, - 'disableWorkflow': true, - 'modelstocache': ['ACL', 'ModelDefinition', 'PersonalizationRule'], - 'tenantsource': 'HEADER', - 'tenantkey': 'tenant_id', - 'app': 'oecloud.io', - 'realm': 'oecloud', - 'enableJWTAssertion': false, - 'encryptionAlgorithm': 'crypto.aes256', - 'encryptionPassword': 'SomePassword' - }; - var clientConfig = { - 'restApiRoot': '/api', - 'host': '0.0.0.0', - 'port': 5000, - 'cookieSecret': 'cookie-secret', - 'remoting': { - 'context': { - 'enableHttpContext': true - }, - 'rest': { - 'normalizeHttpPath': false, - 'xml': false - }, - 'json': { - 'strict': false, - 'limit': '2048kb' - }, - 'urlencoded': { - 'extended': true, - 'limit': '2048kb' - }, - 'cors': false, - 'errorHandler': { - 'disableStackTrace': false - } - }, - 'legacyExplorer': false, - 'disablecaching': true, - 'modelstocache': ['Loan', 'Country'] - }; - mergeUtil.mergeFn(serverConfig, clientConfig); - - expect(serverConfig).not.to.be.null; - expect(serverConfig).not.to.be.empty; - expect(serverConfig).not.to.be.undefined; - expect(serverConfig.port).to.be.equal(5000); - expect(serverConfig.modelstocache).to.be.instanceof(Array); - expect(serverConfig.modelstocache).to.have.length(5); - expect(serverConfig.frameworkdsname).to.be.equal('ev_db'); - expect(serverConfig.disablecaching).to.be.equal(true); - done(); - }); - - xit('Test - merge middleware ', function (done) { - var serverMiddleware = { - 'initial:before': { - 'loopback#favicon': {} - }, - 'initial': { - 'compression': {}, - 'cors': { - 'params': { - 'origin': true, - 'credentials': true, - 'maxAge': 86400 - } - } - }, - 'session': {}, - 'auth': {}, - 'parse': {}, - 'routes:before': { - 'loopback#context': {}, - './middleware/http-method-overriding-filter': {}, - './middleware/context-populator-filter': { - 'enabled': true - }, - './middleware/req-logging-filter': {}, - './middleware/contributor-selector-filter': {}, - './middleware/useragent-populator-filter': {}, - '../server/middleware/model-discovery-filter': ['restApiPath'] - }, - 'routes:after': { - }, - 'files': { - 'loopback#static': { - 'params': '$!../client' - } - }, - 'final': { - 'loopback#urlNotFound': {} - }, - 'final:after': { - - } - }; - - var clientMiddleware = { - 'initial:before': { - 'loopback#favicon': {} - }, - 'initial': { - 'compression': {}, - 'cors': { - 'params': { - 'origin': true, - 'credentials': true, - 'maxAge': 86400 - } - } - }, - 'session': {}, - 'auth': {}, - 'parse': {}, - 'routes:before': { - './middleware/routes-before-middleware': {}, - '../server/middleware/model-discovery-filter': ['restApiNewPath'] - }, - 'routes:after': { - }, - 'files': { - 'loopback#static': { - 'params': '$!../client' - } - }, - 'final': { - './middleware/final-middleware': {} - }, - 'final:after': { - 'errorhandler': {} - } - }; - var temp = ''; - var relativeServerPath = replaceAll(path.relative(options.appRootDir, options.clientAppRootDir), '\\', '/') + '/'; - var relativePath = replaceAll(path.relative(options.appRootDir, ''), '\\', '/') + '/'; - function escapeRegExp(str) { - return str.replace(/([.*+?^=!:${}()\[\]\/\\])/g, '\\$1'); - } - function replaceAll(str, find, replace) { - return str.replace(new RegExp(escapeRegExp(find), 'g'), replace); - } - var tempmiddleware = replaceAll(JSON.stringify(clientMiddleware), '../', temp); - tempmiddleware = replaceAll(tempmiddleware, './', relativeServerPath); - clientMiddleware = JSON.parse(replaceAll(tempmiddleware, temp, relativePath)); - - mergeUtil.mergeMiddlewareConfig(serverMiddleware, clientMiddleware); - expect(serverMiddleware).not.to.be.null; - expect(serverMiddleware).not.to.be.empty; - expect(serverMiddleware).not.to.be.undefined; - expect(Object.getOwnPropertyNames(serverMiddleware['routes:before']).length).to.be.equal(8); - expect(Object.getOwnPropertyNames(serverMiddleware.final).length).to.be.equal(2); - expect(Object.getOwnPropertyNames(serverMiddleware['final:after']).length).to.be.equal(1); - done(); - }); - - xit('Test - merge model-config ', function (done) { - var serverModelConfig = { - '_meta': { - 'sources': [ - 'loopback/common/models', - 'loopback/server/models', - '../common/models', - './models' - ], - 'mixins': [ - 'loopback/common/mixins', - 'loopback/server/mixins', - '../common/mixins', - './mixins' - ] - }, - 'BaseUser': { - 'dataSource': 'ev_db', - 'public': true - }, - 'ModelDefinition': { - 'dataSource': 'db', - 'public': true - }, - 'DataSourceDefinition': { - 'dataSource': 'db', - 'public': true - } - }; - - var clientModelConfig = { - '_meta': { - 'sources': [ - 'loopback/common/models', - 'loopback/server/models', - '../common/models/demo' - ], - 'mixins': [ - 'loopback/common/mixins', - 'loopback/server/mixins' - ] - }, - 'BaseUser': { - 'dataSource': 'db', - 'public': true - }, - 'Address': { - 'dataSource': 'db', - 'public': true - }, - 'ComboValue': { - 'dataSource': 'db', - 'public': true - }, - 'Contact': { - 'dataSource': 'db', - 'public': true - }, - 'Country': { - 'dataSource': 'db', - 'public': true - }, - 'Customer': { - 'dataSource': 'db', - 'public': true - }, - 'Deposit': { - 'dataSource': 'db', - 'public': true - } - }; - - function modifyPath(element) { - if (element.indexOf('./') === 0 || element.indexOf('../') === 0) { - // Relative path - // element = path.resolve(options.clientAppRootDir, element); - return path.relative(options.appRootDir, path.resolve(options.clientAppRootDir, element)); - } - return element; - } - - if (clientModelConfig._meta && clientModelConfig._meta.sources) { - clientModelConfig._meta.sources = _.map(clientModelConfig._meta.sources, modifyPath); - } - if (clientModelConfig._meta && clientModelConfig._meta.mixins) { - clientModelConfig._meta.mixins = _.map(clientModelConfig._meta.mixins, modifyPath); - } - - mergeUtil.mergeFn(serverModelConfig, clientModelConfig); - - expect(serverModelConfig).not.to.be.null; - expect(serverModelConfig).not.to.be.empty; - expect(serverModelConfig).not.to.be.undefined; - expect(Object.getOwnPropertyNames(serverModelConfig).length).to.be.equal(10); - expect(serverModelConfig._meta.sources).to.have.length(5); - expect(serverModelConfig._meta.mixins).to.have.length(4); - expect(serverModelConfig.BaseUser.dataSource).to.be.equal('db'); - done(); - }); - - xit('Test - merge datasources ', function (done) { - var serverDatasource = { - 'nullsrc': { - 'name': 'nullsrc', - 'connector': 'memory' - }, - 'db': { - 'host': '127.0.0.1', - 'port': 27017, - 'url': 'mongodb://127.0.0.1:27017/mem_db', - 'database': 'mem_db', - 'password': 'admin', - 'name': 'db', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - }, - 'ev_db': { - 'host': '127.0.0.1', - 'port': 27017, - 'url': 'mongodb://127.0.0.1:27017/ev_db', - 'database': 'ev_db', - 'password': 'admin', - 'name': 'ev_db', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - }, - 'fin_db': { - 'host': '127.0.0.1', - 'port': 27017, - 'url': 'mongodb://127.0.0.1:27017/fin_db', - 'database': 'fin_db', - 'password': 'admin', - 'name': 'fin_db', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - } - }; - - var clientDatasource = { - 'db2': { - 'name': 'db', - 'connector': 'memory' - }, - 'fin_db': { - 'host': 'localhost', - 'port': 27017, - 'url': 'mongodb://localhost:27017/fin_db', - 'database': 'fin_db', - 'password': 'admin', - 'name': 'fin_db', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - } - }; - - mergeUtil.mergeDataSourcesObjects(serverDatasource, clientDatasource); - - expect(serverDatasource).not.to.be.null; - expect(serverDatasource).not.to.be.empty; - expect(serverDatasource).not.to.be.undefined; - expect(Object.getOwnPropertyNames(serverDatasource).length).to.be.equal(5); - expect(serverDatasource.fin_db.host).to.be.equal('localhost'); - expect(serverDatasource.fin_db.connector).to.be.equal('mongodb'); - done(); - }); - - xit('Test - merge component config ', function (done) { - var serverComponetConfig = { - 'loopback-component-explorer': { - 'mountPath': '/explorer' - }, - './components/my-component.js': { - 'path': '/my-component' - }, - './components/new-component': 'myApp' - }; - - var clientComponentConfig = { - 'loopback-component-explorer': { - 'mountPath': '/swagger' - } - }; - - mergeUtil.mergeFn(serverComponetConfig, clientComponentConfig); - - expect(serverComponetConfig).not.to.be.null; - expect(serverComponetConfig).not.to.be.empty; - expect(serverComponetConfig).not.to.be.undefined; - expect(Object.getOwnPropertyNames(serverComponetConfig).length).to.be.equal(3); - expect(serverComponetConfig['loopback-component-explorer'].mountPath).to.be.equal('/swagger'); - done(); - }); - - xit('Test - merge providers.json config ', function (done) { - - var loadAppProviders = mergeUtil.loadAppProviders; - var applist = [ - { - "path": "oe-workflow", - "enabled": false - }, - { - "path": "./", - "enabled": true - } - ] - var providers = loadAppProviders(applist); - expect(providers).not.to.be.null; - expect(providers).not.to.be.undefined; - done(); - }); - - xit('Test - merge log config ', function (done) { - - var loadAppLogConfig = mergeUtil.loadAppLogConfig; - var applist = [ - { - "path": "oe-workflow", - "enabled": false - }, - { - "path": "./", - "enabled": true - } - ] - var logConfig = loadAppLogConfig(applist); - expect(logConfig).not.to.be.null; - expect(logConfig).not.to.be.undefined; - done(); - }); - - it('Test - Merge all configs using applist', function (done) { - var loadAppList = mergeUtil.loadAppList; - var applist = [ - { - "path": "oe-workflow", - "enabled": false - }, - { - "path": "./", - "enabled": true - } - ] - var options = loadAppList(applist, "./dummyClientPath", {}); - expect(options).not.to.be.null; - expect(options).not.to.be.empty; - expect(options).not.to.be.undefined; - expect(options).to.include.keys('appRootDir', 'appConfigRootDir', 'modelsRootDir', - 'dsRootDir', 'mixinDirs', 'bootDirs', 'clientAppRootDir', 'skipConfigurePassport', - 'config', 'middleware', 'models', 'dataSources', 'components', 'providerJson'); - expect(options.config).not.to.be.null; - expect(options.config).not.to.be.empty; - expect(options.config).not.to.be.undefined; - expect(options.config).to.include.keys('frameworkdsname', 'restApiRoot', 'host', 'port'); - expect(options.config.modelstocache).to.be.instanceof(Array); - expect(options.middleware).not.to.be.null; - expect(options.middleware).not.to.be.empty; - expect(options.middleware).not.to.be.undefined; - // Removed 'auth' middleware check since the object is empty. - expect(options.middleware).to.include.keys('initial:before', 'session:before', 'auth:after'); - done(); - }); -}); diff --git a/test/config.json b/test/config.json new file mode 100644 index 0000000..6bd6df0 --- /dev/null +++ b/test/config.json @@ -0,0 +1,24 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "context": false, + "rest": { + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false, + "handleErrors": false + }, + "legacyExplorer": false, + "enableAuthCookie" : true +} diff --git a/test/consistenthash/client.js b/test/consistenthash/client.js deleted file mode 100644 index c3c6041..0000000 --- a/test/consistenthash/client.js +++ /dev/null @@ -1,325 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -// Pass the port number and proxy url as command line args in the same order. -var bootstrap = require('../bootstrap'); -var chai = require('chai'); -var app = bootstrap.app; -var models = bootstrap.models; -var expect = chai.expect; -chai.use(require('chai-things')); -var supertest = require('supertest'); -var request, url; -var modelPlural = 'ConsistentHashModels'; -var numberOfReqs = 10; -var host, port; -if (process.argv[3] && !isNaN(parseInt(process.argv[3]))) { - app.set('port', process.argv[3]); -} -// making sure this node does not connect to tx-router -process.env.TX_ROUTER_HOST = '128.0.0.1'; -if (process.env.APP_URL) { - url = process.env.APP_URL; - request = supertest(url); -} -describe('Consistent Hash Client', function () { - var access_token = ''; - before('Login and get Access Token', function (done) { - bootstrap.login(function (accessToken) { - access_token = accessToken; - done(); - }); - }); - describe('With REST Api', function () { - this.timeout(10000); - describe('GET Round Robin', function () { - var ac1Host, ac1Port, ac2Host, ac2Port; - it('/api/ConsistentHashModels', function (done) { - if (url && request) { - var reqUrl = '/' + modelPlural + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - ac1Host = res.text[0].hostname; - ac1Port = res.text[0].portNumber; - ac2Host = res.text[1].hostname; - ac2Port = res.text[1].portNumber; - doTestRoundRobin(0, done); - } else { - done(new Error("Response doesnt have body in it.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - function doTestRoundRobin(count, done) { - if (count < numberOfReqs) { - var reqUrl = '/' + modelPlural + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - expect(res.text).not.to.be.null; - res.text = JSON.parse(res.text); - // the below tests are valid - atleast the hostname in docker - framework paas environment - //expect(res.text[0].hostname).not.to.be.equal(ac1Host); - //expect(res.text[1].hostname).not.to.be.equal(ac2Host); - expect(res.text[0].portNumber).not.to.be.equal(ac1Port); - expect(res.text[1].portNumber).not.to.be.equal(ac2Port); - expect(res.text[0].hostname).to.be.equal(res.text[1].hostname); - expect(res.text[0].portNumber).to.be.equal(res.text[1].portNumber); - ac1Host = res.text[0].hostname; - ac1Port = res.text[0].portNumber; - ac2Host = res.text[1].hostname; - ac2Port = res.text[1].portNumber; - doTestRoundRobin(count + 1, done); - } - }); - } else { - done(); - } - } - }); - }); - describe('GET by id', function () { - it('consist-101', function (done) { - if (url && request) { - var reqUrl = '/' + modelPlural + '/consist-101?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - host = res.text.hostname; - port = res.text.portNumber; - doTestGetById(0, 'consist-101', done); - } else { - done(new Error("Response doesnt have body in it.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - }); - it('consist-201', function (done) { - if (url && request) { - var reqUrl = '/' + modelPlural + '/consist-201?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - host = res.text.hostname; - port = res.text.portNumber; - doTestGetById(0, 'consist-201', done); - } else { - done(new Error("Response doesnt have body in it.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - }); - function doTestGetById(count, id, done) { - if (count < numberOfReqs) { - var reqUrl = '/' + modelPlural + '/' + id + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - expect(res.text).not.to.be.null; - res.text = JSON.parse(res.text); - expect(res.text.hostname).to.be.equal(host); - expect(res.text.portNumber).to.be.equal(port); - - doTestGetById(count + 1, id, done); - } - }); - } else { - done(); - } - } - }); - describe('Update Attributes', function () { - it('test', function (done) { - var acnt1Host, acnt1Port, acnt2Host, acnt2Port, urlPath = 'customUpdateAttributes'; - if (url && request) { - var reqUrl = '/' + modelPlural + '/' + urlPath + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - if (res.text.acct1 && res.text.acct2) { - acnt1Host = res.text.acct1.hostname; - acnt1Port = res.text.acct1.portNumber; - acnt2Host = res.text.acct2.hostname; - acnt2Port = res.text.acct2.portNumber; - doTestUpdate(0, done); - } else { - done(new Error("Response is not in proper format as expected.")); - } - } else { - done(new Error("Response doesnt have body.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - function doTestUpdate(count, done) { - if (count < numberOfReqs) { - var reqUrl = '/' + modelPlural + '/' + urlPath + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - expect(res.text).not.to.be.null; - res.text = JSON.parse(res.text); - expect(res.text.acct1).not.to.be.null; - expect(res.text.acct2).not.to.be.null; - expect(res.text.acct1.hostname).to.be.equal(acnt1Host); - expect(res.text.acct1.portNumber).to.be.equal(acnt1Port); - expect(res.text.acct2.hostname).to.be.equal(acnt2Host); - expect(res.text.acct2.portNumber).to.be.equal(acnt2Port); - doTestUpdate(count + 1, done); - } - }); - } else { - done(); - } - } - }) - }); - describe('Upsert', function () { - it('test', function (done) { - var acnt1Host, acnt1Port, acnt2Host, acnt2Port, urlPath = 'customUpsert'; - if (url && request) { - var reqUrl = '/' + modelPlural + '/' + urlPath + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - if (res.text.acct1 && res.text.acct2) { - acnt1Host = res.text.acct1.hostname; - acnt1Port = res.text.acct1.portNumber; - acnt2Host = res.text.acct2.hostname; - acnt2Port = res.text.acct2.portNumber; - doTestUpsert(0, done); - } else { - done(new Error("Response is not in proper format as expected.")); - } - } else { - done(new Error("Response doesnt have body.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - function doTestUpsert(count, done) { - if (count < numberOfReqs) { - var reqUrl = '/' + modelPlural + '/' + urlPath + '?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - expect(res.text).not.to.be.null; - res.text = JSON.parse(res.text); - expect(res.text.acct1).not.to.be.null; - expect(res.text.acct2).not.to.be.null; - expect(res.text.acct1.hostname).to.be.equal(acnt1Host); - expect(res.text.acct1.portNumber).to.be.equal(acnt1Port); - expect(res.text.acct2.hostname).to.be.equal(acnt2Host); - expect(res.text.acct2.portNumber).to.be.equal(acnt2Port); - doTestUpsert(count + 1, done); - } - }); - } else { - done(); - } - } - }) - }); - function getRequest(reqUrl, cb) { - request - .get(reqUrl) - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .expect(200) - .end(function (err, response) { - cb(err, response); - }); - } - it('Get Report', function (done) { - var acnt1Host, acnt1Port, acnt2Host, acnt2Port; - if (url && request) { - var reqUrl = '/' + modelPlural + '/report?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - if (res.text) { - res.text = JSON.parse(res.text); - if (res.text.acct1 && res.text.acct2) { - acnt1Host = res.text.acct1.hostname; - acnt1Port = res.text.acct1.portNumber; - acnt2Host = res.text.acct2.hostname; - acnt2Port = res.text.acct2.portNumber; - doTestReport(0, done); - } else { - done(new Error("Response is not in proper format as expected.")); - } - } else { - done(new Error("Response doesnt have body.")); - } - } - }); - } else { - done(new Error("Proxy url not provided, couldn't construct the request object.")); - } - function doTestReport(count, done) { - if (count < numberOfReqs) { - var reqUrl = '/' + modelPlural + '/report?access_token=' + access_token; - getRequest(reqUrl, function (err, res) { - if (err) { - done(err); - } else { - expect(res.text).not.to.be.null; - res.text = JSON.parse(res.text); - expect(res.text.acct1).not.to.be.null; - expect(res.text.acct2).not.to.be.null; - expect(res.text.acct1.hostname).to.be.equal(acnt1Host); - expect(res.text.acct1.portNumber).to.be.equal(acnt1Port); - expect(res.text.acct2.hostname).to.be.equal(acnt2Host); - expect(res.text.acct2.portNumber).to.be.equal(acnt2Port); - doTestReport(count + 1, done); - } - }); - } else { - done(); - } - } - }); - }); - describe('With Programmatic Api', function () { - //this.timeout(10000); - }); - after('', function (done) { - done(); - }); -}); diff --git a/test/consistenthash/haproxy.cfg b/test/consistenthash/haproxy.cfg deleted file mode 100644 index c3e6d3d..0000000 --- a/test/consistenthash/haproxy.cfg +++ /dev/null @@ -1,22 +0,0 @@ -global - daemon - stats timeout 30 -# debug -# stats socket /var/run/haproxy.stat - -defaults - mode http - timeout connect 5000 - timeout client 50000 - timeout server 50000 - -frontend http-in - bind *:8080 - mode http - default_backend rrservers - - -backend rrservers - http-send-name-header 'x-evproxy-server-name' - server server3 127.0.0.1:3000 id 5 check - server server4 127.0.0.1:3100 id 6 check diff --git a/test/consistenthash/server.js b/test/consistenthash/server.js deleted file mode 100644 index 22c0584..0000000 --- a/test/consistenthash/server.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('../bootstrap'); -var app = bootstrap.app; -if (process.argv[3] && !isNaN(parseInt(process.argv[3]))) { - app.set('port', process.argv[3]); -} -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var modelName = 'ConsistentHashModel'; -var os = require('os'); -var loopback=require('loopback'); - -describe('Consistent Hash Server', function () { - var model; - before('Create Model and Upload Data', function (done) { - this.timeout(4000); - bootstrap.login(function (accessToken) { - console.log("AccessToken ", accessToken); - }); - // Change to findOrCreate - models.ModelDefinition.findOne({ 'where': { 'name': modelName } }, bootstrap.defaultContext, function (err, res) { - if (!res) { - var consistHashModel = { - name: modelName, - base: 'BaseEntity', - plural: 'ConsistentHashModels', - options: { - validateUpsert: true, - proxyEnabled: true - }, - properties: { - accountName: { - type: 'string', - required: true - }, - limitAmount: { - type: 'number', - default: 0 - }, - portNumber: { - type: 'string' - }, - hostname: { - type: 'string' - } - }, - filebased: false - }; - models.ModelDefinition.create(consistHashModel, bootstrap.defaultContext, function (err, modelRes) { - if (err) { - console.log(err); - } - expect(err).to.be.null; - model = loopback.getModel(modelName, bootstrap.defaultContext); - checkAndCreateData(done); - }); - } else { - model = loopback.getModel(modelName, bootstrap.defaultContext); - checkAndCreateData(done); - } - }); - - function checkAndCreateData(done) { - // Change to findOrCreate - model.find({ where: { id: { inq: ['consist-101', 'consist-201'] } } }, bootstrap.defaultContext, function (err, val) { - if (val.length > 0) { - done(); - } else { - model.create([{ accountName: 'tywin', id: 'consist-101' }, { accountName: 'tyrion', id: 'consist-201' }], bootstrap.defaultContext, function (err, res) { - done(); - }); - } - }); - model.report = function report1(options, cb) { - - model.findById('consist-101', options, function (err, rec1) { - if (err) { - return cb(err, {}); - } - model.findById('consist-201', options, function (err, rec2) { - if (err) { - return cb(err, {}); - } - var data = { - acct1: rec1, - acct2: rec2 - } - return cb(err, data); - }); - }); - }; - model.remoteMethod('report', { - description: 'Report of Getting accounts by id\'s and combining them.', - accessType: 'READ', - accepts: [ - ], - http: { - verb: 'GET', - path: '/report' - }, - returns: { - type: 'object', - root: true - } - }); - model.observe('after accesss', function (ctx, next) { - var data = ctx.instance || ctx.currentInstance || ctx.data || ctx.accdata; - if (data) { - if (Array.isArray(data) || data === Array) { - data.forEach(function (item) { - item.portNumber = app.get('port'); - item.hostname = os.hostname(); - }); - } else { - data.portNumber = app.get('port'); - data.hostname = os.hostname(); - } - } - next(); - }); - model.customUpdateAttributes = function (options, cb) { - - model.findById('consist-101', options, function (err, rec1) { - if (err) { - return cb(err); - } - rec1.limitAmount = rec1.limitAmount + 1000; - rec1.updateAttributes(rec1, options, function (err, rec1) { - model.findById('consist-201', options, function (err, rec2) { - if (err) { - return cb(err); - } - rec2.limitAmount = rec2.limitAmount + 1000; - rec2.updateAttributes(rec2, options, function (err, rec2) { - var data = { - acct1: rec1, - acct2: rec2 - }; - cb(err, data); - }); - }); - }); - }); - }; - - model.remoteMethod('customUpdateAttributes', { - description: 'increarse limit amount by 1000 for consist-101, consist-201', - accessType: 'WRITE', - accepts: [ - ], - http: { - verb: 'GET', - path: '/customUpdateAttributes' - }, - - returns: { - type: 'object', - root: true - } - }); - - model.customUpsert = function (options, cb) { - - model.findById('consist-101', options, function (err, rec1) { - if (err) { - return cb(err); - } - rec1.limitAmount = rec1.limitAmount + 1000; - model.upsert(rec1, options, function (err, rec1) { - model.findById('consist-201', options, function (err, rec2) { - if (err) { - return cb(err); - } - rec2.limitAmount = rec2.limitAmount + 1000; - model.upsert(rec2, options, function (err, rec2) { - var data = { - acct1: rec1, - acct2: rec2 - }; - cb(err, data); - }); - }); - }); - }); - }; - model.remoteMethod('customUpsert', { - description: 'Custom Upsert', - accessType: 'WRITE', - accepts: [ - ], - http: { - verb: 'GET', - path: '/customUpsert' - }, - returns: { - type: 'object', - root: true - } - }); - } - }); - - it('Waiting for Client requests.', function (done) { - this.timeout(242000); - setTimeout(function () { - done(); - }, 240000); - }); - - -}); diff --git a/test/cr-model-test.js b/test/cr-model-test.js deleted file mode 100644 index 65cbb5c..0000000 --- a/test/cr-model-test.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var debug = require('debug')('version-mixin-test'); -var chalk = require('chalk'); -var loopback = require('loopback'); - -describe(chalk.blue('change request model test'), function () { - this.timeout(20000); - var modelName = 'ChangeRequestModelTest'; - var changeRequestId = null; - var updateInstanceId = null; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - } - }, - plural: modelName - }; - - before('create test model', function (done) { - models.ChangeRequest.defineProperty('_status', { - type: String, - required: false - }); - - models.ModelDefinition.create(modelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - debug('unable to create VersionMixinTest model'); - done(err); - } else { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.evObserve('before save', function beforeSaveHookToCreateCR(ctx, next) { - - var modelInstance = ctx.instance || ctx.data; - var skipCRCreation = ctx.options.updatedByWorkflow; - - if (!ctx.isNewInstance && !skipCRCreation) { - modelInstance.id = ctx.currentInstance.id; - var crData = { - 'originalEntityId': ctx.currentInstance.id, - 'originalEntityType': ctx.currentInstance._type, - 'changedEntity': modelInstance, - '_status': 'pendingApproval' - }; - var CRModel = loopback.getModel('ChangeRequest'); - var context = bootstrap.defaultContext; - context.skipCRCreation = skipCRCreation; - CRModel.create(crData, context, function evWorkflowMixinCRModelCreateCb(err, instance) { - if (err) { - return next(err, null); - } else { - // the data is deleted because on update it - // will be sent to - // change Request and no change should be - // done to current - // Instance of the model - delete ctx.data; - ctx.instanceUpdated = true; - ctx.updatedInstance = instance; - ctx.options.statusCode = 202; - return next(); - } - }); - } else { - return next(); - } - }); - done(); - } - }); - }); - - - it('Should check if change request model have publish, cancel and reject api', function (done) { - expect(typeof models.ChangeRequest.publish).to.be.equal('function'); - expect(typeof models.ChangeRequest.reject).to.be.equal('function'); - expect(typeof models.ChangeRequest.cancel).to.be.equal('function'); - expect(Object.keys(models.ChangeRequest.definition.properties)).to.include.members(['_status']); - done(); - }); - - it('Should check if ChangeRequestModelTest has a before save observer', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var observerList = model._observers['before save']; - expect(observerList).not.to.be.undefined; - done(); - }); - - it('Should create a record in ChangeRequestModelTest and update the same', function (done) { - this.timeout(5000); - var postData = { - 'name': 'testone' - }; - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.name = 'testUpdate'; - postData._version = res._version; - updateInstanceId = res.id; - res.updateAttributes(postData, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - //expect(res2).to.be.equal(res); - models.ChangeRequest.find({}, bootstrap.defaultContext, function (err3, res3) { - if (err) { - done(err); - } else { - expect(res3).to.have.length(1); - expect(res3[0].changedEntity.name).to.be.equal('testUpdate'); - expect(res3[0]._status).to.be.equal('pendingApproval'); - changeRequestId = res3[0].id; - done(); - } - }); - } - }); - } - }); - }); - - it('should be able to reject a change request ', function (done) { - // dummy data set for process instance. - models.ChangeRequest.reject({}, changeRequestId, bootstrap.defaultContext, function (err, instance) { - if (err) { - done(err); - } else { - expect(instance._status).to.be.equal('rejected'); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.findById(updateInstanceId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal('testone'); - done(); - } - }); - } - }); - }); - - it('should be able to cancel a change request ', function (done) { - // dummy data set for process instance. - models.ChangeRequest.cancel({}, changeRequestId, bootstrap.defaultContext, function (err, instance) { - if (err) { - done(err); - } else { - expect(instance._status).to.be.equal('cancelled'); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.findById(updateInstanceId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal('testone'); - done(); - } - }); - } - }); - }); - - it('should be able to publish a change request ', function (done) { - models.ChangeRequest.publish({}, changeRequestId, bootstrap.defaultContext, function (err, instance) { - if (err) { - done(err); - } else { - expect(instance._status).to.be.equal('published'); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.findById(updateInstanceId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - }); - - it('should not be able to publish a change request with wrong change request id', function (done) { - models.ChangeRequest.publish({}, 'testFail', bootstrap.defaultContext, function (err, instance) { - if (err) { - expect(err).not.to.be.empty; - done(); - } else { - done(new Error('Change request published with wrong change request id')); - } - }); - }); -}); diff --git a/test/crypto-test.js b/test/crypto-test.js deleted file mode 100644 index 940d9b5..0000000 --- a/test/crypto-test.js +++ /dev/null @@ -1,202 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This test is for unit-testing the encryption feature in the framework. - * The test involves creating a test model with a property marked as - * "encrypted: true", inserting a record into the model, fetching the same - * by directly accessing the DB (bypassing the framework) as well as - * using the framework. For a successful test, the original property value - * should not match the value retrieved by directly accessing the DB, but it - * should match the value retrieved using the framework. - * - * Author: Ajith Vasudevan - */ - - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var app = bootstrap.app; -var models = bootstrap.models; -var loopback = require('loopback'); -var MongoClient = require('mongodb').MongoClient; -var debug = require('debug')('crypto-test'); -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var postgresHost = process.env.POSTGRES_HOST || 'localhost'; -var oracleHost = process.env.ORACLE_HOST || 'localhost'; -var oraclePort = process.env.ORACLE_PORT || 1521; -var oracleService = process.env.ORACLE_SID || 'orclpdb.ad.infosys.com'; -var oracleUser = process.env.ORACLE_USERNAME || 'oeadmin'; -var oraclePassword = process.env.ORACLE_PASSWORD || 'oeadmin'; - -describe('crypto Test', function () { - var modelName = 'CryptoTest'; - var dsname = 'db'; - var dbname = process.env.DB_NAME || dsname; - var ccNo = "1234-5678-9012-3456"; - - var TestModelSchema = { - 'creditCardNo': { - 'type': 'string', - 'encrypt': true, - 'required': true - } - }; - var opts = { - strict: true, - base: 'BaseEntity', - plural: modelName + "s", - mixins: { - "CryptoMixin": true, - }, - dataSourceName: dsname - }; - - - var TestModel = null; - var result1, result2 = null; - var id; - - - before('Create Test Model and do crypto test', function (done) { - this.timeout(200000); - // Get a datasource - var dataSource = app.datasources[dsname]; - proceed0(dataSource.name, done); - }); - - function proceed0(dataSourceName, done) { - // Create a TestModel - TestModel = loopback.createModel(modelName, TestModelSchema, opts); - app.model(TestModel, { - dataSource: dsname - }); - TestModel.destroyAll({}, bootstrap.defaultContext, function (err, info) { - proceed(dataSourceName, done); - }); - } - - - // Create a record in TestModel with encryption enabled on a field, and - // fetch the inserted record using standard framework API - // and get the value of the encrypted field - function proceed(dataSourceName, done) { - // Add a record - TestModel.create({ - creditCardNo: ccNo - }, bootstrap.defaultContext, function (err, data) { - if (err) { - done(); - } else { - id = data.id; - debug("id", id); - TestModel.findById(id, bootstrap.defaultContext, function (err1, data1) { - if (err1) { - done(err1); - } else { - debug("data1", data1); - result1 = data1.creditCardNo; - debug('result1', result1); - proceed2(dataSourceName, done); - } - }); - } - }); - } - - - // Fetch the inserted record directly from DB using MongoDB API - // and get the value of the encrypted field - function proceed2(dataSourceName, done) { - if (dataSourceName === 'mongodb') { - var url = 'mongodb://' + mongoHost + ':27017/' + dbname; - MongoClient.connect(url, function (err, db) { - if (err) { - done(err); - } else { - var collection = db.collection(modelName); - collection.findOne({ - _id: id - }, function (err2, data2) { - if (err2) { - done(err2); - } else { - debug('data2', data2); - result2 = data2 && data2.creditCardNo; - debug('result2', result2); - done(); - } - }); - } - }); - } else if (dataSourceName === 'oe-connector-oracle') { - var oracledb = require('oracledb'); - oracledb.getConnection({ - "password": oraclePassword, - "user": oracleUser, - "connectString": oracleHost + ":" + oraclePort + "/" + oracleService - }, function (err, connection) { - if (err) { - done(err); - } - connection.execute( - "SELECT * from " + modelName.toLowerCase(), - function (error, result) { - if (error) { - done(error); - } - debug('data2', result); - result2 = result.rows && result.rows[0].creditcardno; - debug('result2', result2); - done(); - }); - }); - } else { - var connectionString = "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbname; - var pg = require('pg'); - var client = new pg.Client(connectionString); - client.connect(function (err) { - if (err) { - done(err); - } else { - // console.log("Connected to Postgres server"); - var query = client.query("SELECT * from " + modelName.toLowerCase(), function (err2, data2) { - if (err2) { - done(err2); - } else { - debug('data2', data2); - result2 = data2.rows && data2.rows[0].creditcardno; - debug('result2', result2); - done(); - } - }); - } - }); - } - } - - - after('Cleanup', function (done) { - TestModel.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - console.log(err, info); - } - done(); - }); - }); - - - it('Should encrypt the creditCardNo field in TestModel when "encrypt" is set to "true"', function (done) { - expect(models[modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(ccNo).to.equal(result1); - expect(result1).to.not.equal(result2); - done(); - }); - -}); \ No newline at end of file diff --git a/test/data-acl-test.js b/test/data-acl-test.js deleted file mode 100644 index 7d7f898..0000000 --- a/test/data-acl-test.js +++ /dev/null @@ -1,437 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -chai.use(require('chai-things')); -var loopback = require('loopback'); -var async = require('async'); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; -var expect = chai.expect; - -var models = bootstrap.models; - -describe(chalk.blue('data-acl-test'), function () { - - var modelName = 'DataACLModel'; - - var user1 = { - 'username': 'foo', - 'password': 'bar', - 'email': 'foo@gmail.com' - }; - - var user2 = { - 'username': 'foo2', - 'password': 'bar2', - 'email': 'foo2@gmail.com' - }; - - var modeldefs = [ - { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - }, - 'product': { - 'type': 'string' - }, - 'quantity': { - 'type': 'number' - }, - 'department': { - 'type': 'string' - }, - 'category': { - 'type': 'string' - } - } - } - ]; - - var items = [ - { - name: 'book-d1-p1', - category: 'book', - qunatity: 200, - department: 'd1', - product: 'p1' - }, - { - name: 'boasdsadok', - category: 'book', - qunatity: 500, - department: 'd1', - product: 'p2' - }, - { - name: 'bo23324432ok', - category: 'book', - qunatity: 200, - department: 'd2', - id: '50023128-5d57-11e6-8b77-86f30ca893d4', - product: 'p1' - }, - { - name: 'item567', - category: 'book', - qunatity: 200, - department: 'd9', - product: 'p2' - }, - { - name: 'item568', - category: 'music', - qunatity: 200, - department: 'd1', - product: 'p1' - }, - { - name: 'item569', - category: 'others', - qunatity: 200, - department: 'd1', - product: 'p7' - }, - { - name: 'itemo600', - category: 'music', - qunatity: 200, - department: 'd2', - product: 'p2' - }, - { - name: 'item601', - category: 'book', - qunatity: 200, - department: 'd1', - product: 'p1' - }, - { - name: 'item0812341243', - category: 'music', - qunatity: 200, - department: 'd3', - product: 'special1' - }, - { - name: 'item888823', - category: 'special2', - qunatity: 200, - department: 'd1', - product: 'special2', - id: '49023128-5d57-11e6-8b77-86f30ca893d4' - }, - { - name: 'powaee3213', - category: 'others', - qunatity: 200, - department: 'm1', - product: 'm3' - }, - { - name: 'finance department only', - category: 'finance', - qunatity: 200, - department: 'finance', - product: 'm3' - }, - { - name: 'fetchbyid', - id: '49023128-5d57-11e6-8b77-86f30ca893d3', - category: 'byid', - department: 'byid' - } - - ]; - - var dataacls = [ - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEA', - accessType: 'READ', - group: 'category', - filter: { 'category': 'book' } - }, - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEA', - accessType: 'READ', - group: 'category', - filter: { 'category': 'music' } - }, - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEA', - accessType: 'READ', - group: 'department', - filter: { 'department': { 'inq': ['d1', 'd2'] } } - }, - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEB', - accessType: 'READ', - filter: { 'category': 'others' } - }, - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEB', - accessType: 'READ', - filter: { 'product': { 'inq': ['special1', 'special2'] } } - }, - { - model: modelName, - principalType: 'ROLE', - principalId: 'ROLEB', - accessType: 'READ', - filter: { 'department': '@ctx.department' } - } - - ]; - - var user1token; - var user2token; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - this.timeout(10000000); - - var cleanup = function (done) { - async.series([function (cb) { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - if (model) { - model.remove({}, bootstrap.defaultContext, function () { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - ModelDefinition.remove({ - 'name': modelName - }, bootstrap.defaultContext, function (err, res) { - cb(); - }); - }, function () { - done(); - }]); - }; - - before('setup model for dataacl', function (done) { - async.series([function (cb) { - cleanup(cb); - }, - function (cb) { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - if (model) { - model.remove({}, bootstrap.defaultContext, function () { - cb(); - }); - } else { - cb(); - } - }, - function (cb) { - ModelDefinition.remove({ - 'name': modelName - }, bootstrap.defaultContext, function (err, res) { - cb(); - }); - }, - function (cb) { - ModelDefinition.create(modeldefs, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', JSON.stringify(err)); - cb(); - } else { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.create(items, bootstrap.defaultContext, function (err, result) { - cb(); - }); - } - }); - }, - function (cb) { - models.DataACL.create(dataacls, bootstrap.defaultContext, function (err, res) { - cb(); - }); - }, - function (cb) { - bootstrap.createTestUser(user1, 'ROLEA', cb); - }, - function (cb) { - bootstrap.createTestUser(user2, 'ROLEB', cb); - }, - function () { - done(); - }]); - }); - - it('login1', function (done) { - var postData = { - 'username': user1.username, - 'password': user1.password - }; - - var postUrl = baseUrl + '/BaseUsers/login'; - - // without jwt token - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - user1token = response.body.id; - done(); - }); - }); - - it('fetch1', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName + 's?access_token=' + user1token; - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - response.forEach(function (item) { - expect((item.category === 'book' || - item.category === 'music') && - (item.department === 'd1' || - item.department === 'd2')).to.be.true; - }); - - done(); - }); - }); - - it('fetch with filter', function (done) { - var api = defaults(supertest(bootstrap.app)); - var filter = {where:{category:'book'}}; - - var url = bootstrap.basePath + '/' + modelName + 's?access_token=' + user1token + '&filter=' + JSON.stringify(filter); - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - response.forEach(function (item) { - expect((item.category === 'book') && - (item.department === 'd1' || - item.department === 'd2')).to.be.true; - }); - - done(); - }); - }); - - it('login2', function (done) { - var postData = { - 'username': user2.username, - 'password': user2.password - }; - - var postUrl = baseUrl + '/BaseUsers/login'; - - // without jwt token - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - user2token = response.body.id; - done(); - }); - }); - - it('fetch2', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName + 's?access_token=' + user2token; - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - response.forEach(function (item) { - expect(item.product === 'special1' || - item.product === 'special2' || - item.department === 'finance' || - item.category === 'others').to.be.true; - }); - done(); - }); - }); - - - it('fetchbyid - not permissioned', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName + 's/49023128-5d57-11e6-8b77-86f30ca893d3?access_token=' + user2token; - api - .get(url) - .expect(404).end(function (err, res) { - var response = res.body; - expect(response.error.code === 'MODEL_NOT_FOUND').to.be.true; - done(); - }); - }); - - it('fetchbyid - permissioned', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName + 's/49023128-5d57-11e6-8b77-86f30ca893d4?access_token=' + user2token; - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - expect(response.id === '49023128-5d57-11e6-8b77-86f30ca893d4').to.be.true; - done(); - }); - }); - - it('fetch and delete', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName + 's/50023128-5d57-11e6-8b77-86f30ca893d4/?access_token=' + user1token; - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response._version).not.to.be.null; - url = bootstrap.basePath + '/' + modelName + 's/50023128-5d57-11e6-8b77-86f30ca893d4/?access_token=' + user1token; - var api2 = defaults(supertest(bootstrap.app)); - api2.del(url, function (err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.count).to.be.equal(1); - done(); - - }); - }); - }); - - after('after clean up', function (done) { - cleanup(done); - }); - -}); - diff --git a/test/data-hierarchy-test.js b/test/data-hierarchy-test.js deleted file mode 100644 index 1c046fb..0000000 --- a/test/data-hierarchy-test.js +++ /dev/null @@ -1,1734 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var supertest = require('supertest'); -var app = bootstrap.app; -var api = supertest(app); -var apiV2 = bootstrap.api; -var models = bootstrap.models; -var logger = require('oe-logger'); -var log = logger('data-hierarchy-test'); -var loopback = require('loopback'); -var async = require('async'); - -describe(chalk.blue('Data Hierarchy Test --Programatic'), function () { - this.timeout(50000); - var RegionModel; - var ProductModel; - var SettingsModel; - - var regionModel = 'Region'; - var regionModelDetails = { - name: regionModel, - base: 'BaseEntity', - properties: { - 'regionName': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: true, - plural: regionModel, - mixins: { - 'HistoryMixin': true, - 'DataHierarchyMixin': true, - 'SoftDeleteMixin': false - }, - autoscope: [ - 'tenantId' - ], - 'hierarchyScope': ['regionHierarchy'] - }; - - var productModel = 'Product'; - var productModelDetails = { - name: productModel, - base: 'BaseEntity', - properties: { - 'productName': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: true, - plural: productModel, - mixins: { - 'HistoryMixin': true, - 'DataHierarchyMixin': true, - 'SoftDeleteMixin': false - }, - autoscope: [ - 'tenantId' - ], - hierarchyScope: ['regionHierarchy'] - }; - - var settingsModel = 'SystemSettings'; - var settingsModelDetails = { - name: settingsModel, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'unique': true - }, - 'value': 'object' - }, - strict: false, - idInjection: true, - plural: settingsModel, - mixins: { - 'HistoryMixin': true, - 'DataPersonalizationMixin': false, - 'DataHierarchyMixin': true, - 'SoftDeleteMixin': false - - }, - autoscope: [ - 'tenantId' - ], - hierarchyScope: ['regionHierarchy'], - upward: true - }; - - before('Create Test models', function (done) { - models.ModelDefinition.create(regionModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create Region model'); - done(err); - } else { - models.ModelDefinition.create(productModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create Product model'); - done(err); - } else { - models.ModelDefinition.create(settingsModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create Settings model'); - done(err); - } else { - RegionModel = loopback.getModel(regionModel, bootstrap.defaultContext); - ProductModel = loopback.getModel(productModel, bootstrap.defaultContext); - SettingsModel = loopback.getModel(settingsModel, bootstrap.defaultContext); - done(); - } - }); - } - }); - } - }); - }); - - after('Remove Data from Test Models', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - RegionModel.destroyAll({}, callContext, function (err, result) { - if (err) { - done(err); - } - ProductModel.destroyAll({}, callContext, function (err, result) { - if (err) { - done(err); - } - SettingsModel.destroyAll({}, callContext, function (err, result) { - if (err) { - done(err); - } - done(); - }); - }); - }); - }); - - it('Create region Hierarchy in region model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - - async.series([ - function (callback) { - var testData = { - 'regionName': 'Continents', - 'id': 'root' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Asia', - 'id': 'asia' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'India', - 'id': 'india', - 'parentId': 'asia' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Delhi', - 'id': 'delhi', - 'parentId': 'india' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,delhi,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Bangalore', - 'id': 'bangalore', - 'parentId': 'india' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,bangalore,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Japan', - 'id': 'japan', - 'parentId': 'asia' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,japan,'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Tokyo', - 'id': 'tokyo', - 'parentId': 'japan' - }; - RegionModel.create(testData, callContext, function (err, result) { - if (err) { - callback(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,japan,tokyo,'); - callback(); - } - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Create products hierarchy based on region', function (done) { - var callContext = {}; - - async.series([ - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,' - }; - var newProduct = { - 'productName': 'Coca-Cola' - }; - ProductModel.create(newProduct, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.productName).to.be.equal('Coca-Cola'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,'); - callback(); - } - }); - }, - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - var newProduct = { - 'productName': 'Diet coke' - }; - ProductModel.create(newProduct, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.productName).to.be.equal('Diet coke'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,'); - callback(); - } - }); - }, - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,delhi,' - }; - var newProduct = { - 'productName': 'Coke Zero' - }; - ProductModel.create(newProduct, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.productName).to.be.equal('Coke Zero'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,delhi,'); - callback(); - } - }); - }, - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - var newProduct = { - 'productName': 'Pulpy Orange' - }; - ProductModel.create(newProduct, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.productName).to.be.equal('Pulpy Orange'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,'); - callback(); - } - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Get products based on regional context Asia/India', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - - ProductModel.find({}, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(2); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India/Delhi', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,delhi,' - }; - - ProductModel.find({}, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(1); - expect(res[0].productName).to.be.equal('Coke Zero'); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth *', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - - ProductModel.find({ 'depth': '*' }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(3); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth 1', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - - ProductModel.find({ 'depth': '1' }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(3); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth 3(Actual level of hierarchy ends at 1)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - - ProductModel.find({ 'depth': '3' }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(3); - done(); - } - }); - }); - - it('Create SystemSetings based on regionHierarchy', function (done) { - var callContext = {}; - - async.series([ - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - var newSetting = { - 'name': 'passwordPolicy', - 'value': { - 'maxLength': 8 - } - }; - SettingsModel.create(newSetting, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('passwordPolicy'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,'); - callback(); - } - }); - }, - function (callback) { - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,bangalore,' - }; - var newSetting = { - 'name': 'passwordPolicy', - 'value': { - 'maxLength': 12 - } - }; - SettingsModel.create(newSetting, callContext, function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('passwordPolicy'); - expect(res._hierarchyScope.regionHierarchy).to.be.equal(',root,asia,india,bangalore,'); - callback(); - } - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India with upward true on model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,' - }; - - SettingsModel.find({}, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(1); - expect(res[0].value.maxLength).to.be.equal(8); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Bangalore with upward true on model without depth', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,bangalore,' - }; - - SettingsModel.find({}, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(1); - expect(res[0].value.maxLength).to.be.equal(12); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Bangalore with upward true on model with depth 1', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,bangalore,' - }; - - SettingsModel.find({ 'depth': '1' }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(1); - expect(res[0].value.maxLength).to.be.equal(12); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Delhi with upward true on model(Test for fallback)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,delhi,' - }; - - SettingsModel.find({}, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(0); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Delhi with upward true on model with depth(Test for fallback)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'regionHierarchy': ',root,asia,india,delhi,' - }; - - SettingsModel.find({ 'depth': 1 }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res).to.be.instanceof(Array); - expect(res).to.have.length(1); - expect(res[0].value.maxLength).to.be.equal(8); - done(); - } - }); - }); - - it('Should throw an error if type of hierarchy key in model definition is not of type string (Create)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'employeeHierarchy': ',principal,headMaster,teacher,' - }; - - var employeeModel = { - "name": "EmployeeModel", - "properties": { - "empName": "string" - }, - 'base': 'PersistedModel', - "mixins": { - 'ObserverMixin': true, - 'ModelValidations': true, - 'HistoryMixin': true, - 'DataPersonalizationMixin': true, - 'DataHierarchyMixin': true, - 'SoftDeleteMixin': false - }, - 'hierarchyScope': [{ employeeHierarchy: "test" }] - } - - var newmodel = loopback.createModel(employeeModel); - newmodel.clientModelName = "EmployeeModel"; - newmodel.clientPlural = "EmployeeModels"; - app.model(newmodel, { - dataSource: 'db' - }); - var myModel = loopback.findModel('EmployeeModel'); - - myModel.create({ empName: 'Mikasa' }, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - expect(err).not.to.be.empty; - expect(err).not.to.be.undefined; - expect(err.code).to.be.equal('DATA_HIERARCHY_ERROR_001'); - expect(err.name).to.be.equal('Hierarchy Scope Definition Error'); - expect(err.type).to.be.equal('Type mismatch in Declaration'); - done(); - } else { - done('Error: Should throw an error "Type mismatch in Declaration"'); - } - }); - - }); - - it('Should throw an error if type of hierarchy key in model definition is not of type string (find)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'employeeHierarchy': ',principal,headMaster,teacher,' - }; - var myModel = loopback.findModel('EmployeeModel'); - - myModel.find({ where: { empName: 'Mikasa' } }, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - expect(err).not.to.be.empty; - expect(err).not.to.be.undefined; - expect(err.code).to.be.equal('DATA_HIERARCHY_ERROR_001'); - expect(err.name).to.be.equal('Hierarchy Scope Definition Error'); - expect(err.type).to.be.equal('Type mismatch in Declaration'); - done(); - } else { - done('Error: Should throw an error "Type mismatch in Declaration"'); - } - }); - - }); - - it('Should throw an error if parent not found for given parentId (create)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'staffmodelHierarchy': ',principal,headMaster,teacher,', - 'studentinchargeHierarchy': ',root,' - }; - - var staffModel = { - "name": "StaffModel", - "properties": { - "name": "string" - }, - "mixins": { - 'HistoryMixin': true, - 'DataHierarchyMixin': true, - 'SoftDeleteMixin': false - }, - autoscope: [ - 'tenantId' - ], - 'hierarchyScope': ['staffmodelHierarchy', 'studentinchargeHierarchy'] - } - - models.ModelDefinition.create(staffModel, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create dummyTestModel model'); - done(err); - } else { - var myModel = loopback.getModel('StaffModel', bootstrap.defaultContext); - myModel.create({ name: 'Asuna', parentId: 'school' }, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - expect(err).not.to.be.empty; - expect(err).not.to.be.undefined; - expect(err.code).to.be.equal('DATA_HIERARCHY_ERROR_003'); - expect(err.name).to.be.equal('Parent Not Found'); - expect(err.type).to.be.equal('ParentNotFound'); - done(); - } else { - done('Error: Should throw an error "Parent Not Found"'); - } - }); - } - }); - }); - - it('Should throw an error if hierarchy data not provided for defined hierarchy (create)', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'staffmodelHierarchy': ',principal,headMaster,teacher,' - }; - - var myModel = loopback.getModel('StaffModel', bootstrap.defaultContext); - myModel.create({ name: 'Kirito' }, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - expect(err).not.to.be.empty; - expect(err).not.to.be.undefined; - expect(err.code).to.be.equal('DATA_HIERARCHY_ERROR_002'); - expect(err.name).to.be.equal('Hierarchy Personalization error'); - expect(err.type).to.be.equal('Insufficient data'); - done(); - } else { - done('Error: Should throw an error "Insufficient data"'); - } - }); - }); - -}); -// END of Describe - - -describe(chalk.blue('Data Hierarchy Test --REST'), function () { - var RegionModel; - var ProductModel; - var SettingsModel; - - var regionModel = 'Region'; - var productModel = 'Product'; - var settingsModel = 'SystemSettings'; - this.timeout(50000); - var testUserAccessToken; - var asiaUserAccessToken; - var indiaUserAccessToken; - var delhiUserAccessToken; - var bangaloreUserAccessToken; - - // Creating testuser access token since removed jwt-assertion middleware - // so that we can access the models which are created using bootstrap.defaultContext - // are based on testuesr and test-tenant. - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - before('Create Test models and users', function (done) { - var atModel = loopback.getModelByType('AccessToken'); - var user = loopback.getModelByType('BaseUser'); - - user.defineProperty('region', { - type: 'string' - }); - - atModel.defineProperty('regionHierarchy', { - type: 'string' - }); - - - atModel.observe('before save', function (ctx, next) { - var data = ctx.data || ctx.instance; - var userid = data.userId; - user.find({ 'where': { 'id': userid } }, bootstrap.defaultContext, function (err, instance) { - if (err) { - next(err); - } else if (instance.length) { - // console.log("========================= instance", instance); - RegionModel.findOne({ where: { regionName: instance[0].region } }, bootstrap.defaultContext, function (err, res) { - if (err) { - next(err); - } else if (res) { - // console.log("========================== res", res); - data.__data.regionHierarchy = res._hierarchyScope.regionHierarchy; - // console.log("*********************", ctx.instance); - next(); - } else { - next(); - } - }); - } else { - next(); - } - }); - }); - - - async.series([ - function (callback) { - // var aSession = loopback.getModelByType('AuthSession'); - // aSession.defineProperty('_hierarchyScope', { - // type: 'string' - // }); - user.dataSource.autoupdate(['BaseUser', 'AuthSession'], function fnDSAutoUpdate(err) { - if (err) callback(err); - callback(); - }); - }, - function (callback) { - var userDetails = { - 'username': 'AsiaUser', - 'password': 'AsiaUser@1', - 'email': 'AsiaUser@mycompany.com', - 'region': 'Asia' - }; - user.create(userDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - callback(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - callback(); - } - }); - }, function (callback) { - var userDetails = { - 'username': 'Indiauser', - 'password': 'IndiaUser@1', - 'email': 'IndiaUser@mycompany.com', - 'region': 'India' - }; - user.create(userDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - callback(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - callback(); - } - }); - }, function (callback) { - var userDetails = { - 'username': 'DelhiUser', - 'password': 'DelhiUser@1', - 'email': 'DelhiUser@mycompany.com', - 'region': 'Delhi' - }; - user.create(userDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - callback(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - callback(); - } - }); - }, function (callback) { - var userDetails = { - 'username': 'BangaloreUser', - 'password': 'BangaloreUser@1', - 'email': 'BangaloreUser@mycompany.com', - 'region': 'Bangalore' - }; - user.create(userDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - callback(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - callback(); - } - }); - }, function (callback) { - var userDetails = { - 'username': 'JapanUser', - 'password': 'JapanUser@1', - 'email': 'JapanUser@mycompany.com', - 'region': 'Japan' - }; - user.create(userDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - callback(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - callback(); - } - }); - }], - function (err) { - if (err) { - done(err); - } else { - RegionModel = loopback.getModel(regionModel, bootstrap.defaultContext); - ProductModel = loopback.getModel(productModel, bootstrap.defaultContext); - SettingsModel = loopback.getModel(settingsModel, bootstrap.defaultContext); - done(); - } - }); - }); - - after('Remove Data from Test Models', function (done) { - var atModel = loopback.getModelByType('AccessToken'); - RegionModel.destroyAll({}, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } - ProductModel.destroyAll({}, bootstrap.defaultContext, function (err, result) { - atModel.removeObserver('before save'); - if (err) { - done(err); - } - done(); - }); - }); - }); - - it('Create region Hierarchy in region model', function (done) { - this.timeout(60000); - // Passing access_token query param - var url = bootstrap.basePath + '/' + regionModel + '?access_token='+testUserAccessToken; - async.series([ - function (callback) { - var testData = { - 'regionName': 'Continents', - 'id': 'root' - }; - apiV2 - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - // console.log("------------", result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('root'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Asia', - 'id': 'asia' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('asia'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'India', - 'id': 'india', - 'parentId': 'asia' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('india'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Delhi', - 'id': 'delhi', - 'parentId': 'india' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('delhi'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Bangalore', - 'id': 'bangalore', - 'parentId': 'india' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('bangalore'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Japan', - 'id': 'japan', - 'parentId': 'asia' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('japan'); - callback(); - } - }); - }, - function (callback) { - var testData = { - 'regionName': 'Tokyo', - 'id': 'tokyo', - 'parentId': 'japan' - }; - apiV2.post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - callback(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.id).to.be.equal('tokyo'); - callback(); - } - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Create products hierarchy based on region', function (done) { - this.timeout(6000000); - var url = bootstrap.basePath + '/' + productModel; - async.series([ - function (callback) { - var userDetails = { - 'password': 'AsiaUser@1', - 'email': 'AsiaUser@mycompany.com' - }; - var newProduct = { - 'productName': 'Coca-Cola' - }; - bootstrap.login(userDetails, function (token) { - asiaUserAccessToken = token; - var newUrl = url + '?access_token=' + asiaUserAccessToken; - api - .post(newUrl) - .send(newProduct) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.productName).to.be.equal('Coca-Cola'); - callback(); - } - }); - }); - }, - function (callback) { - var userDetails = { - 'password': 'IndiaUser@1', - 'email': 'IndiaUser@mycompany.com' - }; - var newProduct = { - 'productName': 'Diet coke' - }; - bootstrap.login(userDetails, function (token) { - indiaUserAccessToken = token; - var newUrl = url + '?access_token=' + indiaUserAccessToken; - - api - .post(newUrl) - .send(newProduct) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.productName).to.be.equal('Diet coke'); - callback(); - } - }); - }); - }, - function (callback) { - var userDetails = { - 'password': 'DelhiUser@1', - 'email': 'DelhiUser@mycompany.com' - }; - var newProduct = { - 'productName': 'Coke Zero' - }; - bootstrap.login(userDetails, function (token) { - delhiUserAccessToken = token; - var newUrl = url + '?access_token=' + delhiUserAccessToken; - api - .post(newUrl) - .send(newProduct) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.productName).to.be.equal('Coke Zero'); - callback(); - } - }); - }); - }, - function (callback) { - var newProduct = { - 'productName': 'Pulpy Orange' - }; - var newUrl = url + '?access_token=' + indiaUserAccessToken; - api - .post(newUrl) - .send(newProduct) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.productName).to.be.equal('Pulpy Orange'); - callback(); - } - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Get products based on regional context Asia/India', function (done) { - var url = bootstrap.basePath + '/' + productModel + '?access_token=' + indiaUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(2); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India/Delhi', function (done) { - var url = bootstrap.basePath + '/' + productModel + '?access_token=' + delhiUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(1); - expect(res.body[0].productName).to.be.equal('Coke Zero'); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth *', function (done) { - var filter = 'filter={"depth":"*"}'; - var url = bootstrap.basePath + '/' + productModel + '?access_token=' + indiaUserAccessToken + '&' + filter; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(3); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth 1', function (done) { - var filter = 'filter={"depth":"1"}'; - var url = bootstrap.basePath + '/' + productModel + '?access_token=' + indiaUserAccessToken + '&' + filter; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(3); - done(); - } - }); - }); - - it('Get products based on regional context Asia/India with depth 3(Actual level of hierarchy ends at 1)', function (done) { - var filter = 'filter={"depth":"3"}'; - var url = bootstrap.basePath + '/' + productModel + '?access_token=' + indiaUserAccessToken + '&' + filter; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(3); - done(); - } - }); - }); - - it('Create SystemSetings based on regionHierarchy', function (done) { - this.timeout(6000); - var url = bootstrap.basePath + '/' + settingsModel; - - async.series([ - function (callback) { - var newSetting = { - 'name': 'passwordPolicy', - 'value': { - 'maxLength': 8 - } - }; - var newUrl = url + '?access_token=' + indiaUserAccessToken; - api - .post(newUrl) - .send(newSetting) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.name).to.be.equal('passwordPolicy'); - callback(); - } - }); - }, - function (callback) { - var newSetting = { - 'name': 'passwordPolicy', - 'value': { - 'maxLength': 12 - } - }; - var userDetails = { - 'password': 'BangaloreUser@1', - 'email': 'BangaloreUser@mycompany.com' - }; - bootstrap.login(userDetails, function (token) { - bangaloreUserAccessToken = token; - var newUrl = url + '?access_token=' + bangaloreUserAccessToken; - api - .post(newUrl) - .send(newSetting) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - callback(err); - } else { - // console.log("-------------", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.name).to.be.equal('passwordPolicy'); - callback(); - } - }); - }); - } - ], function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India with upward true on model', function (done) { - var url = bootstrap.basePath + '/' + settingsModel + '?access_token=' + indiaUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(1); - expect(res.body[0].value.maxLength).to.be.equal(8); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Bangalore with upward true on model without depth', function (done) { - var url = bootstrap.basePath + '/' + settingsModel + '?access_token=' + bangaloreUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(1); - expect(res.body[0].value.maxLength).to.be.equal(12); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Bangalore with upward true on model with depth 1', function (done) { - var filter = 'filter={"depth":1}'; - var url = bootstrap.basePath + '/' + settingsModel + '?access_token=' + bangaloreUserAccessToken + '&' + filter; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(1); - expect(res.body[0].value.maxLength).to.be.equal(12); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Delhi with upward true on model(Test for fallback)', function (done) { - var url = bootstrap.basePath + '/' + settingsModel + '?access_token=' + delhiUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(0); - done(); - } - }); - }); - - it('Get settings based on regional context Asia/India/Delhi with upward true on model with depth(Test for fallback)', function (done) { - var filter = 'filter={"depth":1}'; - var url = bootstrap.basePath + '/' + settingsModel + '?access_token=' + delhiUserAccessToken + '&' + filter; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body).to.be.instanceof(Array); - expect(res.body).to.have.length(1); - expect(res.body[0].value.maxLength).to.be.equal(8); - done(); - } - }); - }); - - it('Should throw an error if type of hierarchy key in model definition is not of type string (Create)', function (done) { - var data = { empName: 'Erin' }; - var url = bootstrap.basePath + '/EmployeeModels'; - api - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('employeeHierarchy', ',principal,headMaster,teacher,') - .end(function (err, res) { - if (err) { - done('Error: Should throw an error "Type mismatch in Declaration"'); - } else { - expect(res.body.error).not.to.be.null; - expect(res.body.error).not.to.be.empty; - expect(res.body.error).not.to.be.undefined; - expect(res.body.error.code).to.be.equal('DATA_HIERARCHY_ERROR_001'); - expect(res.body.error.name).to.be.equal('Hierarchy Scope Definition Error'); - expect(res.body.error.type).to.be.equal('Type mismatch in Declaration'); - done(); - } - }); - - }); - - it('Should throw an error if type of hierarchy key in model definition is not of type string (find)', function (done) { - var url = bootstrap.basePath + '/EmployeeModels?filter={ "where": { "empName": "Erin" } }'; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('employeeHierarchy', ',principal,headMaster,teacher,') - .end(function (err, res) { - if (err) { - done('Error: Should throw an error "Type mismatch in Declaration"'); - } else { - expect(res.body.error).not.to.be.null; - expect(res.body.error).not.to.be.empty; - expect(res.body.error).not.to.be.undefined; - expect(res.body.error.code).to.be.equal('DATA_HIERARCHY_ERROR_001'); - expect(res.body.error.name).to.be.equal('Hierarchy Scope Definition Error'); - expect(res.body.error.type).to.be.equal('Type mismatch in Declaration'); - done(); - } - }); - - }); - - it('Should throw an error if parent not found for given parentId (Create)', function (done) { - // Passing access_token query param - var url = bootstrap.basePath + '/StaffModels?access_token=' + testUserAccessToken; - var data = { name: 'Naruto', parentId: 'konaha' }; - apiV2 - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('staffmodel-Hierarchy', ',principal,headMaster,teacher,') - .set('studentincharge-Hierarchy', ',root,') - .end(function (err, res) { - if (err) { - done('Error: Should throw an error "Parent Not Found"'); - } else { - expect(res.body.error).not.to.be.null; - expect(res.body.error).not.to.be.empty; - expect(res.body.error).not.to.be.undefined; - expect(res.body.error.code).to.be.equal('DATA_HIERARCHY_ERROR_003'); - expect(res.body.error.name).to.be.equal('Parent Not Found'); - expect(res.body.error.type).to.be.equal('ParentNotFound'); - done(); - } - }); - }); - - it('Should throw an error if hierarchy data not provided for defined hierarchy (Create)', function (done) { - var data = { name: 'Hinata' }; - // Passing access_token query param - var url = bootstrap.basePath + '/StaffModels?access_token=' + testUserAccessToken; - apiV2 - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('staffmodel-Hierarchy', ',principal,headMaster,teacher,') - .end(function (err, res) { - if (err) { - done('Error: Should throw an error "Insufficient data"'); - } else { - expect(res.body.error).not.to.be.null; - expect(res.body.error).not.to.be.empty; - expect(res.body.error).not.to.be.undefined; - expect(res.body.error.code).to.be.equal('DATA_HIERARCHY_ERROR_002'); - expect(res.body.error.name).to.be.equal('Hierarchy Personalization error'); - expect(res.body.error.type).to.be.equal('Insufficient data'); - done(); - } - }); - }); - -}); -// END of Describe diff --git a/test/data-personalization-test.js b/test/data-personalization-test.js deleted file mode 100644 index 4d5fac5..0000000 --- a/test/data-personalization-test.js +++ /dev/null @@ -1,3468 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var api = bootstrap.api; -var models = bootstrap.models; -var logger = require('oe-logger'); -var log = logger('data-personalization-test'); -var loopback = require('loopback'); -var async = require('async'); -var app = bootstrap.app; - -describe(chalk.blue('Data Personalization Test --REST'), function DataPersonalizationRest() { - this.timeout(1000000); - var fiveKageModel; - var tailedBeastModel; - var animeCharacterModel; - var personalizedModelScope; - var PersonalizedModelWithScopeAsModel; - var testUserAccessToken; - - // Testmodel has no autoscoped variable(not auto-scoped) - var fiveKage = 'FiveKage'; - var fiveKageDetails = { - name: fiveKage, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: false, - plural: fiveKage, - mixins: { - 'HistoryMixin': true - } - }; - - // Testmodel one has one autoscoped variable(tenantId) - var tailedBeast = 'TailedBeast'; - var tailedBeastsDetails = { - name: tailedBeast, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: true, - plural: tailedBeast, - mixins: { - 'HistoryMixin': true - }, - autoscope: [ - 'tenantId' - ] - }; - - // Testmodel two has two autoscoped variable(tenantId,username) - var animeCharacter = 'AnimeCharacter'; - var animeCharacterDetails = { - name: animeCharacter, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: false, - plural: animeCharacter, - mixins: { - 'HistoryMixin': true - }, - autoscope: [ - 'tenantId', 'username' - ] - }; - - var myScopeModel = 'CustomScope'; - var modelDetailsScope = { - name: myScopeModel, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'device': { - 'type': 'string' - }, - 'location': { - 'type': 'string' - }, - 'lang': { - 'type': 'string' - } - }, - strict: false, - idInjection: false, - plural: myScopeModel, - mixins: { - 'HistoryMixin': true, - 'SoftDeleteMixin': false, - 'FailsafeObserverMixin': false - } - }; - - var myScopeModel1 = 'ModelWithScopeAsModel'; - var modelDetailsScopeModel = { - name: myScopeModel1, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string' - }, - 'scope': { - 'type': myScopeModel - } - }, - strict: false, - idInjection: false, - plural: myScopeModel1, - mixins: { - 'HistoryMixin': true - }, - autoscope: [ - 'tenantId' - ] - }; - - var characterData = [ - { - 'name': 'Naruto' - }, - { - 'name': 'Hinata', - 'scope': { - 'device': 'ios' - } - }, - { - 'name': 'Sasuke', - 'scope': { - 'device': 'android' - } - }, - { - 'name': 'Sakura', - 'scope': { - 'device': 'windows' - } - }, - { - 'name': 'RockLee', - 'scope': { - 'device': 'ios', - 'location': 'us' - } - }, - { - 'name': 'Gaara', - 'scope': { - 'device': 'ios', - 'location': 'uk' - } - }, - { - 'name': 'Shikamaru', - 'scope': { - 'device': 'ios', - 'location': 'in' - } - }, - { - 'name': 'Choji', - 'scope': { - 'device': 'android', - 'location': 'us' - } - }, - { - 'name': 'Shino', - 'scope': { - 'device': 'android', - 'location': 'uk' - } - }, - { - 'name': 'TenTen', - 'scope': { - 'device': 'android', - 'location': 'in' - } - }, - { - 'name': 'Minato', - 'scope': { - 'device': 'ios', - 'lang': 'en-us' - } - }, - { - 'name': 'Nagato', - 'scope': { - 'device': 'ios', - 'lang': 'en-uk' - } - }, - { - 'name': 'Itachi', - 'scope': { - 'device': 'ios', - 'lang': 'en-in' - } - }, - { - 'name': 'Madara', - 'scope': { - 'device': 'android', - 'lang': 'en-us' - } - }, - { - 'name': 'Neji', - 'scope': { - 'device': 'android', - 'lang': 'en-uk' - } - }, - { - 'name': 'Might Guy', - 'scope': { - 'device': 'android', - 'lang': 'en-in' - } - }, - { - 'name': 'Jiraya', - 'scope': { - 'location': 'us', - 'lang': 'en-us' - } - }, - { - 'name': 'Sai', - 'scope': { - 'location': 'us', - 'lang': 'en-uk' - } - }, - { - 'name': 'Tsunade', - 'scope': { - 'location': 'us', - 'lang': 'en-in' - } - }, - { - 'name': 'Kakashi', - 'scope': { - 'location': 'uk', - 'lang': 'en-us' - } - }, - { - 'name': 'Karin', - 'scope': { - 'location': 'uk', - 'lang': 'en-uk' - } - }, - { - 'name': 'Ino', - 'scope': { - 'location': 'uk', - 'lang': 'en-in' - } - }, - { - 'name': 'Sasori', - 'scope': { - 'location': 'in', - 'lang': 'en-us' - } - }, - { - 'name': 'Orochimaru', - 'scope': { - 'location': 'in', - 'lang': 'en-uk' - } - }, - { - 'name': 'Obito', - 'scope': { - 'location': 'in', - 'lang': 'en-in' - } - }, - { - 'name': 'Hashirama', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'ios' - } - }, - { - 'name': 'Kiba', - 'scope': { - 'location': 'in', - 'lang': 'en-uk', - 'device': 'android' - } - }, - { - 'name': 'Killer Bee', - 'scope': { - 'location': 'in', - 'lang': 'en-in', - 'device': 'windows' - } - }, - { - 'name': 'Temari', - 'scope': { - 'location': 'us', - 'lang': 'en-in', - 'device': 'windows' - } - }, - { - 'name': 'Asuma', - 'scope': { - 'location': 'uk', - 'lang': 'en-in', - 'device': 'windows' - } - } - ]; - - var kageData = [ - { - 'name': 'HoKage' - }, - { - 'name': 'RaiKage', - 'scope': { - 'location': 'us' - } - } - ]; - - // Creating testuser access token since removed jwt-assertion middleware - // so that we can access the models which are created using bootstrap.defaultContext - // are based on testuesr and test-tenant. - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - before('Create Test model', function restBeforeAll(done) { - async.parallel([ - function asyncModel(callback) { - models.ModelDefinition.create(fiveKageDetails, bootstrap.defaultContext, function modelCreate(err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create DataPersonalizationModel model'); - callback(err); - } else { - callback(); - } - }); - }, - function asyncModelOne(callback) { - models.ModelDefinition.create(tailedBeastsDetails, bootstrap.defaultContext, function modelOneCreate(err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create DataPersonalizationModel1 model'); - callback(err); - } else { - callback(); - } - }); - }, - function asyncModelTwo(callback) { - models.ModelDefinition.create(animeCharacterDetails, bootstrap.defaultContext, function modelTwoCreate(err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create DataPersonalizationModel2 model'); - callback(err); - } else { - callback(); - } - }); - }, - function asyncDetailScopeModel(callback) { - models.ModelDefinition.create(modelDetailsScope, bootstrap.defaultContext, function detailScopeModelCreate(err, res) { - if (err) { - callback(err); - } else { - callback(); - } - }); - }, - function asyncScopeModel(callback) { - models.ModelDefinition.create(modelDetailsScopeModel, bootstrap.defaultContext, function scopeModelCreate(err, res) { - if (err) { - callback(err); - } else { - callback(); - } - }); - } - ], - function asyncFinalCb(err) { - if (err) { - done(err); - } else { - fiveKageModel = loopback.getModel(fiveKage, bootstrap.defaultContext); - tailedBeastModel = loopback.getModel(tailedBeast, bootstrap.defaultContext); - animeCharacterModel = loopback.getModel(animeCharacter, bootstrap.defaultContext); - personalizedModelScope = loopback.getModel(myScopeModel, bootstrap.defaultContext); - PersonalizedModelWithScopeAsModel = loopback.getModel(myScopeModel1, bootstrap.defaultContext); - done(); - } - }); - }); - - after('Remove Test Model', function restAfterAll(done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': ['ios', 'windows', 'android'], - 'location': ['us', 'in', 'uk'], - 'lang': ['en-us', 'en-in'], - 'roles': ['admin', 'designer'] - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '0', - 'location': '0', - 'lang': '0', - 'roles': '0' - }; - - fiveKageModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - tailedBeastModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - animeCharacterModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - done(); - }); - }); - }); - }); - - it('- Should insert data into TestModel with and without any manual scope into non-autoscoped test model[Group of records]', function (done) { - var url = bootstrap.basePath + '/' + fiveKage + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(kageData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - done(); - } - }); - }); - - it('- Should insert data into TestModel with and without any manual scope [Group of records]', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(characterData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - done(); - } - }); - }); - - it('- Should insert data into TestModel without any manual or auto scope in data', function (done) { - var postData = { - 'name': 'Kurama' - }; - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body.name).to.be.equal('Kurama'); - done(); - } - }); - }); - - it('- Should insert data into TestModel with manual scope', function (done) { - var postData = { - 'name': 'Ten-Tails', - 'scope': { - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - } - }; - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body.name).to.be.equal('Ten-Tails'); - done(); - } - }); - }); - - it('- Should not insert data into TestModel with auto scope defined on model and not passed as part of header or query string', function (done) { - var postData = { - 'name': 'Shukaku', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'android' - } - }; - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(500); - done(); - }); - - it('- Should not insert data into TestModel with auto scope', function (done) { - var postData = { - 'name': 'Matatabi', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'android', - 'tenantId': 'test-tenant' - } - }; - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(500); - done(); - }); - - it('- Should retrieve data from TestModel without any manual scope contributors', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - done(); - } - }); - }); - - it('- Should retrieve data from TestModel with context contributors', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('device', 'ios') - .set('location', 'in') - .set('lang', 'en-in') - .set('x-ctx-weight-device', '30') - .set('x-ctx-weight-location', '20') - .set('x-ctx-weight-lang', '50') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - done(); - } - }); - }); - - it('- Should retrieve data from TestModel without any autoscoped values defined on Model', function (done) { - var url = bootstrap.basePath + '/' + fiveKage + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('device', 'ios') - .set('location', 'in') - .set('lang', 'en-in') - .set('x-ctx-weight-device', '30') - .set('x-ctx-weight-location', '20') - .set('x-ctx-weight-lang', '50') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - done(); - } - }); - }); - - it('- Should not retrieve any data from TestModel with autoscoped values defined on Model but not provided by user', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('device', 'ios') - .set('location', 'in') - .set('lang', 'en-in') - .set('x-ctx-weight-device', '30') - .set('x-ctx-weight-location', '20') - .set('x-ctx-weight-lang', '50') - .expect(500); - done(); - }); - - it('- Should retrieve data from TestModel in Descending order based on score calculated from context', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('device', 'windows') - .set('location', 'IN') - .set('lang', 'en-IN') - .set('x-ctx-weight-device', '30') - .set('x-ctx-weight-location', '20') - .set('x-ctx-weight-lang', '50') - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - expect(result.body[0].name).to.be.equal('Killer Bee'); - done(); - } - }); - }); - - it('- Should retrieve data from TestModel in Descending order based on score calculated from context', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('device', 'ios') - .set('location', 'us') - .set('lang', 'en-us') - .set('x-ctx-weight-device', '50') - .set('x-ctx-weight-location', '20') - .set('x-ctx-weight-lang', '30') - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - expect(result.body[0].name).to.be.equal('Hashirama'); - done(); - } - }); - }); - - it('- Should retrieve data from TestModel without any scope when defaults is set', function (done) { - var url = bootstrap.basePath + '/' + animeCharacter + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('defaults', true) - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.empty; - expect(result.body[0].name).to.be.equal('Naruto'); - done(); - } - }); - }); - - it('- Should be able to post data with scope containing array of values', function (done) { - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - var postData = { - 'name': 'Son Goku', - 'scope': { - 'roles': ['admin', 'designer'] - } - }; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Son Goku'); - done(); - } - }); - }); - - it('- Should be able retrieve all record with scope values in ignoreList', function (done) { - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('x-ignore-context', '["device"]') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.have.length(1); - done(); - } - }); - }); - - it('- Should be able to write custom query on scope which will take higher precidence on manual scope query', function (done) { - var url = bootstrap.basePath + '/' + tailedBeast + '?access_token=' + testUserAccessToken + '&filter={"where":{"scope.device": "android"}}'; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('device', 'windows') - .set('lang', 'en-us') - .set('location', 'in') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.have.length(1); - expect(result.body[0].name).to.be.equal('Ten-Tails'); - done(); - } - }); - }); - - it('- Should be able to insert data into TestModel with manual scope being a another Model', function (done) { - var postData = { - 'name': 'modelWithScopeAsModel', - 'scope': { - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - } - }; - var url = bootstrap.basePath + '/' + myScopeModel1 + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log("=================",result.body); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.body.name).to.be.equal('modelWithScopeAsModel'); - done(); - } - }); - }); - - it('- Should be able to retrieve data from TestModel with manual scope being a another Model', function (done) { - var url = bootstrap.basePath + '/' + myScopeModel1 + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('location', 'in') - .set('lang', 'en-us') - .set('device', 'android') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log("================", result.body); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.body[0].name).to.be.equal('modelWithScopeAsModel'); - done(); - } - }); - }); - - it('- Should be able to update data in TestModel with manual scope being a another Model', function (done) { - var url = bootstrap.basePath + '/' + myScopeModel1 + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('location', 'in') - .set('lang', 'en-us') - .set('device', 'android') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - var postData = res.body[0]; - postData.name = 'modelWithScopeAsModelUpdate'; - api - .put(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('location', 'in') - .set('lang', 'en-us') - .set('device', 'android') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log("--------------", result.body); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.body.name).to.be.equal('modelWithScopeAsModelUpdate'); - done(); - } - }); - } - }); - }); - - it('- Should be able to delete data in TestModel with manual scope being a another Model', function (done) { - var url = bootstrap.basePath + '/' + myScopeModel1; - var postUrl = url + '?access_token=' + testUserAccessToken; - api - .get(postUrl) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('location', 'in') - .set('lang', 'en-us') - .set('device', 'android') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log("==============", res.body); - url = url + '/' + res.body[0].id + '?access_token=' + testUserAccessToken; - api - .delete(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('location', 'in') - .set('lang', 'en-us') - .set('device', 'android') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log("--------------", result.body); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.body.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - - it('- Test for insertion of data in model connected to memory DB', function (done) { - var memDbModel = { - 'name': 'NewMemDBModel', - 'base': 'PersistedModel', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'ObserverMixin': true, - 'ModelValidations': true, - 'HistoryMixin': true, - 'DataPersonalizationMixin': true - }, - 'autoscope': [ - 'org' - ] - }; - - var data = { - 'name': 'scopedRecord', - 'scope': { - 'device': 'mobile', - 'location': 'in' - } - }; - - var newmodel = loopback.createModel(memDbModel); - app.model(newmodel, { - dataSource: 'nullsrc' - }); - - var url = bootstrap.basePath + '/NewMemDBModels' + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('org', 'ev') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model connected to memory DB', function (done) { - var url = bootstrap.basePath + '/NewMemDBModels' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('org', 'ev') - .set('location', 'in') - .set('device', 'mobile') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - expect(result.body).to.have.length(1); - expect(result.body[0].name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for insertion of data in model(no autoscope) connected to memory DB', function (done) { - var memDbModel = { - 'name': 'NewMemDBModelNoAutoScope', - 'base': 'PersistedModel', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'ObserverMixin': true, - 'ModelValidations': true, - 'HistoryMixin': true, - 'DataPersonalizationMixin': true - } - }; - - var data = { - 'name': 'scopedRecord', - 'scope': { - 'device': 'mobile', - 'location': 'in' - } - }; - var newmodel = loopback.createModel(memDbModel); - app.model(newmodel, { - dataSource: 'nullsrc' - }); - - var url = bootstrap.basePath + '/NewMemDBModelNoAutoScopes'+ '?access_token=' + testUserAccessToken; - api - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model(no autoscope) connected to memory DB', function (done) { - var url = bootstrap.basePath + '/NewMemDBModelNoAutoScopes' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('location', 'in') - .set('device', 'mobile') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - expect(result.body).to.have.length(1); - expect(result.body[0].name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model connected to memory DB with wrong contributor values', function (done) { - var url = bootstrap.basePath + '/NewMemDBModels' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('org', 'ev') - .set('location', 'in') - .set('device', 'tab') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - done(); - } - }); - }); - - it('- Test for mixin applied property on model while posting data', function (done) { - var modelWithOutMixin = { - 'name': 'NewModelWithOutMixin', - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'HistoryMixin': true, - 'DataPersonalizationMixin': false - }, - 'autoscope': [ - 'tenantId' - ] - }; - - var data = { - 'name': 'scopedRecord', - 'scope': { - 'device': 'mobile', - 'location': 'in' - } - }; - - models.ModelDefinition.create(modelWithOutMixin, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create model'); - done(err); - } else { - var url = bootstrap.basePath + '/NewModelWithOutMixins' + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('scopedRecord'); - done(); - } - }); - } - }); - }); - - it('- Test for mixin applied property on model while getting data', function (done) { - var url = bootstrap.basePath + '/NewModelWithOutMixins' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - expect(result.body).to.have.length(1); - expect(result.body[0].name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Should be able to post a record with unique validation on property with scope', function (done) { - var modelUnique = 'NewModelUnique'; - var modelDetailsUnique = { - name: modelUnique, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'a': { - 'type': 'string', - 'unique': true - }, - 'b': { - 'type': 'string', - 'unique': true - } - }, - strict: false, - idInjection: false, - mixins: { - 'HistoryMixin': true - }, - autoscope: [ - 'org' - ], - scoreScheme: 'max' - }; - - var postData = [ - { - 'a': '1', - 'b': '1', - 'scope': { - 'rule': 'x' - } - }, - { - 'a': '2', - 'b': '2', - 'scope': { - 'rule': 'x' - } - }, - { - 'a': '1', - 'b': '1', - 'scope': { - 'rule': 'y', - 'category': 'x' - } - }, - { - 'a': '2', - 'b': '1', - 'scope': { - 'rule': 'y', - 'category': 'y' - } - }, - { - 'a': '1', - 'b': '2', - 'scope': { - 'rule': 'y', - 'category': 'y' - } - } - ]; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'unique-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - models.ModelDefinition.create(modelDetailsUnique, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - var url = bootstrap.basePath + '/NewModelUniques'+ '?access_token=' + testUserAccessToken; - api - .post(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('org', 'fin') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - expect(result.body).to.have.length(5); - done(); - } - }); - } - }); - }); - - it('- Should be able to get unique records with unique validation on property with scope', function (done) { - var url = bootstrap.basePath + '/NewModelUniques' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('org', 'fin') - .set('rule', 'y') - .set('category', 'y') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.be.instanceof(Array); - expect(result.body).to.have.length(2); - done(); - } - }); - }); - -}); -//End Of Describe - - -describe(chalk.blue('Data Personalization Test --Programatic'), function () { - this.timeout(200000); - - var fiveKageModel; - var tailedBeastModel; - var animeCharacterModel; - var personalizedModelScope; - var PersonalizedModelWithScopeAsModel; - - // Testmodel has no autoscoped variable(not auto-scoped) - var fiveKage = 'FiveKage'; - // Testmodel one has one autoscoped variable(tenantId) - var tailedBeast = 'TailedBeast'; - // Testmodel two has two autoscoped variable(tenantId,username) - var animeCharacter = 'AnimeCharacter'; - var myScopeModel = 'CustomScope'; - var myScopeModel1 = 'ModelWithScopeAsModel'; - - var characterData = [ - { - 'name': 'Naruto' - }, - { - 'name': 'Hinata', - 'scope': { - 'device': 'ios' - } - }, - { - 'name': 'Sasuke', - 'scope': { - 'device': 'android' - } - }, - { - 'name': 'Sakura', - 'scope': { - 'device': 'windows' - } - }, - { - 'name': 'RockLee', - 'scope': { - 'device': 'ios', - 'location': 'us' - } - }, - { - 'name': 'Gaara', - 'scope': { - 'device': 'ios', - 'location': 'uk' - } - }, - { - 'name': 'Shikamaru', - 'scope': { - 'device': 'ios', - 'location': 'in' - } - }, - { - 'name': 'Choji', - 'scope': { - 'device': 'android', - 'location': 'us' - } - }, - { - 'name': 'Shino', - 'scope': { - 'device': 'android', - 'location': 'uk' - } - }, - { - 'name': 'TenTen', - 'scope': { - 'device': 'android', - 'location': 'in' - } - }, - { - 'name': 'Minato', - 'scope': { - 'device': 'ios', - 'lang': 'en-us' - } - }, - { - 'name': 'Nagato', - 'scope': { - 'device': 'ios', - 'lang': 'en-uk' - } - }, - { - 'name': 'Itachi', - 'scope': { - 'device': 'ios', - 'lang': 'en-in' - } - }, - { - 'name': 'Madara', - 'scope': { - 'device': 'android', - 'lang': 'en-us' - } - }, - { - 'name': 'Neji', - 'scope': { - 'device': 'android', - 'lang': 'en-uk' - } - }, - { - 'name': 'Might Guy', - 'scope': { - 'device': 'android', - 'lang': 'en-in' - } - }, - { - 'name': 'Jiraya', - 'scope': { - 'location': 'us', - 'lang': 'en-us' - } - }, - { - 'name': 'Sai', - 'scope': { - 'location': 'us', - 'lang': 'en-uk' - } - }, - { - 'name': 'Tsunade', - 'scope': { - 'location': 'us', - 'lang': 'en-in' - } - }, - { - 'name': 'Kakashi', - 'scope': { - 'location': 'uk', - 'lang': 'en-us' - } - }, - { - 'name': 'Karin', - 'scope': { - 'location': 'uk', - 'lang': 'en-uk' - } - }, - { - 'name': 'Ino', - 'scope': { - 'location': 'uk', - 'lang': 'en-in' - } - }, - { - 'name': 'Sasori', - 'scope': { - 'location': 'in', - 'lang': 'en-us' - } - }, - { - 'name': 'Orochimaru', - 'scope': { - 'location': 'in', - 'lang': 'en-uk' - } - }, - { - 'name': 'Obito', - 'scope': { - 'location': 'in', - 'lang': 'en-in' - } - }, - { - 'name': 'Hashirama', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'ios' - } - }, - { - 'name': 'Kiba', - 'scope': { - 'location': 'in', - 'lang': 'en-uk', - 'device': 'android' - } - }, - { - 'name': 'Killer Bee', - 'scope': { - 'location': 'in', - 'lang': 'en-in', - 'device': 'windows' - } - }, - { - 'name': 'Temari', - 'scope': { - 'location': 'us', - 'lang': 'en-in', - 'device': 'windows' - } - }, - { - 'name': 'Asuma', - 'scope': { - 'location': 'uk', - 'lang': 'en-in', - 'device': 'windows' - } - } - ]; - - var kageData = [ - { - 'name': 'HoKage' - }, - { - 'name': 'RaiKage', - 'scope': { - 'location': 'us' - } - } - ]; - - before('Create Test Models', function (done) { - fiveKageModel = loopback.getModel(fiveKage, bootstrap.defaultContext); - tailedBeastModel = loopback.getModel(tailedBeast, bootstrap.defaultContext); - animeCharacterModel = loopback.getModel(animeCharacter, bootstrap.defaultContext); - personalizedModelScope = loopback.getModel(myScopeModel, bootstrap.defaultContext); - PersonalizedModelWithScopeAsModel = loopback.getModel(myScopeModel1, bootstrap.defaultContext); - done(); - }); - - after('Remove Test Model', function restAfterAll(done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': ['ios', 'windows', 'android'], - 'location': ['us', 'in', 'uk'], - 'lang': ['en-us', 'en-in'], - 'roles': ['admin', 'designer'] - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '0', - 'location': '0', - 'lang': '0', - 'roles': '0' - }; - - fiveKageModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - tailedBeastModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - animeCharacterModel.destroyAll({}, callContext, function modelDestroyAll(err, result) { - if (err) { - done(err); - } - done(); - }); - }); - }); - }); - - it('- Should insert data into TestModel with and without any manual scope [Group of records]', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - animeCharacterModel.create(characterData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - done(); - } - }); - }); - - it('- Should insert data into TestModel with and without any manual scope into non-autoscoped test model[Group of records]', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - fiveKageModel.create(kageData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - done(); - } - }); - }); - - it('- Should insert data into TestModel without any manual or auto scope in data', function (done) { - var postData = { - 'name': 'Saiken' - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result.name).to.be.equal('Saiken'); - // expect(result.body.scope).not.to.be.null; - // expect(result.body.scope).not.to.be.undefined; - // expect(result.body.scope.tenantId).to.be.equal('test-tenant'); - done(); - } - }); - }); - - it('- Should insert data into TestModel with manual scope', function (done) { - var postData = [ - { - 'name': 'Shukaku', - 'scope': { - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - } - }, - { - 'name': 'Kurama', - 'scope': { - 'device': 'ios' - } - }, - { - 'name': 'Gyuki', - 'scope': { - 'device': 'windows' - } - }]; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.be.instanceof(Array); - expect(result).to.have.length(3); - done(); - } - }); - }); - - it('- Should not insert data into TestModel with auto scope defined on model and not passed as part of header or query string', function (done) { - var postData = { - 'name': 'Ten-Tails', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'android' - } - }; - - var callContext = {}; - callContext.ctx = {}; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - done(); - } else { - done(new Error('Should not insert without autoscope value')); - } - }); - }); - - it('- Should not insert data into TestModel with auto scope', function (done) { - var postData = { - 'name': 'Matatabi', - 'scope': { - 'location': 'us', - 'lang': 'en-us', - 'device': 'android', - 'tenantId': 'test-tenant' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - expect(err).not.to.be.null; - done(); - } else { - done(new Error('Should not insert with autoscope values in scope')); - } - }); - }); - - it('- Should retrieve data from TestModel without any manual scope contributors', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - done(); - } - }); - }); - - it('- Should retrieve data from TestModel with context contributors', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': 'ios', - 'location': 'in', - 'lang': 'en-in' - - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '30', - 'location': '20', - 'lang': '50' - }; - - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - done(); - } - }); - }); - - it('- Should retrieve data from TestModel without any autoscoped values defined on Model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': 'ios', - 'location': 'in', - 'lang': 'en-in' - - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '30', - 'location': '20', - 'lang': '50' - }; - - fiveKageModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - done(); - } - }); - }); - - it('- Should not retrieve any data from TestModel with autoscoped values defined on Model but not provided by user', function (done) { - var callContext = {}; - callContext.ctx = { - - 'device': 'ios', - 'location': 'in', - 'lang': 'en-in' - - }; - - callContext.ctxWeights = { - - 'device': '30', - 'location': '20', - 'lang': '50' - }; - - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(); - } else { - done(new Error('insufficient data')); - } - }); - }); - - it('- Should retrieve data from TestModel in Descending order based on score calculated from context', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': 'windows', - 'location': 'in', - 'lang': 'en-in' - - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '30', - 'location': '20', - 'lang': '50' - }; - - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - expect(result[0].name).to.be.equal('Killer Bee'); - done(); - } - }); - }); - - it('- Should retrieve data from TestModel in Descending order based on score calculated from context', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': 'ios', - 'location': 'us', - 'lang': 'en-us' - - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '50', - 'location': '20', - 'lang': '30' - }; - - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - expect(result[0].name).to.be.equal('Hashirama'); - done(); - } - }); - }); - - it('- Should retrieve data from TestModel without any scope when defaults is set', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'defaults': true - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'defaults': '0' - }; - animeCharacterModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.empty; - expect(result[0].name).to.be.equal('Naruto'); - done(); - } - }); - }); - - it('- Should be able to post data with scope containing array of values', function (done) { - var postData = { - 'name': 'Saiken', - 'scope': { - 'roles': ['admin', 'designer'] - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result._scope).to.have.length(3); - expect(result._scope).to.have.members(['roles:admin', 'roles:designer', 'tenantId:test-tenant']); - expect(result.name).to.be.equal('Saiken'); - done(); - } - }); - }); - - it('- Should be able to get data with scope containing array of values', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'device': ['ios', 'windows'], - 'username': 'testuser' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '0' - }; - - tailedBeastModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.be.instanceof(Array); - expect(result).to.have.length(3); - done(); - } - }); - }); - - it('- Should be able to post data with ignoreAutoScope setting', function (done) { - var postData = { - 'name': 'Kyubi' - }; - var callContext = { ctx: {} }; - callContext.ignoreAutoScope = 'true'; - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.have.length(1); - expect(result._scope).to.have.members(['tenantId:default']); - expect(result.name).to.be.equal('Kyubi'); - done(); - } - }); - }); - - it('- Should be able retrieve records including the autoscope default', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'roles': ['admin'], - 'device': 'android' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'roles': '0', - 'device': '0' - }; - - tailedBeastModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.have.length(2); - done(); - } - }); - }); - - it('- Should be able to write custom query on scope which will take higher precidence on manual scope query', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser', - 'device': 'windows' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'device': '0' - }; - - tailedBeastModel.find({ - 'where': { - 'scope.device': 'ios' - } - }, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.have.length(1); - expect(result[0].name).to.be.equal('Kurama'); - done(); - } - }); - }); - - it('- Should be able retrieve default autoscoped records when ignore autoscope set internally', function (done) { - var callContext = {}; - callContext.ignoreAutoScope = true; - - tailedBeastModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.have.length(1); - expect(result[0].name).to.be.equal('Kyubi'); - done(); - } - }); - }); - - it('- Should be able to update record with scope for same tenant', function (done) { - var postData = { - 'name': 'Isobu', - 'scope': { - 'org': 'ev' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Isobu'); - postData.id = result.id; - postData._version = result._version; - postData.name = 'Isobu Part2'; - tailedBeastModel.upsert(postData, callContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res._scope).to.be.instanceof(Array); - expect(res.name).to.be.equal('Isobu Part2'); - - callContext.ctx = { - 'tenantId': 'test-tenant', - 'org': 'ev' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'org': '1' - }; - - tailedBeastModel.find({ where: { 'name': 'Isobu' } }, callContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).to.be.empty; - expect(res1).not.to.be.undefined; - - tailedBeastModel.find({}, callContext, function (err, res2) { - if (err) { - done(err); - } else { - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2[0].name).to.be.equal('Isobu Part2'); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Should create a new record when scope is changed for upsert for same tenant', function (done) { - var postData = { - 'name': 'Chomei', - 'scope': { - 'org': 'infy' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Chomei'); - - postData.id = result.id; - //postData._version = result._version; - postData.name = 'Chomei part2'; - postData.scope.unit = 'finacle'; - - tailedBeastModel.upsert(postData, callContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res._scope).to.be.instanceof(Array); - expect(res.name).to.be.equal('Chomei part2'); - - callContext.ctx = { - 'tenantId': 'test-tenant', - 'org': 'infy', - 'unit': 'finacle' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'org': '1', - 'unit': '1' - }; - - tailedBeastModel.find({ where: { 'name': 'Chomei' } }, callContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1[0].name).to.be.equal('Chomei'); - - tailedBeastModel.find({}, callContext, function (err, res2) { - if (err) { - done(err); - } else { - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2[0].name).to.be.equal('Chomei part2'); - expect(res2[1].name).to.be.equal('Chomei'); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Should not be able to update data created by another tenant', function (done) { - var postData = { - 'name': 'Nine tails', - 'scope': { - 'org': 'fin' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'org': 'fin' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'org': '1' - }; - - tailedBeastModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Nine tails'); - postData.id = result.id; - postData._version = result._version; - postData.name = 'Nine tails Kurama'; - - callContext.ctx = { - 'tenantId': 'new-tenant', - 'org': 'fin' - }; - - - tailedBeastModel.upsert(postData, callContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res._scope).to.be.instanceof(Array); - expect(res.name).to.be.equal('Nine tails Kurama'); - - tailedBeastModel.find({}, callContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1[0].name).to.be.equal('Nine tails Kurama'); - - callContext.ctx = { - 'tenantId': 'test-tenant', - 'org': 'fin' - }; - - - tailedBeastModel.find({}, callContext, function (err, res2) { - if (err) { - done(err); - } else { - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2[0].name).to.be.equal('Nine tails'); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Should be able to update record with scope for same tenant when idInjection is false on model', function (done) { - var postData = { - 'name': 'Rinn', - 'scope': { - 'org': 'ev' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'test-case' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - animeCharacterModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Rinn'); - postData.id = result.id; - postData._version = result._version; - postData.name = 'Rinn Nohara'; - - animeCharacterModel.upsert(postData, callContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res._scope).to.be.instanceof(Array); - expect(res.name).to.be.equal('Rinn Nohara'); - - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'test-case', - 'org': 'ev' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0', - 'org': '1' - }; - - animeCharacterModel.find({ where: { 'name': 'Rinn' } }, callContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).to.be.empty; - expect(res1).not.to.be.undefined; - - animeCharacterModel.find({}, callContext, function (err, res2) { - if (err) { - done(err); - } else { - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2[0].name).to.be.equal('Rinn Nohara'); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Should create a new record when scope is changed for upsert for same tenant when idInjection is false on model', function (done) { - var postData = { - 'name': 'Zetsu', - 'scope': { - 'org': 'infy' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'test-case' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - animeCharacterModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Zetsu'); - - postData.id = result.id; - postData._version = result._version; - postData.name = 'Black Zetsu'; - postData.scope.unit = 'finacle'; - - animeCharacterModel.upsert(postData, callContext, function (err, res) { - if (err) { - expect(err).not.to.be.null; - expect(err).not.to.be.undefined; - done(); - } else { - done(new Error('should throw an error because of same id')); - } - }); - } - }); - }); - - it('- Should not be able to update data created by another tenant when idInjection is false on model', function (done) { - var postData = { - 'name': 'Kabuto', - 'scope': { - 'org': 'ev' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'test-case' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - animeCharacterModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result._scope).to.be.instanceof(Array); - expect(result.name).to.be.equal('Kabuto'); - postData.id = result.id; - postData._version = result._version; - postData.name = 'Kabuto Yakushi'; - - callContext.ctx = { - 'tenantId': 'new-tenant', - 'username': 'new-test-case' - }; - - animeCharacterModel.upsert(postData, callContext, function (err, res) { - if (err) { - return done(err); - //expect(err).not.to.be.null; - //expect(err).not.to.be.undefined; - //done(); - } - else { - //done(new Error('Should not update the record of other tenant')); - expect(res.id).to.not.equal(postData.id); - return done(); - } - }); - } - }); - }); - - it('- Should be able to post a record with unique validation on property with scope', function (done) { - var modelUnique = 'ModelUnique'; - var modelDetailsUnique = { - name: modelUnique, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'a': { - 'type': 'string', - 'unique': true - }, - 'b': { - 'type': 'string', - 'unique': true - } - }, - strict: false, - idInjection: false, - mixins: { - 'HistoryMixin': true - }, - autoscope: [ - 'tenantId' - ], - scoreScheme: 'max' - }; - - var postData = [ - { - 'a': '1', - 'b': '1', - 'scope': { - 'rule': 'x' - } - }, - { - 'a': '2', - 'b': '2', - 'scope': { - 'rule': 'x' - } - }, - { - 'a': '1', - 'b': '1', - 'scope': { - 'rule': 'y', - 'category': 'x' - } - }, - { - 'a': '2', - 'b': '1', - 'scope': { - 'rule': 'y', - 'category': 'y' - } - }, - { - 'a': '1', - 'b': '2', - 'scope': { - 'rule': 'y', - 'category': 'y' - } - } - ]; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'unique-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - models.ModelDefinition.create(modelDetailsUnique, callContext, function (err, res) { - if (err) { - done(err); - } else { - var uniquePropModel = loopback.getModel('ModelUnique', callContext); - uniquePropModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.be.instanceof(Array); - expect(result).to.have.length(5); - done(); - } - }); - } - }); - }); - - it('- Should be able to get unique records with unique validation on property with scope', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'unique-tenant', - 'rule': 'y', - 'category': 'y' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'rule': '0', - 'category': '0' - }; - - var uniquePropModel = loopback.getModel('ModelUnique', callContext); - uniquePropModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result).to.be.instanceof(Array); - expect(result).to.have.length(2); - done(); - } - }); - }); - - it('- Should be able to insert data into TestModel with manual scope being a another Model', function (done) { - var postData = { - 'name': 'modelScopeAsModel', - 'scope': { - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - - PersonalizedModelWithScopeAsModel.create(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log("=================",result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.name).to.be.equal('modelScopeAsModel'); - expect(result._scope).to.be.instanceof(Array); - expect(result._scope).to.have.length(4); - expect(result.scope.__data).to.include.keys('location', 'lang', 'device'); - done(); - } - }); - }); - - it('- Should be able to retrieve data from TestModel with manual scope being a another Model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'location': '0', - 'lang': '0', - 'device': '0' - }; - - PersonalizedModelWithScopeAsModel.find({ 'where': { 'name': 'modelScopeAsModel' } }, callContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log("----------", result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result[0].name).to.be.equal('modelScopeAsModel'); - expect(result[0]._scope).to.be.instanceof(Array); - expect(result[0]._scope).to.have.length(4); - expect(result[0].scope.__data).to.include.keys('location', 'lang', 'device'); - done(); - } - }); - }); - - it('- Should be able to update in TestModel with manual scope being a another Model', function (done) { - var postData = { - 'name': 'modelScopeAsModelUpdate', - 'scope': { - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - } - }; - - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'location': '0', - 'lang': '0', - 'device': '0' - }; - PersonalizedModelWithScopeAsModel.find({ 'where': { 'name': 'modelScopeAsModel' } }, callContext, function (err, res) { - if (err) { - done(err); - } else { - postData._version = res[0]._version; - postData.id = res[0].id; - PersonalizedModelWithScopeAsModel.upsert(postData, callContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log("-------------", result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.name).to.be.equal('modelScopeAsModelUpdate'); - expect(result._scope).to.be.instanceof(Array); - expect(result._scope).to.have.length(4); - expect(result.scope.__data).to.include.keys('location', 'lang', 'device'); - done(); - } - }); - } - }); - }); - - it('- Should be able to delete data from TestModel with manual scope being a another Model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'lang': 'en-us', - 'device': 'android' - }; - - callContext.ctxWeights = { - 'tenantId': '0' - }; - PersonalizedModelWithScopeAsModel.find({ 'where': { 'name': 'modelScopeAsModelUpdate' } }, callContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("================", res); - PersonalizedModelWithScopeAsModel.deleteById(res[0].id, callContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log('-------------', result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - - it('- Test for insertion of data in model connected to memory DB', function (done) { - var memDbModel = { - 'name': 'MemDBModel', - 'base': 'PersistedModel', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'ObserverMixin': true, - 'ModelValidations': true, - 'HistoryMixin': true, - 'DataPersonalizationMixin': true - }, - 'autoscope': [ - 'tenantId' - ] - }; - - var data = { - 'name': 'scopedRecord', - 'scope': { - 'roles': ['admin', 'designer'], - 'device': 'mobile', - 'location': 'in' - } - }; - - var newmodel = loopback.createModel(memDbModel); - app.model(newmodel, { - dataSource: 'nullsrc' - }); - - var myModel = loopback.findModel('MemDBModel'); - - myModel.create(data, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model connected to memory DB', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'roles': ['admin', 'designer'], - 'device': 'mobile' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'location': '0', - 'roles': '0', - 'device': '0' - }; - var myModel = loopback.findModel('MemDBModel'); - myModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result[0].name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for insertion of data in model(no autoscope) connected to memory DB', function (done) { - var memDbModel = { - 'name': 'MemDBModelNoAutoScope', - 'base': 'PersistedModel', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'ObserverMixin': true, - 'ModelValidations': true, - 'HistoryMixin': true, - 'DataPersonalizationMixin': true - } - }; - - var data = { - 'name': 'scopedRecord', - 'scope': { - 'roles': ['admin', 'designer'], - 'device': 'mobile', - 'location': 'in' - } - }; - var newmodel = loopback.createModel(memDbModel); - app.model(newmodel, { - dataSource: 'nullsrc' - }); - - var myModel = loopback.findModel('MemDBModelNoAutoScope'); - - myModel.create(data, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model(no autoscope) connected to memory DB', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'roles': ['admin', 'designer'], - 'device': 'mobile' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'location': '0', - 'roles': '0', - 'device': '0' - }; - var myModel = loopback.findModel('MemDBModelNoAutoScope'); - myModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result[0].name).to.be.equal('scopedRecord'); - done(); - } - }); - }); - - it('- Test for fetching data from a model connected to memory DB with wrong contributor values', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'location': 'in', - 'roles': ['admin', 'designer'], - 'device': 'tab' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'location': '0', - 'roles': '0', - 'device': '0' - }; - callContext.ignoreContextList = ['device']; - var myModel = loopback.findModel('MemDBModel'); - myModel.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).to.be.empty; - expect(result).not.to.be.undefined; - done(); - } - }); - }); - - it('- Test for mixin applied property on model while posting data', function (done) { - var modelWithOutMixin = { - 'name': 'ModelWithOutMixin', - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': false, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'hidden': [], - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'mixins': { - 'HistoryMixin': true, - 'DataPersonalizationMixin': false - }, - 'autoscope': [ - 'tenantId' - ] - }; - var data = { - 'name': 'scopedRecord', - 'scope': { - 'roles': ['admin', 'designer'], - 'device': 'mobile', - 'location': 'in' - } - }; - - models.ModelDefinition.create(modelWithOutMixin, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create DataPersonalizationModel model'); - done(err); - } else { - var ModelWithOutMixins = loopback.getModel('ModelWithOutMixin', bootstrap.defaultContext); - ModelWithOutMixins.create(data, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.name).to.be.equal('scopedRecord'); - expect(result._scope).to.be.undefined; - expect(result._autoScope).to.be.undefined; - done(); - } - }); - } - }); - }); - - it('- Test for mixin applied property on model while getting data', function (done) { - var callContext = { ctx: {} }; - - var ModelWithOutMixins = loopback.getModel('ModelWithOutMixin', bootstrap.defaultContext); - ModelWithOutMixins.find({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log("=============",result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result[0].name).to.be.equal('scopedRecord'); - expect(result[0]._scope).to.be.oneOf([null, undefined]); - expect(result[0]._autoScope).to.be.oneOf([null, undefined]); - done(); - } - }); - }); - -}); -//End Of Describe - - -describe(chalk.blue('Data Personalization -Test for Persisted Model Static calls --REST'), function () { - this.timeout(1000000); - var modelName = 'OnlineGames'; - var personalizedModel; - var testUserAccessToken; - var newModelDetails = { - name: modelName, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: false, - plural: modelName, - mixins: { - 'HistoryMixin': true, - 'VersionMixin': true - }, - autoscope: [ - 'tenantId' - ] - }; - - var testData = [ - { - 'name': 'Assasins Creed', - 'id': '1a' - }, - { - 'name': 'Counter Strike', - 'id': '2a' - }, - { - 'name': 'Injustice', - 'id': '3a' - } - ]; - - // Creating testuser access token since removed jwt-assertion middleware - // so that we can access the models which are created using bootstrap.defaultContext - // are based on testuesr and test-tenant. - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - before('Create Test model', function (done) { - models.ModelDefinition.create(newModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create DataPersonalizationTestModel model'); - done(err); - } else { - personalizedModel = loopback.getModel(modelName, bootstrap.defaultContext); - done(); - } - }); - }); - - after('Remove Test Model', function (done) { - var callContext = {}; - callContext.ctx = { - 'tenantId': 'test-tenant', - 'username': 'testuser' - }; - - callContext.ctxWeights = { - 'tenantId': '0', - 'username': '0' - }; - - personalizedModel.destroyAll({}, callContext, function (err, result) { - if (err) { - done(err); - } else { - log.debug(bootstrap.defaultContext, 'Records deleted from Test model'); - } - }); - done(); - }); - - it('- POST', function (done) { - var url = bootstrap.basePath + '/' + modelName + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - testData = result.body; - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.have.length(3); - done(); - } - }); - }); - - it('- EXISTS', function (done) { - var url = bootstrap.basePath + '/' + modelName + '/1a/exists' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.exists).to.be.equal(true); - done(); - } - }); - }); - - it('- GET', function (done) { - var url = bootstrap.basePath + '/' + modelName + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body).to.have.length(3); - done(); - } - }); - }); - - it('- GET by ID', function (done) { - var url = bootstrap.basePath + '/' + modelName + '/1a' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Assasins Creed'); - done(); - } - }); - }); - - it('- COUNT', function (done) { - var url = bootstrap.basePath + '/' + modelName + '/count' + '?access_token=' + testUserAccessToken; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.count).to.be.equal(3); - done(); - } - }); - }); - - it('- PUT', function (done) { - var postData = testData[1]; - delete postData._version; - postData.id = '4a'; - postData.name = 'Sword Art Online'; - - var url = bootstrap.basePath + '/' + modelName + '?access_token=' + testUserAccessToken; - api - .put(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Sword Art Online'); - testData[1] = result.body; - done(); - } - }); - }); - - it('- FINDONE', function (done) { - var url = bootstrap.basePath + '/' + modelName + '/findOne?' + '?access_token=' + testUserAccessToken + '&{"where":{"name":"Assasins Creed"}'; - api - .get(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Assasins Creed'); - done(); - } - }); - }); - - it('- PUT by ID', function (done) { - var postData = testData[2]; - postData.name = 'Injustice 2'; - var url = bootstrap.basePath + '/' + modelName + '/3a' + '?access_token=' + testUserAccessToken; - api - .put(url) - .send(postData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Injustice 2'); - testData[2] = result.body; - done(); - } - }); - }); - - it('- DELETE by ID', function (done) { - var url = bootstrap.basePath + '/' + modelName + '/1a' + '?access_token=' + testUserAccessToken; - api - .del(url) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.count).to.be.equal(1); - done(); - } - }); - }); -}); -//End of Describe \ No newline at end of file diff --git a/test/datasource-personalization.js b/test/datasource-personalization.js deleted file mode 100644 index a8a53e5..0000000 --- a/test/datasource-personalization.js +++ /dev/null @@ -1,267 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('switch-data-source-test'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var events = require('events'); -var eventEmitter = new events.EventEmitter(); -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var loopback = require('loopback'); -describe(chalk.blue('data-source-personalization-test'), function () { - - this.timeout(60000); - - var models = [ - { - name: 'md1', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - } - }, - { - name: 'md2', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - } - } - ]; - - var datasources = [ - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/tenant1a', - 'database': 'tenant1a', - 'password': 'admin', - 'name': 'appdb', - 'connector': 'mongodb', - 'user': 'admin', - 'id': 'tenant1-appdb', - 'description': 'tenant1a', - 'connectionTimeout': 50000 - }, - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/tenant2a', - 'database': 'tenant2a', - 'password': 'admin', - 'name': 'appdb', - 'connector': 'mongodb', - 'user': 'admin', - 'id': 'tenant2-appdb', - 'description': 'tenant2a', - 'connectionTimeout': 50000 - } - ]; - - var tenant1Scope = { - ignoreAutoScope: false, - ctx: { - tenantId: 'tenant1' - } - }; - - var tenant2Scope = { - ignoreAutoScope: false, - ctx: { - tenantId: 'tenant2' - } - }; - - var mappings = [ - { - modelName: 'md1', - dataSourceName: 'appdb', - }, - { - modelName: 'md2', - dataSourceName: 'appdb', - } - ]; - - var callContext = bootstrap.defaultContext; - callContext.ignoreAutoScope = true; - callContext.tenantId = 'default'; - - var ModelDefinition = bootstrap.models.ModelDefinition; - var DataSourceDefinition = bootstrap.models.DataSourceDefinition; - var DataSourceMapping = bootstrap.models.DataSourceMapping; - - var cleanup = function (done) { - async.series([function (cb) { - var model = bootstrap.models['DataSourceDefinition']; - if (model) { - var options = { - ctx: {} - }; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - model.remove({}, options, function () { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - var model = bootstrap.models['DataSourceMapping']; - var options = { - ctx: {} - }; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - if (model) { - model.destroyAll({}, options, function () { - cb(); - }); - } else { - cb(); - } - }, - function (cb) { - var options = { - ctx: {} - }; - options.fetchAllScopes = true; - ModelDefinition.remove({ - 'where': { - 'name': { - inq: ['md1', 'md2'] - } - } - }, options, function () { - cb(); - }); - }, function () { - done(); - }]); - }; - - before('setup datasources', function (done) { - - eventEmitter.setMaxListeners(100); - var ds; - async.series([function (cb) { - cleanup(cb); - }, - function (cb) { - ds = datasources[0]; - DataSourceDefinition.findById(ds.id, tenant1Scope, function (err, res) { - if (err) { - log.error(callContext, 'error in datasource find', err); - return cb(err); - } - if (!res) { - DataSourceDefinition.create(ds, tenant1Scope, function (err, res) { - if (err) { - log.error(callContext, 'error in datasource find', err); - return cb(err); - } - cb(); - }); - } else { - log.debug(callContext, 'data source exists ', ds.name, ds.database); - cb(); - } - }); - }, - function (cb) { - ds = datasources[1]; - DataSourceDefinition.findById(ds.id, tenant2Scope, function (err, res) { - if (err) { - log.error(callContext, 'error in datasource find', err); - return cb(err); - } - if (!res) { - DataSourceDefinition.create(ds, tenant2Scope, function (err, res) { - if (err) { - log.error(callContext, 'error in datasource find', err); - return cb(err); - } - cb(); - }); - } else { - log.debug(callContext, 'data source exists ', ds.name, ds.database); - cb(); - } - }); - - }, - function (cb) { - ModelDefinition.create(models, callContext, function (err, res) { - if (err) { - log.debug(callContext, 'unable to create model'); - cb(); - } else { - cb(); - } - }); - }, - function (cb) { - DataSourceMapping.create(mappings, callContext, function (err, res) { - if (err) { - cb(err); - } else { - cb(); - } - }); - }, - function (cb) { - Object.keys(bootstrap.app.datasources).forEach(function iter(id) { - log.debug(callContext, id, bootstrap.app.datasources[id].settings); - }); - cb(); - }, - function () { - done(); - }]); - }); - - - it('tenant1 and model 1 ', function (done) { - var model = loopback.getModel('md1', callContext); - var ds = model.getDataSource(tenant1Scope); - expect(ds).not.to.be.null; - expect(ds.settings.database).to.equal('tenant1a'); - done(); - }); - - it('tenant2 and model 1 ', function (done) { - var model = loopback.getModel('md2', callContext); - var ds = model.getDataSource(tenant2Scope); - expect(ds).not.to.be.null; - expect(ds.settings.database).to.equal('tenant2a'); - done(); - }); - - after('after clean up', function (done) { - cleanup(function () { - done(); - }); - }); - -}); diff --git a/test/datasources-pg.json1 b/test/datasources-pg.json1 new file mode 100644 index 0000000..dc7d93d --- /dev/null +++ b/test/datasources-pg.json1 @@ -0,0 +1,37 @@ +{ + "memdb": { + "name": "memdb", + "connector": "memory" + }, + "transient": { + "name": "transient", + "connector": "transient" + }, + + "mongodb": { + "host": "localhost", + "port": 27017, + "url": "mongodb://localhost:27017/oe-common-mixins-test", + "database": "oe-common-mixins-test", + "password": "admin", + "name": "mongodb", + "connector": "mongodb", + "user": "admin", + "connectionTimeout": 500000, + "connectTimeoutMS": 500000, + "socketTimeoutMS": 500000 + }, + + "db": { + "host": "localhost", + "port": 5432, + "url": "postgres://postgres:postgres@localhost:5432/oe-common-mixins-test", + "database": "oe-common-mixins-test", + "password": "postgres", + "name": "db", + "connector": "loopback-connector-postgresql", + "user": "postgres", + "max": 50, + "connectionTimeout": 50000 + } +} diff --git a/test/datasources.json b/test/datasources.json new file mode 100644 index 0000000..c0b79f1 --- /dev/null +++ b/test/datasources.json @@ -0,0 +1,24 @@ +{ + "memdb": { + "name": "memdb", + "connector": "memory" + }, + "transient": { + "name": "transient", + "connector": "transient" + }, + + "db": { + "host": "localhost", + "port": 27017, + "url": "mongodb://localhost:27017/oe-cloud-test", + "database": "oe-cloud-test", + "password": "admin", + "name": "db", + "connector": "mongodb", + "user": "admin", + "connectionTimeout": 500000, + "connectTimeoutMS": 500000, + "socketTimeoutMS": 500000 + } +} diff --git a/test/datasources.mongo.js b/test/datasources.mongo.js new file mode 100644 index 0000000..c4cf4f9 --- /dev/null +++ b/test/datasources.mongo.js @@ -0,0 +1,31 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ +var mongoHost = process.env.MONGO_HOST || 'localhost'; +var dbName = process.env.DB_NAME || 'oe-cloud-test'; +module.exports = +{ + "memdb": { + "name": "memdb", + "connector": "memory" + }, + "transient": { + "name": "transient", + "connector": "transient" + }, + "db": { + "host": mongoHost, + "port": 27017, + "url": "mongodb://" + mongoHost + ":27017/" + dbName, + "database": dbName, + "password": "admin", + "name": "db", + "connector": "mongodb", + "user": "admin", + "connectionTimeout": 500000 + } +}; + diff --git a/server/datasources.oracle.js b/test/datasources.oracle.js similarity index 66% rename from server/datasources.oracle.js rename to test/datasources.oracle.js index 4c5bf06..8792ffd 100644 --- a/server/datasources.oracle.js +++ b/test/datasources.oracle.js @@ -1,3 +1,9 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ /** ** ** ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), @@ -28,24 +34,7 @@ module.exports = { 'port': oraclePort, 'password': oracleUserPassword, 'user': oracleUserName - }, - 'emailDs': { - 'name': 'emailDs', - 'connector': 'mail', - 'transports': [{ - 'type': 'smtp', - 'host': 'smtp.gmail.com', - 'port': 587, - 'auth': { - 'user': 'yourGmailAccount@gmail.com', - 'pass': 'yourSecretPassword' - } - }] - }, - 'gridfs_db': { - 'name': 'gridfs_db', - 'connector': 'loopback-component-storage', - 'provider': 'filesystem', - 'root': './' } }; + + diff --git a/test/datasources.postgres.js b/test/datasources.postgres.js new file mode 100644 index 0000000..f2c7ba6 --- /dev/null +++ b/test/datasources.postgres.js @@ -0,0 +1,32 @@ +/** + * + * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ +var postgresHost = process.env.POSTGRES_HOST || 'localhost'; +var dbName = process.env.DB_NAME || 'oe-cloud-test'; +module.exports = +{ + "memdb": { + "name": "memdb", + "connector": "memory" + }, + "transient": { + "name": "transient", + "connector": "transient" + }, + + "db": { + "host": postgresHost, + "port": 5432, + "url": "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbName, + "database": dbName, + "password": "postgres", + "name": "db", + "connector": "oe-connector-postgresql", + "user": "postgres", + "connectionTimeout": 50000 + } +}; + diff --git a/test/datasources_alldb.json1 b/test/datasources_alldb.json1 new file mode 100644 index 0000000..21c611d --- /dev/null +++ b/test/datasources_alldb.json1 @@ -0,0 +1,46 @@ +{ + "memdb": { + "name": "memdb", + "connector": "memory" + }, + "transient": { + "name": "transient", + "connector": "transient" + }, + + "db": { + "host": "localhost", + "port": 27017, + "url": "mongodb://localhost:27017/oe-cloud-test", + "database": "oe-cloud-test", + "password": "admin", + "name": "db", + "connector": "mongodb", + "user": "admin", + "connectionTimeout": 500000, + "connectTimeoutMS": 500000, + "socketTimeoutMS": 500000 + }, + + "pgdb": { + "host": "localhost", + "port": 5432, + "url": "postgres://postgres:postgres@localhost:5432/oe-cloud-test", + "database": "oe-cloud-test", + "password": "postgres", + "name": "pgdb", + "connector": "oe-connector-postgresql", + "user": "postgres", + "max": 50, + "connectionTimeout": 50000 + }, + "oradb": { + "name": "oradb", + "connector": "oe-connector-oracle", + "database": "ORCLCDB", + "host": "10.73.53.144", + "port": "1521", + "password": "atul", + "user": "ATUL_TEST" + } +} diff --git a/test/decision-graph-tests.js b/test/decision-graph-tests.js deleted file mode 100644 index 4f60b8d..0000000 --- a/test/decision-graph-tests.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -//var loopback = require('loopback'); -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -// var _db = require('mongodb').Db; -// var _srvr = require('mongodb').Server; -//var api = bootstrap.api; -//var debug = require('debug')('decision-table-test'); -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var dbName = process.env.DB_NAME || 'db'; - -describe(chalk.blue('Decision graph insertion tests'), function() { - this.timeout(60000); - before('remove db entries', function(done){ - models.DecisionGraph.destroyAll({}, bootstrap.defaultContext, function(err){ - done(); - }); - }); - after('remove db entries', function(done){ - models.DecisionGraph.destroyAll({}, bootstrap.defaultContext, function(){ - done(); - }); - }); - - it('Should fail to create decision graph if any node fails the validation ', function (done) { - var decisionGraphData = { "name": "new22", "data": { "Boxed Invocation 1": " (key : value)", "Boxed Context 1": "{key : value}" }, "graph": { "nodes": [{ "id": "00oz0nv89", "i": 2, "name": "Knowledge Source 1", "nodeType": "KNOWLEDGE_SOURCE", "x": 846, "y": 285, "data": {} }, { "id": "fvtqfb1pg", "i": 4, "name": "Boxed Invocation 1", "nodeType": "BOXED_INVOCATION", "x": 709, "y": 143, "data": { "target": "", "parameters": [{ "key": "key", "value": "value" }] }, "__validity": { "valid": false, "errormessage": { "name": "SyntaxError", "location": { "start": { "offset": 6, "line": 1, "column": 7 }, "end": { "offset": 7, "line": 1, "column": 8 } } } } }, { "id": "btdt3cu6hg", "i": 5, "name": "Boxed Context 1", "nodeType": "BOXED_CONTEXT", "x": 497, "y": 261, "data": { "result": { "type": "none" }, "parameters": [{ "key": "key", "value": "value" }] }, "__validity": { "valid": true, "errormessage": null } }], "connections": [] }, "payload": "" } - - models.DecisionGraph.create(decisionGraphData, bootstrap.defaultContext, function (err, res) { - expect(err).not.to.be.undefined; - expect(err.message).to.equal('Decision graph contains an invalid FEEL node.'); - done(); - }); - }); - - it('should parse and insert workbook data correctly', function(done) { - //our workbook is a base64 encoded string. - var workbookBase64 = ""; - var inputData = { - name: 'foo', - graphDocument: { - "documentName": "RoutingDecisionService-Demo.xlsx", - "documentData" : "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }; - - models.DecisionGraph.create(inputData, bootstrap.defaultContext, function(err, decisionGraph) { - // debugger; - if (err) { - done(err) - } - else { - expect(decisionGraph).to.be.defined; - // expect(decisionGraph).to.be.array; - expect(decisionGraph.data).to.be.object; - var keys = [ - 'Routing', - 'Routing Rules', - 'Post bureau risk category', - 'Post Bureau risk category table', - 'Post bureau affordability', - 'Affordability calculation', - 'Credit contingency factor', - 'Credit Contingency factor table', - 'Required monthly installment', - 'Installment Calculation', - 'Application risk score', - 'Application risk score model' - ]; - - Object.keys(decisionGraph.data).map(key => { - expect(keys.indexOf(key), 'Missing key: ' + key).to.not.equal(-1); - }); - done(); - } - - // done(err); - }); - }); -}); - diff --git a/test/decision-service-tests.js b/test/decision-service-tests.js deleted file mode 100644 index e698609..0000000 --- a/test/decision-service-tests.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -//var loopback = require('loopback'); -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var _db = require('mongodb').Db; -var _srvr = require('mongodb').Server; -//var api = bootstrap.api; -//var debug = require('debug')('decision-table-test'); -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var dbName = process.env.DB_NAME || 'db'; -var async = require('async'); - -describe(chalk.blue('Decision service insertion tests'), function() { - this.timeout(60000); - var testData = { - graphName: 'foo1', - svcName: 'foosvc', - graphDocument: { - "documentName": "RoutingDecisionService-Demo.xlsx", - "documentData" : "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - }; - - //Note: - //assuming here each time the test is run - //we start off with a fresh/blank database... - - it('should insert service data correctly', function(done) { - var decisionGraphData = { - name: testData.graphName, - graphDocument: testData.graphDocument - }; - // debugger; - models.DecisionGraph.create(decisionGraphData, bootstrap.defaultContext, function(err) { - if (err) { - done(err); - } - else { - var decisionServiceData = { - name: testData.svcName, - graphId: testData.graphName, - decisions: ['Routing'] - }; - - models.DecisionService.create(decisionServiceData, bootstrap.defaultContext, function(err) { - if(err) { - done(err); - } - else { - done(); - } - }); - } - }) - }); - - it('should fail when you insert service data with a non-existant decision name', function(done) { - // debugger; - models.DecisionService.create({ - name: 'foosvc2', - graphId: testData.graphName, - decisions: ['Routing', 'Affordability'] - }, bootstrap.defaultContext, function(err, res) { - expect(err, 'it did not fail!').to.not.be.null; - done(); - }) - }); - - it('should execute a service correctly', function(done){ - var payload = { - 'Applicant data': { - Age: 51, - MaritalStatus: 'M', - EmploymentStatus: 'EMPLOYED', - 'ExistingCustomer': false, - 'Monthly': { - 'Income': 10000, - 'Repayments': 2500, - 'Expenses': 3000 - } - }, - 'Requested product': { - ProductType: 'STANDARD LOAN', - Rate: 0.08, - Term: 36, - Amount: 100000 - }, - 'Bureau data': { - Bankrupt: false, - CreditScore: 600 - } - }; - models.DecisionService.invoke('foosvc', payload, bootstrap.defaultContext, (err, result) => { - if (err) { - done(err) - } else { - expect(result).to.eql({ - Routing: { - Routing: 'ACCEPT' - } - }); - done(); - } - }); - }); - - it('should update a service without errors', function(done){ - models.DecisionService.findOne({ where: { name: testData.svcName }}, bootstrap.defaultContext, function(err, data) { - if (err) { - done(err); - } else { - // console.dir(data); - expect(data).to.not.be.null; - expect(data.name).to.equal(testData.svcName); - // expect(data.decisions).to.be.array; - expect(Array.isArray(data.decisions)).to.be.true; - // console.log(data.decisions); - expect(data.decisions.slice()).to.eql(['Routing']); - expect(data.id).to.be.string; - var id = data.id; - var version = data._version - var graphId = data.graphId; - debugger; - models.DecisionService.upsert({ - id: id, - _version: version, - decisions:['Routing Rules'], - graphId - }, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err); - } else { - expect(result).to.be.defined; - expect(result.name).to.equal(testData.svcName); - done(); - } - }); - // done(); - } - }); - }); -}); - diff --git a/test/decision-table-test.js b/test/decision-table-test.js deleted file mode 100644 index dbe3130..0000000 --- a/test/decision-table-test.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); - -var ruleEngineConfigData = { - "key": "businessRuleEngine", - "value": { - "engine": "DROOLS" - } -} - -var payload = { - "countries": ["Australia", "India", "USA", "UK"] -}; - -describe(chalk.blue('Decision Table Create - Drools'), function () { - this.timeout(600000); - - after('cleaning up', function (done) { - models.DocumentData.destroyAll({}, bootstrap.defaultContext, function (err, result) { - models.DecisionTable.destroyAll({}, bootstrap.defaultContext, function (err, result) { - done(); - }); - }); - }); - - it('Should fail to create decision table as DROOLs is not configured', function (done) { - var decisionTableData = { - "name": "sample", - "document": { - "documentName": "sample.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - } - models.SystemConfig.create(ruleEngineConfigData, bootstrap.defaultContext, function (err, res) { - if (err) { - return done(err); - } - models.DecisionTable.create(decisionTableData, bootstrap.defaultContext, function (err, result) { - console.log("=========================================="); - console.log("err: ", err); - console.log("=========================================="); - expect(err).not.to.be.undefined; - expect(err.code).to.equal('ECONNREFUSED'); - expect(err.errno).to.equal('ECONNREFUSED'); - models.SystemConfig.destroyAll({}, bootstrap.defaultContext, function (err, result) { - done(err); - }); - }); - }); - }); - - it('Should sucessfully create decision table but fail to execute it', function (done) { - var decisionTableData = { - "name": "sample", - "document": { - "documentName": "sample.xlsx", - "documentData": "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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" - } - } - models.DecisionTable.create(decisionTableData, bootstrap.defaultContext, function (error, result) { - if (error) { - done(error); - } - models.SystemConfig.create(ruleEngineConfigData, bootstrap.defaultContext, function (sysErr, data) { - if (sysErr) { - done(sysErr); - } - models.DecisionTable.exec("sample", payload, bootstrap.defaultContext, function (err, res) { - console.log("=========================================="); - console.log("err: ", err); - console.log("=========================================="); - expect(err).not.to.be.undefined; - expect(err.code).to.equal('ECONNREFUSED'); - expect(err.errno).to.equal('ECONNREFUSED'); - models.SystemConfig.destroyAll({}, bootstrap.defaultContext, function (err, result) { - done(err); - }); - }); - }); - }); - }); - - it('Should fail to create decision table as base64 validation of decision data is violated', function (done) { - var decisionTableData = { - "name": "sample", - "document": { - "documentName": "sample.xlsx", - "documentData": "wrong decision data" - } - } - models.DecisionTable.create(decisionTableData, bootstrap.defaultContext, function (err, res) { - expect(err).not.to.be.undefined; - expect(err.message).to.equal('Decision table data provided is not a base64 encoded string'); - done(); - }); - }); - - it('Should parse the document and return the decision table.', function (done) { - var decisionTableExcel = {"documentData":"data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,UEsDBBQABgAIAAAAIQBBN4LPbgEAAAQFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsVMluwjAQvVfqP0S+Vomhh6qqCBy6HFsk6AeYeJJYJLblGSj8fSdmUVWxCMElUWzPWybzPBit2iZZQkDjbC76WU8kYAunja1y8T39SJ9FgqSsVo2zkIs1oBgN7+8G07UHTLjaYi5qIv8iJRY1tAoz58HyTulCq4g/QyW9KuaqAvnY6z3JwlkCSyl1GGI4eINSLRpK3le8vFEyM1Ykr5tzHVUulPeNKRSxULm0+h9J6srSFKBdsWgZOkMfQGmsAahtMh8MM4YJELExFPIgZ4AGLyPdusq4MgrD2nh8YOtHGLqd4662dV/8O4LRkIxVoE/Vsne5auSPC/OZc/PsNMilrYktylpl7E73Cf54GGV89W8spPMXgc/oIJ4xkPF5vYQIc4YQad0A3rrtEfQcc60C6Anx9FY3F/AX+5QOjtQ4OI+c2gCXd2EXka469QwEgQzsQ3Jo2PaMHPmr2w7dnaJBH+CW8Q4b/gIAAP//AwBQSwMEFAAGAAgAAAAhALVVMCP0AAAATAIAAAsACAJfcmVscy8ucmVscyCiBAIooAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACskk1PwzAMhu9I/IfI99XdkBBCS3dBSLshVH6ASdwPtY2jJBvdvyccEFQagwNHf71+/Mrb3TyN6sgh9uI0rIsSFDsjtnethpf6cXUHKiZylkZxrOHEEXbV9dX2mUdKeSh2vY8qq7iooUvJ3yNG0/FEsRDPLlcaCROlHIYWPZmBWsZNWd5i+K4B1UJT7a2GsLc3oOqTz5t/15am6Q0/iDlM7NKZFchzYmfZrnzIbCH1+RpVU2g5abBinnI6InlfZGzA80SbvxP9fC1OnMhSIjQS+DLPR8cloPV/WrQ08cudecQ3CcOryPDJgosfqN4BAAD//wMAUEsDBBQABgAIAAAAIQCBPpSX8wAAALoCAAAaAAgBeGwvX3JlbHMvd29ya2Jvb2sueG1sLnJlbHMgogQBKKAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsUk1LxDAQvQv+hzB3m3YVEdl0LyLsVesPCMm0KdsmITN+9N8bKrpdWNZLLwNvhnnvzcd29zUO4gMT9cErqIoSBHoTbO87BW/N880DCGLtrR6CRwUTEuzq66vtCw6acxO5PpLILJ4UOOb4KCUZh6OmIkT0udKGNGrOMHUyanPQHcpNWd7LtOSA+oRT7K2CtLe3IJopZuX/uUPb9gafgnkf0fMZCUk8DXkA0ejUISv4wUX2CPK8/GZNec5rwaP6DOUcq0seqjU9fIZ0IIfIRx9/KZJz5aKZu1Xv4XRC+8opv9vyLMv072bkycfV3wAAAP//AwBQSwMEFAAGAAgAAAAhAMAAUPgvAgAAggQAAA8AAAB4bC93b3JrYm9vay54bWysVF1v0zAUfUfiP1h+T/OxZN2iJtPWFlEJoWmU7aUvrnPTWHXsYDtrK8R/5yYhMOjLELzE1/bN8T3nXHt2c6wleQZjhVYZDScBJaC4LoTaZfTz+p13RYl1TBVMagUZPYGlN/nbN7ODNvut1nuCAMpmtHKuSX3f8gpqZie6AYU7pTY1czg1O982BlhhKwBXSz8Kgku/ZkLRASE1r8HQZSk4LDRva1BuADEgmcPybSUaO6LV/DVwNTP7tvG4rhuE2Aop3KkHpaTm6WqntGFbibSPYTIiY3gGXQtutNWlmyCUPxR5xjcM/DAcKOezUkh4HGQnrGk+sro7RVIimXXLQjgoMnqJU32A3xZM29y1QuJuGMdRQP38pxX3hhRQsla6NZowwmNichFFUZeJpG6lA6OYg7lWDjX8of6/6tVjzyuN7pAH+NIKA9gUnWz5DL+Mp2xr75mrSGtkRhfpZvm4gWevBJAbhxQ3L/Rl5+b9hcKMd1R95DrUM8R/8s5nXfc+CjjYXwp2U3J8EqrQh4ziXTi9iA/98pMoXJXRKIivcX9Yew9iV7mMTqdJ0p/9ArrvdzyiH4nqff7U3YEQL1Y3rjorKTGpwMCsirBHGH/jTHL0tRv6xCRKwj4Dju6DdfkMR5RUZPRrGAe30+A69oLlReLFV9eRdxVfRN48XkTLZLpcLO+Sb/+3i9HZdHwIuiorZtzaML7H5+MByjtmsasHQlgnGjFW7Y9/5d8BAAD//wMAUEsDBBQABgAIAAAAIQDQUPfTaQEAAJcDAAAUAAAAeGwvc2hhcmVkU3RyaW5ncy54bWx8k9FOwjAUhu9NfIfmXHg1GCzGEN1GCMR4gZoQfYDSHUZj1872DOTt7ZjGZB3erd9/enL6tUvnX5ViB7ROGp3BdDwBhlqYQuoyg/e3x9EMmCOuC66MxgxO6GCeX1+lzhHze7XLYE9U38exE3usuBubGrVPdsZWnPzSlrGrLfLC7RGpUnEymdzFFZcamDCNpgxuE2CNlp8NLjuQzCBPncxTypdGF5L8eGlMeRq3sAsWYohuGoVvfKuwXz7qg41pyB+TWb/DXQgDjAeJR6bwgCrMuBuYsgwmWeGWmD13CnpI98EEJyyNPfXD1z64UfQwnfUpPMlyDwFdCIE1hXyFQkmNYbDBHdoQv/h3ENJ1a2R6gSeXpmnvaSCrayUF18TIGHbyL6IMi9pDMtvq4l15+xj+69XoAi0r/twHxWSb4K5gbY4QwTMWsqn8x7DbX4cRdNIiuGT77CnxjX6ERdDzGfsfK/8GAAD//wMAUEsDBBQABgAIAAAAIQA7bTJLwQAAAEIBAAAjAAAAeGwvd29ya3NoZWV0cy9fcmVscy9zaGVldDEueG1sLnJlbHOEj8GKwjAURfcD/kN4e5PWhQxDUzciuFXnA2L62gbbl5D3FP17sxxlwOXlcM/lNpv7PKkbZg6RLNS6AoXkYxdosPB72i2/QbE46twUCS08kGHTLr6aA05OSonHkFgVC7GFUST9GMN+xNmxjgmpkD7m2UmJeTDJ+Ysb0Kyqam3yXwe0L0617yzkfVeDOj1SWf7sjn0fPG6jv85I8s+ESTmQYD6iSDnIRe3ygGJB63f2nmt9DgSmbczL8/YJAAD//wMAUEsDBBQABgAIAAAAIQCLgm5YkwYAAI4aAAATAAAAeGwvdGhlbWUvdGhlbWUxLnhtbOxZz4sbNxS+F/o/DHN3/GtmbC/xBntsZ9vsJiHrpOSotWWPspqRGcm7MSFQkmOhUJqWXgq99VDaBhLoJf1rtk1pU8i/0CfN2COt5W6abiAtWcMyo/n09Om9N9+TNBcv3Y2pc4RTTljSdqsXKq6DkxEbk2Tadm8OB6Wm63CBkjGiLMFtd4G5e2n7/fcuoi0R4Rg70D/hW6jtRkLMtsplPoJmxC+wGU7g2YSlMRJwm07L4xQdg92YlmuVSlCOEUlcJ0ExmL02mZARdobSpLu9NN6ncJsILhtGNN2XprHRQ2HHh1WJ4Ase0tQ5QrTtwjhjdjzEd4XrUMQFPGi7FfXnlrcvltFW3omKDX21fgP1l/fLO4wPa2rMdHqwGtTzfC/orOwrABXruH6jH/SDlT0FQKMRzDTjotv0u61uz8+xGii7tNjuNXr1qoHX7NfXOHd8+TPwCpTZ99bwg0EIXjTwCpThfYtPGrXQM/AKlOGDNXyj0ul5DQOvQBElyeEauuIH9XA52xVkwuiOFd7yvUGjlhsvUJANq+ySQ0xYIjblWozusHQAAAmkSJDEEYsZnqARZHGIKDlIibNLphEk3gwljENzpVYZVOrwX/48daU8grYw0npLXsCErzVJPg4fpWQm2u6HYNXVIC+fff/y2RPn5bPHJw+enjz46eThw5MHP2a2jI47KJnqHV98+9mfX3/s/PHkmxePvrDjuY7/9YdPfvn5czsQJlt44fmXj397+vj5V5/+/t0jC7yTogMdPiQx5s5VfOzcYDHMTXnBZI4P0n/WYxghYvRAEdi2mO6LyABeXSBqw3Wx6bxbKQiMDXh5fsfguh+lc0EsI1+JYgO4xxjtstTqgCtyLM3Dw3kytQ+eznXcDYSObGOHKDFC25/PQFmJzWQYYYPmdYoSgaY4wcKRz9ghxpbZ3SbE8OseGaWMs4lwbhOni4jVJUNyYCRS0WmHxBCXhY0ghNrwzd4tp8uobdY9fGQi4YVA1EJ+iKnhxstoLlBsMzlEMdUdvotEZCO5v0hHOq7PBUR6iilz+mPMua3PtRTmqwX9CoiLPex7dBGbyFSQQ5vNXcSYjuyxwzBC8czKmSSRjv2AH0KKIuc6Ezb4HjPfEHkPcUDJxnDfItgI99lCcBN0VadUJIh8Mk8tsbyMmfk+LugEYaUyIPuGmsckOVPaT4m6/07Us6p0WtQ7KbG+WjunpHwT7j8o4D00T65jeGfWC9g7/X6n3+7/Xr83vcvnr9qFUIOGF6t1tXaPNy7dJ4TSfbGgeJer1TuH8jQeQKPaVqi95WorN4vgMt8oGLhpilQfJ2XiIyKi/QjNYIlfVRvRKc9NT7kzYxxW/qpZbYnxKdtq/zCP99g427FWq3J3mokHR6Jor/irdthtiAwdNIpd2Mq82tdO1W55SUD2/ScktMFMEnULicayEaLwdyTUzM6FRcvCoinNL0O1jOLKFUBtFRVYPzmw6mq7vpedBMCmClE8lnHKDgWW0ZXBOddIb3Im1TMAFhPLDCgi3ZJcN05Pzi5LtVeItEFCSzeThJaGERrjPDv1o5PzjHWrCKlBT7pi+TYUNBrNNxFrKSKntIEmulLQxDluu0Hdh9OxEZq13Qns/OEynkHucLnuRXQKx2cjkWYv/Osoyyzlood4lDlciU6mBjEROHUoiduunP4qG2iiNERxq9ZAEN5aci2QlbeNHATdDDKeTPBI6GHXWqSns1tQ+EwrrE9V99cHy55sDuHej8bHzgGdpzcQpJjfqEoHjgmHA6Bq5s0xgRPNlZAV+XeqMOWyqx8pqhzK2hGdRSivKLqYZ3Alois66m7lA+0unzM4dN2FB1NZYP911T27VEvPaaJZ1ExDVWTVtIvpmyvyGquiiBqsMulW2wZeaF1rqXWQqNYqcUbVfYWCoFErBjOoScbrMiw1O281qZ3jgkDzRLDBb6saYfXE61Z+6Hc6a2WBWK4rVeKrTx/61wl2cAfEowfnwHMquAolfHtIESz6spPkTDbgFbkr8jUiXDnzlLTdexW/44U1PyxVmn6/5NW9Sqnpd+qlju/Xq32/Wul1a/ehsIgorvrZZ5cBnEfRRf7xRbWvfYCJl0duF0YsLjP1gaWsiKsPMNXa5g8wDgHRuRfUBq16qxuUWvXOoOT1us1SKwy6pV4QNnqDXug3W4P7rnOkwF6nHnpBv1kKqmFY8oKKpN9slRperdbxGp1m3+vcz5cxMPNMPnJfgHsVr+2/AAAA//8DAFBLAwQUAAYACAAAACEAD6nv6xAEAACVFQAADQAAAHhsL3N0eWxlcy54bWzcWN+PmzgQfj/p/gfkd5YfARaikKrZLFKlXlVpt9K9OmASq8aOwNlL7nT/e8cGArntZtls0m3vIQo29nzfjGfGw0zebQtmPJCyooLHyLmykUF4KjLKlzH6cp+YITIqiXmGmeAkRjtSoXfT33+bVHLHyN2KEGmACF7FaCXlemxZVboiBa6uxJpweJOLssAShuXSqtYlwVmlNhXMcm07sApMOaoljIt0iJACl183azMVxRpLuqCMyp2WhYwiHX9YclHiBQOqW8fDaStbDx6JL2haikrk8grEWSLPaUoes4ysyAJJ00kuuKyMVGy4jJELohXC+CsXf/FEvQIDNqumk+pv4wEzmHGQNZ2kgonSkGAZIKZnOC5IveIGM7ooqVqW44KyXT3tqgltzGZdQUE1NWkpHjWb6WShVl0cS0NWgEkZ21sgVMrCxHQCJyFJyRMYGM3z/W4NqnJwmpqyXvfM6mWJd47rD99QCUYzxWJ50zewrSQsDuc8ZEiqzs2+uo6iKHSCMAwjb+R4nra01dNBmXgI3yHwlGdkS7IYBZ7W60IwjWudG8PvrDYCq137fug7kevBT3vx2xlZa9rCX87IQw0whIF2KgiihSgzyLhtIgnAgeup6YSRXILvlnS5Uv9SrJUnCykhPU0nGcVLwTFTOaDd0d8JmRqScozkCpJqm3T+y0xBNAiD1msuh1QG7atZvznpQVzBzq2ZfyXdXuIbh171S1mlDoQfTrkJMQjYlDB2p0Lrz3wfteqm3+YG3xRJIT9AfofKSd3L7SPcH81jHaH1QEVuX1otuyfWdU+Sa2zzPcAAVlC7dKwcKGSa3QZer9lO1TKqSmlGoEk3munc1dQwL9X/GaRXyVbl2BEtXir7wELPyFZlz+kWghvmkmcBNle1jPIOKIE6pBN1qn3iPaNLXpDaTcCnBzjC6KcBB4Of4u/n0fxxCDzhPCfDQR464k9dHJwMEBx32HPrc/1j4c4R+JcKkou6yuAEOkA7L3r+MnwUCZ82xYKUie4X9K6fg8vo6TT+BOTxm24I5Ouy+wE+5MD/XeZ56n550ywLN935DK0LNijRenXgQRW4r+cM1dOJ0SflwazHYLGhDHoP36kAQWa27WpK3beQqnulq809CqT0jOR4w+T9/mWMuuc/SEY3Bbhas+ozfRBSi4hR9/xRfVQ6gepCkK38WMFXIPwbm5LG6J/b2XU0v01cM7RnoemNiG9G/mxu+t7NbD5PItu1b/7tNdNe0UrTLT+oGRxvXDFouJWNsg35u24uRr1BTV/3UIB2n3vkBvZ737HNZGQ7phfg0AyDkW8mvuPOA2926yd+j7t/GnfHthyn7lcq8v5Y0oIwytuzak+oPwuHBMMjSljtSVhdP3X6DQAA//8DAFBLAwQUAAYACAAAACEAf/w/hxEEAABBDQAAGAAAAHhsL3dvcmtzaGVldHMvc2hlZXQxLnhtbJRXy3LqOBDdT9X8g8v7i7EDBCjMrQqPkMVUTc2dx1rYAlSxLY8sQjJff48kP2VCmA3G7dOn1Ufqdnvx/T1NnDcqCsaz0PUHQ9ehWcRjlh1D968/t9+mrlNIksUk4RkN3Q9auN+Xv/6yuHDxWpwolQ4YsiJ0T1Lmc88rohNNSTHgOc3w5MBFSiRuxdErckFJrJ3SxAuGw4mXEpa5hmEu7uHghwOL6JpH55Rm0pAImhCJ9RcnlhcVWxrdQ5cS8XrOv0U8zUGxZwmTH5rUddJo/nLMuCD7BHm/+yMSVdz6pkefskjwgh/kAHSeWWg/55k388C0XMQMGSjZHUEPofs0mu983/WWCy3Q34xeitZ/R5L9D5rQSNIY++Q6Sv89568K+ALTEJSFBihKEkn2Rlc0SUL3+RFb+K8Ogr8I4NUR2v+raFu9Y78LJ6YHck7kH/yyo+x4kgg7hgJKiHn8saZFhB1A4EEwVqwRT0CBXydlOEoBFCTv+nphsTzBG6uOzoXk6T+loXQzDg+lA66Vw+ymw6h0wKJKhyAY+KPhBOu5FWhS+uFa+U0GwXTsj7/yhJA6J1wbz+l4PJpMH2/HRBVpT1wrT5D01fCMinqD1kSS5ULwi4PKQLJFTlSdBXOQXN8FyK+wTwoMIGoZ+1PgYLwtRwvvDbsdlZBVCdEHTjute5ZNz7LtWZ57ll3b4mH1dQpQ6P4UFDh0sUV1BoGVgUFAxRox7CLWXyI2BgE9P+PY9hF+N8rzl4hdfx0NR0chpHu/QgqMomov3rfWtioxKLw6wakl0hWIbwm5uYKZdWm2JQRVX0caW0JdgUy6kJ2BQK+a5LFGdJRSLe3uclBgKDXT7UYVyMpYgtqwNga0nua4WcvflCyT2mlbsbScrIR069U9sOFtMtJ1t2utpZOievHenaICqyahSt0+BOaZ307uwToElXuTiYXYlCTtcrMg2xLS0tC3us7zFYgtRx8SNMexIxDeDvcLpMCh+6AFsjuJeYbfepN8C7Ku3D8XyCD8WwKVkHYcu0auQGyBDKRdaMEn7cTHC+B+hTS6ksja21X50Edzr0WyD1GFaZevb5+jCtTu7PZBKjHtFH27sK5hrNa2u4IJmo3tnCU1m/wPqRS6ksp+s2oqtJv227cnlSG4hdlUPJ13oBVsW4JuS2WCdTBW+1aTpx4Z2g2wWbSRyoyNZipJqTjq8bJwIn5WY2CA0qqt5Ug7mT/pgdO2Y9Qd6UG0oVkucnKkvxFxZFnhJPSgJ0tUkzCj53CgKovnat5Us9aeSwyS1d0J3xUUjX04QJc7cC6rG8ylivcHlefc4YJhYtWfCqGbcyEFYdJ1TrD/x/EgWecsdGfYOHwQSRa1DGLOMGOLl1gPTF798bP8CQAA//8DAFBLAwQUAAYACAAAACEAAUB6gFEBAAB1AgAAEQAIAWRvY1Byb3BzL2NvcmUueG1sIKIEASigAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfJJRT8IwFIXfTfwPS9+3rgwRmzESNTxJYsKMxrfaXqBh7Zq2CPx7uwFzBuJje879es5N8+leVdE3WCdrPUEkSVEEmtdC6tUEvZWzeIwi55kWrKo1TNABHJoWtzc5N5TXFl5tbcB6CS4KJO0oNxO09t5QjB1fg2IuCQ4dxGVtFfPhaFfYML5hK8CDNB1hBZ4J5hlugLHpiOiEFLxDmq2tWoDgGCpQoL3DJCH41+vBKnd1oFV6TiX9wYROp7h9tuBHsXPvneyMu90u2WVtjJCf4I/5y6KtGkvd7IoDKnLBKbfAfG2LBVNf0kebrWI2MsxbluOe3KyyYs7Pw9aXEsTj4erEpSu80VY6PgQiCiHpsdJZec+enssZKgYpGcVkEJNhSQZ0SGg6/mxC/JlvQh8v1CnK/8T7OCUxeShJSu9GNMt6xDOgyPHFRyl+AAAA//8DAFBLAwQUAAYACAAAACEAfrJ7IkIEAADoEgAAJwAAAHhsL3ByaW50ZXJTZXR0aW5ncy9wcmludGVyU2V0dGluZ3MxLmJpbophiGFIYshhKGJIZShkqGAoALLyGAwYDIHiTgw+DEEMrgyBDBEMIUARNwZjhgCgiB9YnoHBjIGBgZGFQeEOA48Q/38GJkYGRoZXXPkcKUCanSECyGcCkkxAVbQBjEBjwaYDCRAbHRgauAR9eHxWpkGz1gkkd5yLgaFiztyw09Num/H8FV+/WULd93LKbP6M7bEvHVRUCn0EbWoqHj15cp87YMvXDWu33D0TcUC14KPCAfn2FWxXbi76dnDHFZ4phx+efvj0w7sJTCsmX/xzVP7Fgq0rTkR5nFjUdOzArN7GiztiBJKceB9IPl14/IaLz8kddZuusxZ5XpQ8Kl16l61GeNuZRN7uaz8sFJbNbunc9UJxQfD0Yxbzhb311rTN8ahzS3mV+bS0dXL98aUv229JTz2eLmzB6+i/8d2miI+ygdIe4vGTsg5amAkHxIqkfzn3Rv+A8eGg7olbGHQPSUZzv9vC9VHTZaWnubibafh8xvMvJikuPxFvo7Ju+m65ygVZB76G8dVV3HR2/RzrML/MUVbUS4Dnhm3MofyVl84KNh87t2PS3OXqJrsyJk3JMokyvt8k6jLf8quvQ/8Gr+Nrfpcz9gn4T52sXP48Qfj/mh39ldfeHrVcrMX8xurd3og/7YX2jdUPu4Kv1fz9vH7/dq3T73auvvrYw6aw66agtui1kx+nHPyvUOv+pvPpf6fHekretv7mftuaBWe3n9SPN/l0NSYphWXDp6m3tDYs8StNuubsZm+2wvPpxJ0bA/TK3MM2/7fZVjH38mvGmtXSr+SET97fKrNnu1sjy++5GTbNZx99/fFp0xez0iVHQ4qnJC+62HxcYZWatbJYb/Bh5rezL2ZmHJso+L5f3t7eIaLkL++nl5/z/XtF+OP56mtEyhofh3Scc3rRGpcetEgo9uHvK76mRXOS9vRXmR1cKqQnwqPOmLW8gstmU2+IloB34P1JJ0Wfyhdu7Cn5JrZ/qtDuuE35G71zGvaYCMRocH/T+BwTMc312OTZL1KmhzHtKW20XqH6rrXyp67Yjcs/dZetX/5qy8pns5d22bR7eJfVWF4qkp+89MK+j+bRYepxpvnNnhGvBE4+mzX3vjfXWQ1G1iCJx1uerJ/dPbWWwV5CVPu28f0srtR4w9TWBfE78y6VFfEIR3i/zb47Vcaho2BeTtx9ltnJvq2fzaYZBnCfSnsinLTQ/vv+rTbTZcJNllU1u1xhu8zfqV+z5pD2y+reiLuzNDZZL5HXuNj3snci5z5BL/7CZxq8r2UszPdOvba9Wvl+fUfOwf0VcScm8Ahelz2Z+i2V2W++nX/52m3a/qtd7SuD48qOr9z5Lm5SjWnaptAzzQKbRDcVPrALnWk833lG1dNVf37y73v2MIxmuX7U4NEQGA2B0RAYDYHREBgNgdEQGA2B0RAYDYHREBgNgdEQwB0CAAAAAP//AwBQSwMEFAAGAAgAAAAhAPRCZ8iQAQAAGAMAABAACAFkb2NQcm9wcy9hcHAueG1sIKIEASigAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJJNb9swDIbvA/ofDN0bOd0HhkBWMaQdOqDDAiRtz6pMx0JlyRBZI+6vH20jqbPttBs/Xrx6SFFdHxqfdZDQxVCI5SIXGQQbSxf2hXjYfb/8KjIkE0rjY4BC9IDiWl98UJsUW0jkADO2CFiImqhdSYm2hsbggtuBO1VMjSFO017GqnIWbqJ9bSCQvMrzLxIOBKGE8rI9GYrJcdXR/5qW0Q58+LjrWwbW6lvbemcN8ZT6p7MpYqwouz1Y8ErOm4rptmBfk6Ne50rOU7W1xsOajXVlPIKS7wV1B2ZY2sa4hFp1tOrAUkwZujde25XIng3CgFOIziRnAjHWIJuSMfYtUtJPMb1gDUCoJAum4hjOtfPYfdLLUcDBuXAwmEC4cY64c+QBf1Ubk+gfxMs58cgw8U4424FvenPON47ML/3hvY5Na0Kvf4QqYs9jHQvq3oUXfGh38cYQHLd6XlTb2iQo+SNOWz8V1B0vNPnBZF2bsIfyqPm7MdzA43Toevl5kX/M+XtnNSXfT1r/BgAA//8DAFBLAQItABQABgAIAAAAIQBBN4LPbgEAAAQFAAATAAAAAAAAAAAAAAAAAAAAAABbQ29udGVudF9UeXBlc10ueG1sUEsBAi0AFAAGAAgAAAAhALVVMCP0AAAATAIAAAsAAAAAAAAAAAAAAAAApwMAAF9yZWxzLy5yZWxzUEsBAi0AFAAGAAgAAAAhAIE+lJfzAAAAugIAABoAAAAAAAAAAAAAAAAAzAYAAHhsL19yZWxzL3dvcmtib29rLnhtbC5yZWxzUEsBAi0AFAAGAAgAAAAhAMAAUPgvAgAAggQAAA8AAAAAAAAAAAAAAAAA/wgAAHhsL3dvcmtib29rLnhtbFBLAQItABQABgAIAAAAIQDQUPfTaQEAAJcDAAAUAAAAAAAAAAAAAAAAAFsLAAB4bC9zaGFyZWRTdHJpbmdzLnhtbFBLAQItABQABgAIAAAAIQA7bTJLwQAAAEIBAAAjAAAAAAAAAAAAAAAAAPYMAAB4bC93b3Jrc2hlZXRzL19yZWxzL3NoZWV0MS54bWwucmVsc1BLAQItABQABgAIAAAAIQCLgm5YkwYAAI4aAAATAAAAAAAAAAAAAAAAAPgNAAB4bC90aGVtZS90aGVtZTEueG1sUEsBAi0AFAAGAAgAAAAhAA+p7+sQBAAAlRUAAA0AAAAAAAAAAAAAAAAAvBQAAHhsL3N0eWxlcy54bWxQSwECLQAUAAYACAAAACEAf/w/hxEEAABBDQAAGAAAAAAAAAAAAAAAAAD3GAAAeGwvd29ya3NoZWV0cy9zaGVldDEueG1sUEsBAi0AFAAGAAgAAAAhAAFAeoBRAQAAdQIAABEAAAAAAAAAAAAAAAAAPh0AAGRvY1Byb3BzL2NvcmUueG1sUEsBAi0AFAAGAAgAAAAhAH6yeyJCBAAA6BIAACcAAAAAAAAAAAAAAAAAxh8AAHhsL3ByaW50ZXJTZXR0aW5ncy9wcmludGVyU2V0dGluZ3MxLmJpblBLAQItABQABgAIAAAAIQD0QmfIkAEAABgDAAAQAAAAAAAAAAAAAAAAAE0kAABkb2NQcm9wcy9hcHAueG1sUEsFBgAAAAAMAAwAJgMAABMnAAAAAA=="} - models.DecisionTable.parseExcel(decisionTableExcel, bootstrap.defaultContext, function (err, res) { - if(err){ - done(err); - }else{ - expect(res.hitPolicy).to.equal('O'); - expect(res.outputs.length).to.equal(3); - done(); - } - }); - }); - - it('Should fail to create decision table as decision data is not correct', function (done) { - var decisionTableData = { - "name": "sample", - "document": { - "documentName": "sample.xlsx", - "documentData": "base64" - } - } - models.DecisionTable.create(decisionTableData, bootstrap.defaultContext, function (err, res) { - // console.log(err); - expect(err).not.to.be.undefined; - expect(err.message).to.equal('Decision table data provided could not be parsed, please provide proper data'); - done(); - }); - }); - - it('Should fail to execute decision table as decision table data is not proper', function (done) { - models.DecisionTable.exec("invalidTableName", {}, bootstrap.defaultContext, function (err, res) { - expect(err).not.to.be.undefined; - expect(err.message).to.equal('No Document found for DocumentName invalidTableName'); - done(); - }); - }); - -}); \ No newline at end of file diff --git a/test/delete-test.js b/test/delete-test.js deleted file mode 100644 index 1e64c55..0000000 --- a/test/delete-test.js +++ /dev/null @@ -1,403 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var chalk = require('chalk'); -var loopback = require('loopback'); -describe(chalk.blue('Delete functionality test - Programmatically'), function () { - this.timeout(90000); - var testModel; - var testModel2; - var testModel3; - var testModel4; - - var modelName = 'TestDeleteModel'; - var TestModel = { - 'name': modelName, - 'base': 'BaseEntity', - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - } - }; - //without soft delete - var modelName2 = 'TestModelWithOutSoftDelete'; - var TestModel2 = { - 'name': modelName2, - 'base': 'BaseEntity', - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - }, - 'mixins': { 'SoftDeleteMixin': false } - }; - - //without version - var modelName3 = 'TestModelWithOutVersion'; - var TestModel3 = { - 'name': modelName3, - 'base': 'BaseEntity', - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - }, - 'mixins': { 'VersionMixin': true } - }; - //without version and soft delete - var modelName4 = 'TestModelWithOutBoth'; - var TestModel4 = { - 'name': modelName4, - 'base': 'BaseEntity', - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - }, - 'mixins': { - 'SoftDeleteMixin': false, - 'VersionMixin': true - } - }; - - before('create test model', function (done) { - models.ModelDefinition.create([TestModel, TestModel2, TestModel3, TestModel4], bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - testModel = loopback.getModel(modelName, bootstrap.defaultContext); - testModel2 = loopback.getModel(modelName2, bootstrap.defaultContext); - testModel3 = loopback.getModel(modelName3, bootstrap.defaultContext); - testModel4 = loopback.getModel(modelName4, bootstrap.defaultContext); - done(); - } - - }); - }); - - after('delete model clear in memory', function (done) { - this.timeout(10000); - models.ModelDefinition.destroyAll({ name: { inq: [TestModel, TestModel2, TestModel3, TestModel4] } }, bootstrap.defaultContext, function (err, res) {// to cover fallback code. - done(); - }); - }); - - it('Should create TestModel with SoftDeleteMixins SET ', function (done) { - // with both version and soft delete - expect(testModel).not.to.be.null; - expect(testModel.definition.properties).not.to.be.undefined; - expect(Object.keys(testModel.definition.properties)).to.include.members(Object.keys(TestModel.properties)); - expect(Object.keys(testModel.definition.properties)).to.include.members(['_isDeleted']); - expect(Object.keys(testModel.definition.properties)).to.include.members(['_version']); - //with version - expect(testModel2).not.to.be.null; - expect(testModel2.definition.properties).not.to.be.undefined; - expect(Object.keys(testModel2.definition.properties)).to.include.members(Object.keys(TestModel2.properties)); - expect(Object.keys(testModel2.definition.properties)).to.not.have.members(['_isDeleted']); - expect(Object.keys(testModel2.definition.properties)).to.include.members(['_version']); - //with softDelete - expect(testModel3).not.to.be.null; - expect(testModel3.definition.properties).not.to.be.undefined; - expect(Object.keys(testModel3.definition.properties)).to.include.members(Object.keys(TestModel3.properties)); - expect(Object.keys(testModel3.definition.properties)).to.include.members(['_isDeleted']); - expect(Object.keys(testModel3.definition.properties)).to.not.have.members(['_version']); - //without both - expect(testModel4).not.to.be.null; - expect(testModel4.definition.properties).not.to.be.undefined; - expect(Object.keys(testModel4.definition.properties)).to.include.members(Object.keys(TestModel4.properties)); - expect(Object.keys(testModel4.definition.properties)).to.not.have.members(['_isDeleted']); - expect(Object.keys(testModel4.definition.properties)).to.not.have.members(['_version']); - - done(); - }); - - it('Should create a record in TestModel and delete the same with version number - deleteByID ', function (done) { - var postData = { - 'name': 'TestCaseOne' - }; - testModel.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - it('Should create a record in TestModel and delete the same - deleteByID', function (done) { - var postData = { - 'name': 'TestCaseTwo' - }; - testModel.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - done(); - } - }); - } - }); - }); - - it('Should create a record in TestModelWithOutSoftDelete and delete the - deleteByID ', function (done) { - var postData = { - 'name': 'TestCaseThree' - }; - testModel2.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel2.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutSoftDelete and delete the same (should fail) - deleteByID', function (done) { - var postData = { - 'name': 'TestCaseFour' - }; - testModel2.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel2.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - done(); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutSoftDelete and delete the same deleteByID', function (done) { - var postData = { - 'name': 'TestCaseFour2' - }; - testModel2.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel2.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - done(); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutVersion and delete the same, without version number (should pass) - deleteByID', function (done) { - var postData = { - 'name': 'TestCaseFive' - }; - testModel3.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel3.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutVersion and delete the same, without version number (should pass) - deleteByID', function (done) { - var postData = { - 'name': 'TestCaseFive2' - }; - testModel3.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel3.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - - it('Should create a record in TestModelWithOutBoth and delete the same, without version number (should pass) - deleteByID', function (done) { - var postData = { - 'name': 'TestCaseSix' - }; - testModel4.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel4.destroyById(res.id, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - it('Should create a record in TestModel and delete the same with where clause - destroyAll', function (done) { - var postData = { - 'name': 'TestCaseSeven' - }; - testModel.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel.destroyAll({ id: res.id }, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - testModel.findById(res.id, bootstrap.defaultContext, function (err3, res3) { - if (err3) { - done(err3); - } else if (res3) { - done(new Error('Record not deleted')); - } else { - done(); - } - }); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutSoftDelete and delete the same, _isDeleted filed should be not present - destroyAll', function (done) { - var postData = { - 'name': 'TestCaseEight' - }; - testModel2.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel2.destroyAll({ id: res.id }, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - it('Should create a record in TestModelWithOutVersion and delete the same, without version number (should pass) - destroyAll', function (done) { - var postData = { - 'name': 'TestCaseNine' - }; - testModel3.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - testModel3.destroyAll({ id: res.id }, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - - it('Should create a record in TestModelWithOutBoth and delete the same - destroyAll', function (done) { - var postData = { - 'name': 'TestCaseTen' - }; - testModel4.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.undefined; - testModel4.destroyAll({ id: res.id }, bootstrap.defaultContext, function (err2, res2) { - if (err2) { - done(err2); - } else { - expect(res2.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - -}); diff --git a/test/embedded-many-test.js b/test/embedded-many-test.js deleted file mode 100644 index 9ce2ad8..0000000 --- a/test/embedded-many-test.js +++ /dev/null @@ -1,187 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var chalk = require('chalk'); -var api = bootstrap.api; -var app = bootstrap.app; -var loopback = require('loopback'); - -describe(chalk.blue('Embed many test'), function () { - - var accountModel; - var customerModel; - var evfUserModel; - - this.timeout(200000); - var model1schema = { - 'name': { - 'type': 'string' - }, - balance: { - type: 'number', - min: 1000, - max: 50000 - } - }; - var opts = { - 'name': 'Account', - 'idInjection': false, - 'base': 'BaseType' - }; - - var model2 = { - 'name': 'CustomerEmbed', - 'idInjection': false, - 'base': 'BaseEntity', - 'properties': { - 'name': { - 'type': 'string' - } - }, options: { validateUpsert: true }, - 'relations': { - 'accounts': { - 'type': 'embedsMany', - 'model': 'Account', - 'property': 'AccountList', - 'options': { - 'validate': false, - 'forceId': false, - 'persistent': true - } - } - }, - 'mixins': { - ModelValidations: true - } - - }; - - var model3 = { - name: 'EVFUser', - properties: { - name: { - type: 'string', - require: true - }, - accountList: ['Account'] - } - }; - - before('create models with embed many relations', function (done) { - var dataSource = app.dataSources['db']; - var myModel = dataSource.createModel('Account', model1schema, opts); - myModel.attachTo(dataSource); - app.model(myModel); - - models.ModelDefinition.create([model2, model3], bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - accountModel = loopback.getModel('Account', bootstrap.defaultContext); - customerModel = loopback.getModel('CustomerEmbed', bootstrap.defaultContext); - evfUserModel = loopback.getModel('EVFUser', bootstrap.defaultContext); - done(); - } - }); - }); - - after('clean up-', function (done) { - customerModel.destroyAll({}, bootstrap.defaultContext, function (err, res) { - console.log(err, '\n', res); - models.ModelDefinition.destroyAll({ name: 'CustomerEmbed' }, bootstrap.defaultContext, function (err, res) { - done(err); - }); - }); - }); - - it('should create customer record and account --- rest', function (done) { - var data = { 'name': 'Shreyas' }; - var accData = { 'name': 'saving', 'id': '1231234123', 'balance': 1500 }; - customerModel.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - var id = res.id; - var version = res._version; - var login = { 'username': 'testuser', 'password': 'testuser123' }; - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(login) - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - accData._parentVersion = version; - api - .post(bootstrap.basePath + '/CustomerEmbeds/' + id + '/accounts?access_token=' + res.body.id) - .send(accData) - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - expect(res.body).not.to.be.undefined; - done(); - } - }); - } - }); - } - }); - }); - - it('should create a customer and account (with parent version in child data) -- programmatically', function (done) { - var data = { 'name': 'helloThere' }; - customerModel.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - var accData = { 'name': 'adas', 'id': '21', '_parentVersion': res._version, 'balance': 1500 }; - res.accounts.create(accData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - done(); - } - }); - } - }); - }); - - it('should create a evfUser with account details -- programmatically', function (done) { - var data = { 'name': 'helloThere', accountList: [{ name: 'saving', 'balance': 1500 }] }; - customerModel.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - customerModel.findById(res.id, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - done(); - } - }); - } - }); - }); - - xit('should not allow create a evfUser with account balance less then min -- programmatically', function (done) { - var data = { 'name': 'helloThere', accountList: [{ name: 'saving', 'balance': 500 }] }; - customerModel.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - expect(err.message).not.to.be.null; - done(); - } else { - done(new Error('validation did not work')); - } - }); - }); -}); \ No newline at end of file diff --git a/test/enum-test.js b/test/enum-test.js deleted file mode 100644 index 904a37f..0000000 --- a/test/enum-test.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var loopback = require('loopback'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -//var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -//var supertest = require('supertest'); -//var api = supertest(app); -//var debug = require('debug')('enum-test'); - -describe(chalk.green('Enum Test'), function () { - var enumName = 'MyTestEnum'; - var enumConfig = { - 'name': enumName, - 'base': 'EnumBase', - 'strict': true, - 'properties': {}, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'enumList': [ - { - code: 'M', - description: 'Monthly' - }, - { - code: 'S', - description: 'Semi' - }, - { - code: 'A', - description: 'Annual' - }, - { - code: 'Qu', - description: 'Quarterly' - } - ] - }; - var parentModelName = 'MyTestModel'; - var parentModelConfig = { - 'name': parentModelName, - 'base': 'BaseEntity', - 'strict': true, - 'properties': { - 'code': { - 'type': 'string', - 'enumtype': 'MyTestEnum', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - - before('create enum MyTestEnum', function (done) { - loopback.createModel(enumConfig); - models.ModelDefinition.create(parentModelConfig, bootstrap.defaultContext, function (err, model) { - done(); - }); - }); - - it('should be valid if code is exact match', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.isValidEnum('S')).to.be.equal(true); - done(); - }); - it('should be valid if code is different case', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.isValidEnum('s')).to.be.equal(true); - done(); - }); - it('should be invalid if code is not valid', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.isValidEnum('Y')).to.be.equal(false); - done(); - }); - it('should be invalid if code is partial match', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.isValidEnum('Q')).to.be.equal(false); - done(); - }); - it('should return correct description for given code', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.toDescription('S')).to.be.equal('Semi'); - done(); - }); - it('should return correct description for code in different case', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.toDescription('qu')).to.be.equal('Quarterly'); - done(); - }); - it('should return undefined for incorrect code', function (done) { - var myenum = loopback.findModel(enumName); - expect(myenum.toDescription('y')).to.be.undefined; - done(); - }); - it('should should return invalid model for invalid enum', function (done) { - var mymodel = loopback.findModel(parentModelName, bootstrap.defaultContext); - var mymodeldata = { - code: 'KKK' - }; - var context = { - options: bootstrap.defaultContext - }; - var mymodelobj = new mymodel(mymodeldata); - mymodelobj.isValid(function (ret) { - expect(ret).to.be.equal(false); - done(); - }, context); - }); - it('should should return valid model for valid enum', function (done) { - var mymodel = loopback.findModel(parentModelName, bootstrap.defaultContext); - var mymodeldata = { - code: 'Qu' - }; - var context = { - options: bootstrap.defaultContext - }; - var mymodelobj = new mymodel(mymodeldata); - mymodelobj.isValid(function (ret) { - expect(ret).to.be.equal(true); - done(); - }, context); - }); -}); diff --git a/test/fail-test.js b/test/fail-test.js deleted file mode 100644 index c2c8526..0000000 --- a/test/fail-test.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('basic-test'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var loopback = require('loopback'); - -describe(chalk.blue('fail-test'), function () { - this.timeout(20000); - var testModelName = 'FailedVehicle'; - var testModelDetails = { - name: testModelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - } - }; - - var accessToken; - - - - var ModelDefinition = bootstrap.models.ModelDefinition; - - before('create model', function (done) { - async.series([ - function createModel(cb) { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - if (model) { - cb(); - } else { - ModelDefinition.create(testModelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', err); - cb(); - } else { - cb(); - } - }); - } - }, - function alldone() { - done(); - } - ]); - }); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - bootstrap.api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - it('upsert test record1', function (done) { - var data = { - "name": "Record1", - "description": "create" - }; - var context = bootstrap.defaultContext; - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - model.create(data, context, function (err, res) { - model.find({ - "where": { - "name": "Record1" - } - }, bootstrap.defaultContext, function (err, res) { - expect(res[0].description).to.be.equal("create"); - model.findById(res[0].id, context, function (err, rec) { - rec.description = 'upsert'; - model.upsert(rec, context, function (err, rec) { - var data = { - description: 'updateAttributes' - }; - rec.updateAttributes(data, context, function (err, rec) { - expect(err).to.not.exist; - expect(rec.description).to.be.equal('updateAttributes'); - done(); - }); - }); - }); - }); - }); - }); - - it('double upsert record2', function (done) { - var data = { - "name": "Record2", - "description": "create" - }; - var context = bootstrap.defaultContext; - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - model.upsert(data, context, function (err, res) { - expect(err).to.not.exist; - res.description = 'update via upsert'; - model.upsert(res, context, function (err, rec) { - expect(err).to.not.exist; - // test case failing - // expect(rec.description).to.be.equal('update via upsert'); - done(); - }); - }); - }); - - it('Should fail on REST put without _version', function () { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - var createData = { - "name": "Record3", - "description": "create" - }; - model.create(createData, bootstrap.defaultContext, function (err, res) { - bootstrap.api - .set('Accept', 'application/json') - .put(bootstrap.basePath + '/FailedVehicles/' + res.id + '?access_token=' + accessToken) - .send({ description: "updateAttributes", }) - .end(function (err, res) { - // test case failing - //expect(res.status).to.not.be.equal(200); - }); - }); - }); - - after('after clean up', function (done) { - var model = loopback.findModel(testModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - done(err); - } else { - ModelDefinition.destroyAll({ - "name": testModelName - }, bootstrap.defaultContext, function () { - done(); - }); - } - }); - }); - -}); diff --git a/test/failSafe-tests/cleanupIntegrationTest.js b/test/failSafe-tests/cleanupIntegrationTest.js deleted file mode 100644 index c9a3b55..0000000 --- a/test/failSafe-tests/cleanupIntegrationTest.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var fs = require('fs'); - -fs.unlink('common/models/framework/note.js',function(err){ - if(err) { - return process.exit(-1); - } -}); - -fs.unlink('common/models/framework/note.json',function(err){ - if(err) { - return process.exit(-1); - } -}); - -fs.writeFileSync('server/model-config.json', fs.readFileSync('server/model-config.orig')); - -fs.unlink('server/model-config.orig',function(err){ - if(err) { - return process.exit(-1); - } -}); \ No newline at end of file diff --git a/test/failSafe-tests/failsafe.yml b/test/failSafe-tests/failsafe.yml deleted file mode 100644 index f802adf..0000000 --- a/test/failSafe-tests/failsafe.yml +++ /dev/null @@ -1,66 +0,0 @@ -version: "3" -services: - mongo: - image: ${REGISTRY}/alpine-mongo:latest - networks: - - ${NETWORK_NAME} - - broadcaster: - image: ${REGISTRY}/evf-broadcaster-server:latest - environment: - SERVICE_PORTS: "2345" - networks: - - ${NETWORK_NAME} - - routerservice: - image: ${REGISTRY}/evfpaas-router-service:latest - environment: - HASH_RING_REFRESH_TIME_INTERVAL: "10000" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - SERVICE_PORTS: "3000" - BROADCASTER_HOST : "broadcaster" - ROUTER_HOST: ${ROUTER} - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - - web: - image: $REGISTRY/${ORIG_APP_IMAGE_NAME}:failsafe-test - depends_on: - - mongo - - broadcaster - deploy: - mode: replicated - replicas: 4 - update_config: - delay: 60s - environment: - VIRTUAL_HOST: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME},${APP_IMAGE_NAME}.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - CONSISTENT_HASH: "true" - SERVICE_NAME: "${APP_IMAGE_NAME}" - BROADCASTER_HOST: "broadcaster" - ORCHESTRATOR: "dockerSwarm" - APP_URL: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/api" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - ROUTER_HOST: ${ROUTER} - ENABLE_EVENT_HISTORY: "true" - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - healthcheck: - test: "wget -s http://localhost:3000" - interval: 1m - timeout: 3s - retries: 10 - - -networks: - $NETWORK_NAME: - router_network: - external: true diff --git a/test/failSafe-tests/integrationTest.js b/test/failSafe-tests/integrationTest.js deleted file mode 100644 index a403f64..0000000 --- a/test/failSafe-tests/integrationTest.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var request = require('request'); -var async = require('async'); -var chai = require('chai'); -var expect = chai.expect; -var chalk = require('chalk'); -var exec = require('child_process').exec; - -const APP_IMAGE_NAME = process.env.APP_IMAGE_NAME; -const DOMAIN_NAME = process.env.DOMAIN_NAME; -const SERVICE_NAME = APP_IMAGE_NAME + '_web'; - -var baseurl = 'https://' + APP_IMAGE_NAME + '.' + DOMAIN_NAME + '/api/'; - -var modelPlural = 'Notes/'; -var eventHistoryPlural = 'EventsHistroy/'; -var headers = {'tenantId': 'default', 'Accept': 'application/json'}; -var eventHistoryRecords; -var results; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - -var token; - -describe(chalk.blue('Failsafe - integrationTest'), function () { - before('login using admin', function fnLogin(done) { - var loginData = {'username': 'admin', 'password': 'admin'}; - console.log('Base Url is ', baseurl); - request.post( - baseurl + 'BaseUsers/login', { - json: loginData - }, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - done(error || body.error); - } else { - expect(response.statusCode).to.equal(200); - console.log('login using admin - success - ' + body.id); - token = body.id; - eventHistoryRecords = { - url: baseurl + eventHistoryPlural + '?access_token=' + token, - strictSSL: false, json: true, 'headers': headers - }; - done(); - } - }); - }); - - var scaleTo = (n, cb) => { - exec('docker service scale ' + SERVICE_NAME + '=' + n, (err, stdout) => { - if (err) console.log('Error in func One: ' + err); - verifyScale(n, cb); - }); - }; - - var verifyScale = (n, cb) => { - exec('docker service ps ' + SERVICE_NAME + ' --format "{{json .CurrentState}}" | grep Running | wc -l', (err, stdout) => { - if (err) { - setTimeout(verifyScale, 100, n, cb); - } else { - var countStatus = parseInt(stdout, 10); - if (countStatus !== n) { - setTimeout(verifyScale, 5000, n, cb); - } else { - cb(); - } - } - }); - }; - - var getServiceStatus = (callback) => { - console.log("Step 5: get eventHistoryRecord status."); - initResults(); - request.get(eventHistoryRecords, function (error, response, records) { - if (error) { - console.log('Step 5: error accord while fetching eventHistoryRecords: ' + error); - return callback(error); - } - expect(response.statusCode).to.equal(200); - expect(records.length).to.not.equal(0); - var status; - records.forEach((eventHistoryRecord) => { - results[eventHistoryRecord.status]++; - }, this); - console.log(results); - if (results.RecoveryFinished < 2) { - setTimeout(getServiceStatus, 5*5000, callback); - } else { - console.log("Step 5: 2 dead hosts where recovered."); - return callback(); - } - }); - }; - - var initResults = () => { - results = {}; - results.undefined = 0; - results.RecoveryFinished = 0; - results.ToBeRecovered = 0; - results.InRecovery = 0; - }; - - it('Recover - Default sceanrio', function (done) { - async.series({ - scaleServiceTo5: (callback) => { - console.log('Step 2: scale service to 5.'); - scaleTo(5,callback); - }, - createModelInstances: (callback) => { - console.log('Step 3: creat model instaces.'); - var createUrl = baseurl + modelPlural + '?access_token=' + token; - async.times(100, (i, next) => { - request.post({url: createUrl, json: {}, headers: headers, method: 'POST'}, function (error, r, body) { - expect(r.statusCode).to.equal(200); - return next(null, body.id); - }); - }, (err, results) => { - if (err) { - return done(err); - } - return callback(); - }); - }, - scaleServiceCountDown: (callback) => { - console.log('Step 4: scale services down to 3'); - scaleTo(3, callback); - }, - getServiceStatus: getServiceStatus - }, (err) => { - console.log('Step 6: done.') - if (err) { - return done(err); - } - - if (results.RecoveryFinished === 2 ) { - return done(); - } - console.log('Test failed, please view the results below'); - console.log(results); - return done(new Error('Not All dead hosts were recoverd')); - }); - }); -}); diff --git a/test/failSafe-tests/prepareIntegrationTest.js b/test/failSafe-tests/prepareIntegrationTest.js deleted file mode 100644 index efd5c70..0000000 --- a/test/failSafe-tests/prepareIntegrationTest.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var fs = require('fs'); - -var notejs = 'module.exports = function (Model) {Model.prototype.remote2 = function (cb) {cb(null, {message: "remote 2 ok"});};Model.remoteMethod("remote2", {isStatic: false,description: "remote2",accessType: "READ",accepts: [],http: {verb: "GET",path: "/remote2"},returns: {type: "object",root: true}});Model.observe("after save", function (ctx, next) {var err = new Error("Note after save fail");return next(err);});};'; - -fs.writeFile("common/models/framework/note.js", notejs, function(err) { - if(err) { - return process.exit(-1); - } -}); - -var notejson = '{"name": "Note","base" : "BaseEntity","strict" : false,"properties": {"title": {"type": "string"},"content": {"type": "string"}}}'; - -fs.writeFile("common/models/framework/note.json", notejson, function(err) { - if(err) { - return process.exit(-1); - } -}); - -fs.writeFileSync('server/model-config.orig', fs.readFileSync('server/model-config.json')); - -fs.readFile('server/model-config.json', 'utf8', function(oErr, sText) { - //console.log(sText); - var r1 = sText.substr(sText.length - 4, sText.length - 1); - var re = new RegExp(r1,"g"); - var result = sText.replace(r1, '},"Note": {"public": true,"dataSource": "db"}}'); - fs.writeFile('server/model-config.json', result, function (err) { - if(err) { - return process.exit(-1); - } - }); -}); diff --git a/test/failsafe-observer-test.js b/test/failsafe-observer-test.js deleted file mode 100644 index cb20316..0000000 --- a/test/failsafe-observer-test.js +++ /dev/null @@ -1,550 +0,0 @@ - - -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ - -/** - * - *@author Lior Schindler - */ - -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var chai = bootstrap.chai; -var expect = chai.expect; -var async = require('async'); -var models = bootstrap.models; -var eventHistoryManager = require('../lib/event-history-manager'); -var debug = require('debug')('failsafe-observer-test'); -var uuidv4 = require('uuid/v4'); -var loopback = require('loopback'); -var os = require('os'); -var currHostName = os.hostname(); - -chai.use(require('chai-datetime')); - -describe('failsafe-observer-mixin', function () { - this.timeout(90000); - var modelName = 'FailSafeTestModel'; - var childModelName = 'FailSafeChildTestModel'; - - var TestModelSchema = { - name: modelName, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - base: 'BaseEntity', - }; - - var TestChildModelSchema = { - name: childModelName, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - base: modelName - }; - - var defaultContext = { ctx: {} }; - defaultContext.ctx.tenantId = 'testTenant'; - defaultContext.ctx.remoteUser = 'insertUser'; - var backupConstants = {}; - var k = 0; - - before('change event history manager constants', function (done) { - backupConstants.eventReliabilityReplayThreshold = app.get('eventReliabilityReplayThreshold'); - backupConstants.eventReliabilityReplayInterval = app.get('eventReliabilityReplayInterval'); - backupConstants.eventReliabilityMaxRetryInterval = app.get('eventReliabilityMaxRetryInterval'); - app.set('eventReliabilityReplayThreshold', 100); - app.set('eventReliabilityReplayInterval', 1000); - app.set('eventReliabilityMaxRetryInterval', 4000); - // backupConstants.eventReliabilityDbPersistenceInterval = app.get('eventReliabilityDbPersistenceInterval'); - // app.set('eventReliabilityDbPersistenceInterval', 2000); - // app.set('eventReliabilityMaxRetry', 4); - eventHistoryManager.config(app); - done(); - }); - after('restore event history manager constants', function (done) { - app.set('eventReliabilityReplayThreshold', backupConstants.eventReliabilityReplayThreshold); - app.set('eventReliabilityReplayInterval', backupConstants.eventReliabilityReplayInterval); - app.set('eventReliabilityMaxRetryInterval', backupConstants.eventReliabilityMaxRetryInterval); - // app.set('eventReliabilityDbPersistenceInterval', backupConstants.eventReliabilityDbPersistenceInterval); - eventHistoryManager.config(app); - done(); - }); - - function createModelDefinition(modelSchema, cb) { - models.ModelDefinition.create(modelSchema, defaultContext, function (err, res) { - if (err) { - console.log('error in create test model', err); - cb(err); - } else { - modelSchema._version = undefined; - modelSchema._oldVersion = undefined; - cb(); - } - }); - } - beforeEach('create test models', function (done) { - k++; - modelName = modelName + k; - childModelName = childModelName + k; - TestModelSchema.name = TestModelSchema.name + k; - TestChildModelSchema.base = TestModelSchema.name; - TestChildModelSchema.name = TestChildModelSchema.name + k; - createModelDefinition(TestModelSchema, function (err) { - if (err) { - done(err); - } else { - createModelDefinition(TestChildModelSchema, function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - }); - - function deleteModelDef(modelName, cb) { - models.ModelDefinition.destroyAll({}, defaultContext, function (err, res) { - if (err) { - cb(err); - } else { - debug('number of record deleted -> ', res.count); - cb(); - } - }); - } - - function deleteModelInst(modelName, cb) { - var model = loopback.getModel(modelName, defaultContext); - model.destroyAll({}, defaultContext, function (err, info) { - if (err) { - cb(err); - } else { - cb(); - } - }); - } - - afterEach('delete model instances', function (done) { - return done(); - deleteModelInst(modelName, function (err) { - if (err) { - done(err); - } else { - deleteModelInst(childModelName, function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - }); - - after('delete model definitions', function (done) { - deleteModelDef(modelName, function (err) { - if (err) { - done(err); - } else { - deleteModelDef(childModelName, function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - }); - - it('Should -- make TestModel availabe in the app with failsafe observers fields on Model', function (done) { - var failSafeObserverFields = ['_fsObservers', 'failSafeObserve']; - var mixins = ['FailsafeObserverMixin']; - var model = loopback.getModel(modelName, defaultContext); - expect(model).to.be.ok; - expect(Object.keys(model.settings.mixins)).to.include.members(mixins); - expect(Object.keys(model)).to.include.members(failSafeObserverFields); - done(); - }); - - it('Should register an observer in _fsObservers object when adding new oberver ', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var test = false; - var observerLength = model._fsObservers['after save'].observers.length; - model.observe('after save', function testObserver(ctx, next) { - test = true; - next(); - }); - expect(model._fsObservers['after save'].observers.length).to.be.equal(observerLength + 1); - expect(model._fsObservers['after save'].observerIds.length).to.be.equal(observerLength + 1); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - expect(test).to.be.true; - done(); - }); - }); - - it('should not rerun an after save observer if it finished executing without error', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - if (counter <= 1) { - var c = counter; - setTimeout(function () { - if (c === 0) { - done(); - } - }, 5000); - next(); - } else { - done(new Error('observer called to many times')); - } - counter++; - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - it('should not rerun an after save observer if it finished executing without error for base model', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var childModel = loopback.getModel(childModelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - if (counter <= 1) { - setTimeout(function () { - if (counter === 1) { - done(); - } - }, 5000); - next(); - } else { - done(new Error('observer called to many times')); - } - counter++; - }); - childModel.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - it('should not rerun an after save observer when error is not retriable for base model', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var err = new Error('testError'); - var childModel = loopback.getModel(childModelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - err.retriable = false; - if (counter === 0) { - next(err); - done(); - } else { - next(); - } - counter++; - }); - childModel.create({ name: 'test', _version: uuidv4() }, defaultContext, function (error, res) { - expect(error).to.be.equal(err); - }); - }); - - it('should not rerun an after save observer when error is not retriable', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var err = new Error('testError'); - var counter = 0; - model.observe('after save', function (ctx, next) { - err.retriable = false; - if (counter == 0) { - next(err); - done(); - } else { - next(); - } - counter++; - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (error, res) { - expect(error).to.be.equal(err); - }); - }); - - it('should rerun an after save observer untill it doesn\'t return an error', function (done) { - // maya - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - if (counter < 2) { - counter++; - next(new Error('testError')); - } else if (counter === 2) { - counter++; - next(); - done(); - } else { - next(); - } - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - xit('recovery should end with RecoveryFinished in the db', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - if (counter < 2) { - counter++; - next(new Error('testError')); - } else if (counter === 2) { - counter++; - next(); - //done(); - } else { - next(); - } - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - return done(err); - } - }); - - setTimeout(function () { - var ev = loopback.getModel('EventHistory', defaultContext); - - ev.find({ where: { hostName: currHostName } }, defaultContext, function (err, results) { - if (err) { - done(err); - } else if (!results || results.length === 0) { - done(); - } else { - results[0].updateAttribute('status', 'ToBeRecovered', defaultContext, function (err) { - if (err) { - done(err); - } else { - eventHistoryManager.recovery(currHostName, function (err) { - if (err) { - done(err); - // TO DO - error handling here - } else { - ev.find({ where: { hostName: currHostName } }, defaultContext, function (err, results) { - if (err) { - done(err); - } else { - expect(results[0].status).to.be.equal('RecoveryFinished'); - } - }); - //done(); - } - }); - done(); - } - }); - } - });}, 5000); - }); - - it('should rerun an after save observer on base model untill it doesn\'t return an error', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - model.observe('after save', function (ctx, next) { - if (counter < 2) { - counter++; - next(new Error('testError' + counter)); - } else { - next(); - done(); - } - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - it('should not rerun an after save observer after RETRY_TIME_INTERVAL (set to 4000ms)', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - var startTime = Date.now(); - var RETRY_TIME_INTERVAL = app.get('eventReliabilityMaxRetryInterval'); - var REPLAY_TIME_INTERVAL = app.get('eventReliabilityReplayInterval'); - - model.observe('after save', function (ctx, next) { - if (counter === 0) { - counter ++; - setTimeout(()=> { - if (counter === 1){ - // next(); - done(); - } else { - // next(); - done(new Error('observer ran too many times')); - } - }, RETRY_TIME_INTERVAL * 2); - next(new Error('testError' + counter)); - } else if (Date.now() < startTime + app.get('eventReliabilityMaxRetryInterval')){ - next(new Error('testError' + counter)); - } else { - counter++; - } - }); - model.create({ name: 'test', _version: uuidv4() }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - - it('should create a log record for failed observer', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - var RETRY_TIME_INTERVAL = app.get('eventReliabilityMaxRetryInterval'); - var version = uuidv4(); - - model.observe('after save', function (ctx, next) { - if (counter === 0) { - counter ++; - setTimeout(()=> { - var LogModel = loopback.getModel("FailedObserverLog", defaultContext); - var query = { where: {'version': version}, fetchDeleted: true }; - LogModel.find(query, defaultContext, function (error, instance) { - if (error || !instance){ - return done(new Error('A log record was not created for observer failed execution.')); - } - return done(); - }); - }, RETRY_TIME_INTERVAL * 2); - next(new Error('testError' + counter)); - } else { - counter++; - next(new Error('testError' + counter)); - }; - }); - model.create({ name: 'test', _version: version }, defaultContext, function (err, res) { - if (err) { - done(err); - } - }); - }); - - it('should update a failed-observer-log record on observer success', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - var RETRY_TIME_INTERVAL = app.get('eventReliabilityMaxRetryInterval'); - var version = uuidv4(); - var LogModel = loopback.getModel("FailedObserverLog", defaultContext); - var instance; - - model.observe('after save', function (ctx, next) { - if (!ctx.fromRecovery) return next(); - setTimeout(function statusCheck(i) { - var query = { where: {'version': version}, fetchDeleted: true }; - LogModel.find(query, defaultContext, function (error, results) { - if (error || !results || results.length != 1){ - return done(new Error('A log record was not created for observer failed execution.')); - } - if (results[0].status != 'SUCCESS'){ - if (i < 5) - return setTimeout(statusCheck, RETRY_TIME_INTERVAL, i+1 ); - else - return done(new Error('The FailedObserverLog status wasn\'t changed.')); - } - return done(); - }); - }, RETRY_TIME_INTERVAL * 2, 0); - next(); - }); - - async.series([ - function(cb){ - model.create({ name: 'test', _version: version }, defaultContext, (err, res) => { - instance = res; - cb(err) - }); - }, - function(cb){ - var log = {}; - log.modelName = model.modelName; - log.version = version; - log.operation = 'after save'; - log.created = new Date(); - log.status = 'EXCEED_MAX_RETRY'; - LogModel.create(log, defaultContext, cb); - }, - function(cb){ - var ctx = {trigger: 'after save', fromRecovery: true, hasFailedObserverLog: true}; - instance.createEventHistory(ctx, defaultContext, cb); - } - ]) - - - }); - - it('should update _fsCtx', function (done) { - var model = loopback.getModel(modelName, defaultContext); - var counter = 0; - var RETRY_TIME_INTERVAL = app.get('eventReliabilityMaxRetryInterval'); - var version = uuidv4(); - var LogModel = loopback.getModel("FailedObserverLog", defaultContext); - var instance; - - var i=0; - model.observe('after save', function (ctx, next){ - var instance = ctx.instance || ctx.currentInstance; - var fsCtx = JSON.parse(instance._fsCtx); - expect(fsCtx.options).to.be.an('object'); - if (i === 0){ - expect(fsCtx.isNewInstance).to.be.true; - } else if (i === 1){ - expect(fsCtx.isNewInstance).to.be.false; - done(); - } else { - done(new Error('Observer ran too many times.')); - } - i++; - next(); - }); - - async.series([ - function(cb){ - model.create({ name: 'test', _version: version }, defaultContext, (err, res) => { - instance = res; - cb(err) - }); - }, - function(cb){ - instance.updateAttribute( 'name', 'updated', defaultContext, (err, res) => { - // instance = res; - oldVersion = res._oldVersion; - version = res._version; - cb(err) - }); - } - ]) - }); - -}); diff --git a/test/file-upload-test.js b/test/file-upload-test.js deleted file mode 100644 index 9575483..0000000 --- a/test/file-upload-test.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var loopback = require('loopback'); -var models = bootstrap.models; -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var fs = require('fs'); -var api = supertest(app); - - -// Test cases for testing the file upload functionality. -describe(chalk.blue('File upload test'), function() { - var accessToken; - this.timeout(5000); - before('login using admin', function fnLogin(done) { - var sendData = { - 'username': 'admin', - 'password': 'admin' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function(err, res) { - if (err) { - log.error(err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - after('delete uploaded file', function(done) { - fs.unlink('./test/x.png', function(err) { - done(); - }); - }); - - it('should upload file successfully', function(done) { - var filename = 'x.png'; - - var api = defaults(supertest(app)); - api.post(bootstrap.basePath + '/documents/test/upload?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .field('name', 'image upload') - .attach('image', './test/upload-file-data/' + filename) - .expect(200) - .end(function(err, res) { - if (err) { - done(err); - } else { - done() - } - }); - - }); - xit('should upload file fail for file size', function(done) { - - }); - - xit('should upload file fail for unsupported file type', function(done) { - - }); -}); \ No newline at end of file diff --git a/test/gridconfig-test.js b/test/gridconfig-test.js deleted file mode 100644 index 06fe86e..0000000 --- a/test/gridconfig-test.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var async = require('async'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var loopback = require('loopback'); - -var models = bootstrap.models; -var expect = bootstrap.chai.expect; -var api = bootstrap.api; - -chai.use(require('chai-things')); - -function create(model, items, callback) { - async.forEachOf(items, - function (item, m, callback2) { - model.create(item, bootstrap.defaultContext, function (a, b) { - callback2(); - }); - }, - function (err) { - if (err) { - return callback(err); - } - callback(); - }); -} - -function deleteAndCreate(model, items, callback) { - model.destroyAll({}, bootstrap.defaultContext, function () { - async.forEachOf(items, - function (item, m, callback2) { - model.create(item, bootstrap.defaultContext, function (e, rec) { - if (e) { - console.error(e.message); - } - callback2(); - }); - }, - function (err) { - if (err) { - return callback(err); - } - callback(); - }); - }); -} - - -describe(chalk.blue('grid-config'), function () { - - this.timeout(60000); - - var modelA = "GridOrder"; - - //definition of a model : Orders - var order = { - "name": "GridOrder", - "plural": "GridOrders", - "base": "BaseEntity", - "idInjection": false, - "properties": { - "category": { - "type": "string", - "required": true - }, - "price": { - "type": "number", - "required": true - }, - "description": { - "type": "string" - }, - "date": { - "type": "date" - } - } - }; - - var testGridConfig = { - "code": "PersonTable", - "label": "Users", - "editorFormUrl": "/components/person-form.html", - "columns": [ - { - "key": "firstName", - "label": "First Name", - "type": "string" - }, - { - "key": "middleName", - "label": "Middle Name", - "type": "string" - }, - { - "key": "lastName", - "label": "Last Name", - "type": "string" - }, - { - "key": "email", - "label": "E-Mail", - "type": "string" - } - ] - }; - - before('Define model and load data', function (done) { - create(models.ModelDefinition, [order], function (err) { - if (err) { - done(err); - } else { - deleteAndCreate(models.GridConfig, [testGridConfig], function (err1) { - done(err1); - }); - } - }); - }); - - after('destroy model', function (done) { - var gridOrder = loopback.getModel('GridOrder', bootstrap.defaultContext); - gridOrder.destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - console.error(err); - } - done(); - }); - }); - - it('should throw an error if configCode is not provided', function (done) { - api - .get(bootstrap.basePath + '/GridConfigs/config/') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var error = res.error; - expect(error).to.exist; - expect(error.status).to.be.equal(404); - done(); - }); - }); - - it('should return fetch/generate grid config if configCode is provided', function (done) { - api - .get(bootstrap.basePath + '/GridConfigs/config/' + modelA) - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response.label).to.exist; - expect(response.columns).to.exist; - expect(response.editorFormUrl).to.exist; - expect(response.columns.length).to.be.equal(4); - done(); - }); - }); - - it('should return grid config if it is defined', function (done) { - api - .get(bootstrap.basePath + '/GridConfigs/config/' + "PersonTable") - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response.label).to.exist; - expect(response.columns).to.exist; - expect(response.editorFormUrl).to.exist; - expect(response.columns.length).to.be.equal(4); - done(); - }); - }); - - it('should return error message if there is no GridConfig entry or a model defined for the provided configCode', function (done) { - api - .get(bootstrap.basePath + '/GridConfigs/config/' + 'unknownConfig') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var error = res.error; - expect(error).to.exist; - expect(error.status).to.be.equal(500); - done(); - }); - }); - -}); \ No newline at end of file diff --git a/test/gridmetadata-test.js b/test/gridmetadata-test.js deleted file mode 100644 index 05427aa..0000000 --- a/test/gridmetadata-test.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -//var app = bootstrap.app; -//var metadataUrl = bootstrap.basePath + '/ModelDefinitions'; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -//var debug = require('debug')('model-definition-test'); -var async = require('async'); - -//var loopback = require('loopback'); - -function create(model, items, callback) { - async.forEachOf(items, - function (item, m, callback2) { - model.create(item, bootstrap.defaultContext, function (a, b) { - callback2(); - }); - }, - function (err) { - if (err) { - throw err; - } - callback(); - }); -} - -function deleteAndCreate(model, items, callback) { - model.destroyAll({}, bootstrap.defaultContext, function () { - async.forEachOf(items, - function (item, m, callback2) { - model.create(item, bootstrap.defaultContext, function (e, rec) { - if (e) { - console.error(e.message); - } - callback2(); - }); - }, - function (err) { - if (err) { - throw err; - } - callback(); - }); - }); -} - - -describe(chalk.blue('grid-meta-data'), function () { - - //var loopbackContext; - //var tenantId = 'test-tenant'; - //var dsname = 'test_ds'; - //var dataSource; - //var tenantId_id; - //var requestId; - this.timeout(60000); - - //fields to populate in fields - var fields = [ - { - key: 'category', - uitype: 'text', - label: 'Category' - }, - { - key: 'price', - uitype: 'number', - label: 'Price' - }, - { - key: 'description', - uitype: 'text', - label: 'Description' - }, - { - key: 'date', - uitype: 'date', - label: 'Date' - } - ]; - - //definition of a model : Orer - var order = { - "name": "Order", - "plural": "Orders", - "base": "BaseEntity", - "idInjection": false, - "properties": { - "category": { - "type": "string", - "required": true - }, - "price": { - "type": "number", - "required": true - }, - "description": { - "type": "string" - }, - "date": { - "type": "date" - } - } - }; - - var modelName = "Order"; - - var testMetaData = { - "gridIdentifier": "Order", - "columnFields": [ - { - "key": "description", - "visible": false - }, - { - "key": "category", - "visible": true - }, - { - "key": "price" - } - ], - "dialogMetaData": "Order", - "dialogTemplateUrl": "/bower_components/evf-ui/demo/templates/dialog.html" - }; - - before('Define model and load data', function (done) { - create(models.ModelDefinition, [order], function (err) { - if (err) { - done(err); - } else { - deleteAndCreate(models.GridMetaData, [testMetaData], function (err1) { - if (err1) { - done(err1); - } else { - deleteAndCreate(models.Field, fields, function (err2) { - done(err2); - }); - } - }); - } - - }); - }); - - - after('destroy model2', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - console.error(err); - } - - models['Field'].destroyAll({}, bootstrap.defaultContext, function (err) { - done(err); - }); - }); - }); - - - it('should return the generated gridmetadata', function (done) { - - api - .get(bootstrap.basePath + '/GridMetaData/' + modelName + '/render') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response.columnData).to.exist; - expect(response.dialogMetaData).to.exist; - done(); - }); - - }); - - it('should return only the fields defined in the gridmetadata', function (done) { - - api - .get(bootstrap.basePath + '/GridMetaData/' + modelName + '/render') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response.columnData.length).to.be.equal(3); - done(); - }); - - }); - - it('should return the columnData fields with key, uitype, label and visible properties', function (done) { - - api - .get(bootstrap.basePath + '/GridMetaData/' + modelName + '/render') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - response.columnData.forEach(function (col) { - expect(col.key).to.exist; - expect(col.uitype).to.exist; - expect(col.label).to.exist; - expect(col.visible).to.exist; - }); - done(); - }); - - }); - - it('should return columnData field, with visible property set to "true", if it is not provided', function (done) { - - api - .get(bootstrap.basePath + '/GridMetaData/' + modelName + '/render') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var response = res.body; - expect(response).to.exist; - expect(response.columnData[2].visible).to.be.true; - done(); - }); - - }); - - it('should return error message if there is no entry for the requested grid identifier', function (done) { - - api - .get(bootstrap.basePath + '/GridMetaData/' + 'unknownGrid' + '/render') - .set('tenant_id', 'test-tenant') - .expect(200).end(function (err, res) { - var error = res.error; - expect(error).to.exist; - expect(error.message).to.exist; - done(); - }); - - }); - -}); \ No newline at end of file diff --git a/test/health-test.js b/test/health-test.js deleted file mode 100644 index 1d5178e..0000000 --- a/test/health-test.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This is a collection of tests that make sure that the health url (/health) works. - * - * @author Ori Press - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var url = '/health'; - -var chai = require('chai'); -chai.use(require('chai-things')); -var api = bootstrap.api; - -var debug = require('debug')('logger-config-test'); - -describe(chalk.blue('health-url-test'), function () { - it('get a response from the server', function (done) { - api - .set('Accept', 'application/json') - .get(url) - .end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error))); - } else { - if (res.status === 500) { - console.log("Checked health an got a 500 error code."); - } - done(); - } - }); - }); -}); diff --git a/test/history-mixin-test.js b/test/history-mixin-test.js deleted file mode 100644 index 14ca574..0000000 --- a/test/history-mixin-test.js +++ /dev/null @@ -1,234 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This file test the functionality of history management, it checks if history - * is maintained for different Write operation such as update and delete. - * - * and it also checks if /history rest api works fine with and without filter. - * - * @author Sivankar Jain - */ -/* jshint -W024 */ -/* jshint expr:true */ -// to avoid jshint errors for expect -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var loopback = require('loopback'); -var debug = require('debug')('history-mixin-test'); -var uuidv4 = require('uuid/v4'); -var api = bootstrap.api; - -describe('history-mixin tests Programmatically', function () { - - this.timeout(50000); - - var modelName = 'MixinTest'; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - }, - strict: false, - plural: modelName, - mixins: { - VersionMixin: true - } - }; - var model; - - before('create test model', function (done) { - models.ModelDefinition.create(modelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - debug('unable to create historyMixinTest model'); - done(err); - } else { - model = loopback.getModel(modelName, bootstrap.defaultContext); - done(); - } - }); - }); - - it('should create a history model for Test model', function (done) { - var mainModel = loopback.getModel(modelName, bootstrap.defaultContext); - var model = loopback.getModel(mainModel.modelName + 'History', bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model).not.to.be.undefined; - done(); - }); - - - it('should insert data to TestModel, check if version is set and history model is empty ---programmatically', - function (done) { - this.timeout(50000); - var postData = { - 'name': 'TestCaseOne' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.history({}, bootstrap.defaultContext, function (err, historyRes) { - if (err) { - done(err); - } else { - expect(historyRes).to.be.empty; - done(); - } - }); - } - }); - }); - - it('should insert data to TestModel model, update the same record multiple times and retrive its history.' + - ' --programmatically', - function (done) { - - this.timeout(15000); - var postData = { - 'name': 'TestCaseTwo' - }; - var dataId; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.id = res.id; - postData.name = 'update1'; - postData._version = res._version; - model.upsert(postData, bootstrap.defaultContext, function (err, upsertRes) { - if (err) { - done(err); - } else { - postData.name = 'update2'; - postData.id = upsertRes.id; - postData._version = upsertRes._version; - model.upsert(postData, bootstrap.defaultContext, function (err, upsertRes) { - if (err) { - done(err); - } else { - model.history({ - where: { - _modelId: dataId - } - }, - bootstrap.defaultContext, function (err, historyRes) { - if (err) { - done(err); - } else { - expect(historyRes).not.to.be.empty; - expect(historyRes).to.have.length(2); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('should insert data to TestModel model, destroy the same record retrive its history ', function (done) { - this.timeout(10000); - - var postData = { - 'name': 'TestCaseFour' - }; - var dataId; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - dataId = res.id; - model.deleteById(dataId, bootstrap.defaultContext, function (err, upsertRes) { - if (err) { - done(err); - } else { - model.history({ - where: { - _modelId: dataId - } - }, bootstrap.defaultContext, function (err, historyRes) { - if (err) { - done(err); - } else { - expect(historyRes).to.have.length(1); - done(); - } - }); - } - }); - } - }); - }); - - it('should insert new record, using upsert if id is not defined.', function (done) { - - var postData = { - 'name': 'TestCaseFive', - '_version': uuidv4() - }; - model.upsert(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal('TestCaseFive'); - expect(res.id).not.to.be.null; - expect(res.id).not.to.be.undefined; - done(); - } - }); - }); - - it('should insert data to TestModel model,update the same record multiple times and retrive its history--REST api', - function (done) { - this.timeout(10000); - - var postData = { - 'name': 'TestCaseThree' - }; - var url = bootstrap.basePath + '/' + modelName + '/history'; - var dataId; - var version; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.id = res.id; - postData._version = res._version; - postData.name = 'newName'; - model.upsert(postData, bootstrap.defaultContext, function (err, upsertRes) { - if (err) { - done(err); - } else { - api - .get(url) - .send() - .expect(200).end(function (err, historyRes) { - debug('response body : ' + JSON.stringify(historyRes.body, null, 4)); - if (err) { - done(err); - } else { - expect(historyRes.body).not.to.be.empty; - expect(historyRes.body).to.have.length(4); - done(); - } - }); - } - }); - } - }); - }); - -}); diff --git a/test/idempotency-attribute-update-test.js b/test/idempotency-attribute-update-test.js deleted file mode 100644 index ee569cf..0000000 --- a/test/idempotency-attribute-update-test.js +++ /dev/null @@ -1,152 +0,0 @@ - -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var chalk = require('chalk'); -var loopback = require('loopback'); -var async = require('async'); - - -var options = JSON.parse(JSON.stringify(bootstrap.defaultContext)); - -describe('create and get "leonTest" model', function () { - - var leonTestData = { - "name": "leonTest", - "base" : "BaseEntity", - "strict" : false, - "properties": { - "title": { - "type": "string" - }, - "content": { - "type": "string" - } - }, - "mixins": { - "SoftDeleteMixin": false - }, - "cacheable": true - }; - - var modelId; - var createNewModelDefinition = function (cb){ - bootstrap.api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/ModelDefinitions/') - .send(leonTestData).expect(200).end(function(err, res){ - if(err){ - return cb(err); - }else{ - modelId = res.body.id; - cb(); - } - }); - } - - var postData = function(cb){ - console.log('post "leonTest" instance to DB') - bootstrap.api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/leonTests/') - .send({ - 'id': 'n1', - 'title': 'initial', - 'content': 'boom' - }) - .expect(200).end(function (err, res) { - if (err) { - return cb(err); - }else{ - cb(); - } - }); - } - - - before('before', function (done) { - process.env.JWT_FOR_ACCESS_TOKEN = true; - - var tasks = [createNewModelDefinition, postData]; - async.series(tasks, done); - }); - - - after('after', function (done) { - process.env.JWT_FOR_ACCESS_TOKEN = true; - var deleteData = function (cb){ - console.log('in delete Data..'); - bootstrap.api - .set('Accept', 'application/json') - .delete(bootstrap.basePath + '/leonTests/' + 'n1') - .end(function(err, resp) { - if (err) { - return cb(err); - } else { - console.log("deleted") - return cb(); - } - }); - } - - var deleteModel = function (cb){ - console.log('in delete Model..'); - bootstrap.api - .set('Accept', 'application/json') - .delete(bootstrap.basePath + '/ModelDefinitions/' + modelId) - .end(function(err, resp) { - if (err) { - return cb(err); - } else { - return cb(); - } - }); - } - - var tasks = [deleteData, deleteModel]; - async.series(tasks, function(err, res){ - process.env.JWT_FOR_ACCESS_TOKEN = ''; - return done(err); - }); - }); - - it('update attributes on the same record should result with the same _version', function (done) { - var filter = {where: {id: 'n1'}}; - var leonTestModel = loopback.getModel('leonTest', bootstrap.defaultContext); - var version1; - leonTestModel.find(filter, options, function(err, models){ - var inv1 = models[0]; - expect(inv1.id).to.be.equal('n1'); - leonTestModel.find(filter, options, function(err, models2){ - var inv2 = models2[0]; - expect(inv2.id).to.be.equal('n1'); - var updateAndCheckVersion = function(inv, check1, check2) { - return function (asyncCB) { - inv.title = check1; - inv.updateAttributes(inv, options, function(err, res){ - if(err){ - return asyncCB(err) - } - expect(res.title).to.equal(check2); - if(check1 === "update1") { - version1 = res._version; - } - if(check1 === "update2") { - expect(res._version).to.be.equal(version1); - } - return asyncCB(); - }); - } - } - async.series([updateAndCheckVersion(inv1, "update1", "update1"), updateAndCheckVersion(inv2, "update2", "update1")], done); - }); - }); - }); -}); diff --git a/test/idempotency-test.js b/test/idempotency-test.js deleted file mode 100644 index 3f562db..0000000 --- a/test/idempotency-test.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * - * �2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; -var uuidv4 = require('uuid/v4'); -var loopback = require('loopback'); - -describe('basic-idempotency', function () { - - this.timeout(10000); - var app = bootstrap.app; - var baseurl = app.get('restApiRoot'); - var options = bootstrap.defaultContext; - var baseurl = app.get('restApiRoot'); - var accessToken; - var Note; - - before('login', function (done) { - Note = app.models.Note; - bootstrap.createAccessToken(bootstrap.defaultContext.ctx.remoteUser.username, function (err, token) { - accessToken = token; - var modelDetails = { - "name": "Note2", - "base": "BaseEntity", - "strict": false, - "properties": { - "title": { - "type": "string" - }, - "content": { - "type": "string" - } - } - }; - app.models.ModelDefinition.create(modelDetails, bootstrap.defaultContext, function modelCreate(err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create Note2 model'); - done(err); - } else { - Note = loopback.getModel('Note2', bootstrap.defaultContext); - done(); - } - }); - }); - }); - - var inst; - it('duplicate create', function (done) { - var data = { - title: 'my note', - content: 'Hello word', - _newVersion: uuidv4() - }; - Note.create(data, options, function (err, rec1) { - expect(err).to.be.null; - expect(rec1._version).to.be.defined; - Note.create(data, options, function (err, rec2) { - expect(err).to.be.not.ok; - expect(rec1.id.toString()).to.be.equal(rec2.id.toString()); - done(); - }); - }); - }); - - it('duplicate update via put with id', function (done) { - var data = { - title: 'rose', - content: 'content abcd', - _newVersion: uuidv4() - }; - var url = baseurl + '/Note2s' + '?access_token=' + accessToken; - var api = defaults(supertest(app)); - api.set('Accept', 'application/json') - .post(url) - .send(data) - .end(function (err, response) { - expect(err).to.be.null; - expect(response.body).to.be.defined; - var rec1 = response.body; - var api = defaults(supertest(app)); - var url = baseurl + '/Note2s/' + rec1.id + '?access_token=' + accessToken; - rec1._newVersion = uuidv4(); - rec1.content = 'update1'; - api.set('Accept', 'application/json') - .put(url) - .send(rec1) - .end(function (err, response) { - expect(err).to.be.null; - expect(response.status).to.be.least(200); - api.set('Accept', 'application/json') - .put(url) - .send(rec1) - .end(function (err, response) { - expect(err).to.be.null; - expect(response.status).to.be.equal(200); - done(); - }); - }); - }); - }); - -}); diff --git a/test/idempotent-behavior-test.js b/test/idempotent-behavior-test.js deleted file mode 100644 index 47db6a0..0000000 --- a/test/idempotent-behavior-test.js +++ /dev/null @@ -1,1239 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This is a collection of tests that make sure that the idempotent behaviour work. - * - */ - -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var api = bootstrap.api; -var models = bootstrap.models; -var logger = require('oe-logger'); -var log = logger('data-personalization-test'); -var loopback = require('loopback'); - -describe(chalk.blue('Idempotent behaviour --REST'), function () { - this.timeout(300000); - var state; - var testUserAccessToken; - var modelName = 'TestState'; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: false, - plural: modelName, - mixins: { - 'HistoryMixin': true, - 'VersionMixin': true, - 'IdempotentMixin': true - } - }; - - // Creating testuser access token since removed jwt-assertion middleware - // so that we can access the models which are created using bootstrap.defaultContext - // are based on testuesr and test-tenant. - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - before('Create Test model', function (done) { - models.ModelDefinition.create(modelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create IdempotentTestModel'); - done(err); - } else { - done(); - } - }); - }); - - it('- Test for Idempotent behaviour - CREATE operation ', function (done) { - var testData = { - 'name': 'Telangana', - '_newVersion': 't1' - }; - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log('========',result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Telangana'); - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - // console.log('========',result1.body); - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.name).to.be.equal('Telangana'); - - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========',res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - UPDATE operation ', function (done) { - var testData = { - 'name': 'Madras State', - '_newVersion': 'm1' - }; - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken - api - .put(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log('========',result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Madras State'); - var testData = { - 'name': 'Tamil Nadu', - '_newVersion': 'm2', - '_version': 'm1' - }; - testData.id = result.body.id; - api - .put(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - // console.log('========',result1.body); - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.name).to.be.equal('Tamil Nadu'); - - api - .put(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result2) { - if (err) { - done(err); - } else { - // console.log('========',result2.body); - expect(result2.body).not.to.be.null; - expect(result2.body).not.to.be.empty; - expect(result2.body).not.to.be.undefined; - expect(result2.body.name).to.be.equal('Tamil Nadu'); - - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========',res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(2); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - DELETE operation ', function (done) { - var testData = { - 'name': 'Mysore', - '_newVersion': 'ms1' - }; - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log('========',result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Mysore'); - var url2 = baseurl + '/' + result.body.id + '?access_token=' + testUserAccessToken; - api - .del(url2) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - // console.log('========', result1.body); - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.count).to.be.equal(1); - api - .del(url2) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result2) { - if (err) { - done(err); - } else { - // console.log('========', result2.body); - expect(result2.body).not.to.be.null; - expect(result2.body).not.to.be.empty; - expect(result2.body).not.to.be.undefined; - expect(result2.body.count).to.be.equal(1); - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========', res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(2); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - update attributes operation ', function (done) { - var testData = { - 'name': 'Madhya Bharat', - '_newVersion': 'mb1' - }; - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log('========', result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.name).to.be.equal('Madhya Bharat'); - var testData1 = {}; - testData1._newVersion = 'mb2'; - testData1._version = result.body._version; - testData1.name = 'Madhya Pradesh'; - testData1.id = result.body.id; - var newUrl = baseurl + '/' + result.body.id + '?access_token=' + testUserAccessToken; - api - .put(newUrl) - .send(testData1) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - // console.log('========', result1.body); - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.name).to.be.equal('Madhya Pradesh'); - - api - .put(newUrl) - .send(testData1) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result2) { - if (err) { - done(err); - } else { - // console.log('========', result2.body); - expect(result2.body).not.to.be.null; - expect(result2.body).not.to.be.empty; - expect(result2.body).not.to.be.undefined; - expect(result2.body.name).to.be.equal('Madhya Pradesh'); - - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========', res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(3); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - CREATE operation on array of records', function (done) { - var testData = [ - { - 'name': 'Rajasthan', - '_newVersion': 'rj1' - }, - { - 'name': 'Andhra Pradesh', - '_newVersion': 'ap1' - }, - { - 'name': 'Kerala', - '_newVersion': 'ke1' - }, - { - 'name': 'Uttaranchal', - '_newVersion': 'ut1' - } - ]; - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken; - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result) { - if (err) { - done(err); - } else { - // console.log('========',result.body); - expect(result.body).not.to.be.null; - expect(result.body).not.to.be.empty; - expect(result.body).not.to.be.undefined; - expect(result.body.length).to.be.equal(4); - api - .post(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - // console.log('========',result.body); - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.length).to.be.equal(4); - - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========',res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(7); - done(); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - UPDATE operation on array of records', function (done) { - var testData = [ - { - 'name': 'Punjab', - '_newVersion': 'pb1' - } - ]; - - - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var baseurl = bootstrap.basePath + '/' + modelName; - var url = baseurl + '?access_token=' + testUserAccessToken; - model.find({ - 'where': { - 'name': 'Uttaranchal' - } - }, bootstrap.defaultContext, function (err, result) { - if (err) { - done(err); - } else { - // console.log('========', result); - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result[0].name).to.be.equal('Uttaranchal'); - result[0].name = 'Uttarakhand'; - result[0]._newVersion = 'ut2'; - testData.push(result[0]); - api - .put(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result1) { - if (err) { - done(err); - } else { - expect(result1.body).not.to.be.null; - expect(result1.body).not.to.be.empty; - expect(result1.body).not.to.be.undefined; - expect(result1.body.length).to.be.equal(2); - - api - .put(url) - .send(testData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, result2) { - if (err) { - done(err); - } else { - // console.log('========', result2.body); - expect(result2.body).not.to.be.null; - expect(result2.body).not.to.be.empty; - expect(result2.body).not.to.be.undefined; - expect(result2.body.length).to.be.equal(2); - - api - .get(baseurl + '/count' + '?access_token=' + testUserAccessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .expect(200).end(function (err, res) { - if (err) { - done(err); - } else { - // console.log('========', res.body); - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.empty; - expect(res.body).not.to.be.undefined; - expect(res.body.count).to.be.equal(8); - done(); - } - }); - } - }); - } - }); - } - }); - - }); - -}); - -describe(chalk.blue('Idempotent behaviour --Programatic'), function () { - this.timeout(30000); - var city; - var modelName = 'City'; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - strict: false, - idInjection: false, - relations: { - 'state': { - 'type': 'hasOne', - 'model': 'TestState' - } - }, - plural: modelName, - mixins: { - 'HistoryMixin': true, - 'VersionMixin': true, - 'IdempotentMixin': true - - } - }; - - - before('Create Test model', function (done) { - models.ModelDefinition.create(modelDetails, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(bootstrap.defaultContext, 'unable to create TestModel'); - done(err); - } else { - done(); - } - }); - }); - - it('- Test for Idempotent behaviour - create ', function (done) { - var testData = { - 'name': 'Bangalore', - '_newVersion': '10' - }; - var testData2 = JSON.parse(JSON.stringify(testData)); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('Bangalore'); - - model.create(testData2, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.name).to.be.equal('Bangalore'); - - model.find({ - where: { - name: 'Bangalore' - } - }, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.length).to.be.equal(1); - done(); - } - }); - - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - upsert ', function (done) { - var testData = { - 'name': 'calcutta', - '_newVersion': '20' - }; - var testData2 = Object.assign({}, testData); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.upsert(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('calcutta'); - - model.upsert(testData2, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.name).to.be.equal('calcutta'); - - model.find({ - where: { - name: 'calcutta' - } - }, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.length).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - update attribute ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - - model.find({ - where: { - name: 'Bangalore' - } - }, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else if (res.length) { - // console.log('---', res); - var testData = res[0]; - testData.name = 'Bengaluru'; - res[0]._newVersion = '30'; - res[0].updateAttribute('name', 'Bengaluru', bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.name).to.be.equal('Bengaluru'); - res[0].updateAttribute('name', 'Bengaluru', bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.name).to.be.equal('Bengaluru'); - model.find({ - where: { - name: 'Bengaluru' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - // console.log('-------', res3); - expect(res3).not.to.be.null; - expect(res3).not.to.be.empty; - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - update attributes ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - - model.find({ - where: { - name: 'calcutta' - } - }, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else if (res.length) { - // console.log('---', res); - var testData = res[0].toObject(); - testData.name = 'Kolkata'; - testData._newVersion = '40'; - testData._version = res[0]._version; - var testData2 = Object.assign({}, testData); - res[0].updateAttributes(testData, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.name).to.be.equal('Kolkata'); - res[0].updateAttributes(testData2, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.name).to.be.equal('Kolkata'); - model.find({ - where: { - name: 'Kolkata' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - // console.log('-------', res3); - expect(res3).not.to.be.null; - expect(res3).not.to.be.empty; - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - updateById ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var testData = { - 'name': 'Madras', - '_newVersion': 'M1' - }; - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('Madras'); - var id = res.id; - var testData1 = {}; - testData1.id = id; - testData1.name = 'Chennai'; - testData1._newVersion = 'M2'; - testData1._version = res._version; - var testData2 = Object.assign({}, testData1); - model.upsert(testData1, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - // expect(res1).not.to.be.empty; - // expect(res1).not.to.be.undefined; - // expect(res1.count).to.be.equal(1); - model.upsert(testData2, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - // expect(res2).not.to.be.empty; - // expect(res2).not.to.be.undefined; - // expect(res2.count).to.be.equal(1); - model.find({ - where: { - name: 'Chennai' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - // console.log('-------', res3); - expect(res3).not.to.be.null; - expect(res3).not.to.be.empty; - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - } - }); - - }); - - // Update and UpdateAll are going via same flow in "before connector" - // the way of doing upsert with current version is not clear in updateall. - // This will be taken up later - As of now, we do not support updateAll - xit('- Test for Idempotent behaviour - updateAll ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var testData = { - 'name': 'Mysore', - '_newVersion': 'C1' - }; - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.updateAll({ - name: 'Mysore', - _version: res._version - }, { - name: 'Mysuru' - }, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.count).to.be.equal(1); - done(); - } - }); - } - }); - - }); - - it('- Test for Idempotent behaviour - findOrCreate', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var testData = { - 'name': 'Delhi', - '_newVersion': 'D1' - }; - model.findOrCreate({ - where: { - name: 'Delhi' - } - }, testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log("-------", res); - model.findOrCreate({ - where: { - name: 'Delhi' - } - }, testData, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - model.find({ - where: { - name: 'Delhi' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - // console.log('-------', res3); - expect(res3).not.to.be.null; - expect(res3).not.to.be.empty; - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(1); - done(); - } - }); - } - }); - } - }); - }); - - //For deleteById, destroyById, removeById - it('- Test for Idempotent behaviour - destroyById ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var testData = { - 'name': 'Mumbai', - '_newVersion': 'did10' - }; - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('Mumbai'); - model.destroyById(res.id, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.count).to.be.equal(1); - model.destroyById(res.id, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.count).to.be.equal(1); - model.find({ - where: { - name: 'Mumbai' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - // console.log('-------', res3); - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(0); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - //For deleteAll, destroyAll, remove - it('- Test for Idempotent behaviour - destroyAll ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var options = bootstrap.defaultContext; - options.requestId = '1001'; - model.destroyAll({}, options, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.count).to.be.equal(4); - options.requestId = '1001'; - model.destroyAll({}, options, function (err, res2) { - if (err) { - done(err); - } else { - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.count).to.be.equal(4); - model.find({ - where: { - name: 'Mumbai' - } - }, bootstrap.defaultContext, function (err, res3) { - if (err) { - done(err); - } else { - expect(res3).not.to.be.undefined; - expect(res3.length).to.be.equal(0); - done(); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - create (array of records) ', function (done) { - var testData = [ - { - 'name': 'Agra', - '_newVersion': 'x1' - }, - { - 'name': 'Jaipur', - '_newVersion': 'y1' - }, - { - 'name': 'Poona', - '_newVersion': 'z1' - } - ]; - var testData2 = JSON.parse(JSON.stringify(testData)); - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.length).to.be.equal(3); - model.create(testData2, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.length).to.be.equal(3); - - model.find({}, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.length).to.be.equal(3); - done(); - } - }); - - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - upsert (array of records)', function (done) { - var testData = [ - { - 'name': 'Shimla', - '_newVersion': 's1' - } - ]; - - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.find({ - where: { - name: 'Poona' - } - }, bootstrap.defaultContext, function (err, record) { - if (err) { - done(err); - } else { - // console.log('---------', record); - expect(record).not.to.be.null; - expect(record).not.to.be.empty; - expect(record).not.to.be.undefined; - record[0].name = 'pune'; - record[0]._newVersion = 'z2'; - testData.push(record[0]); - var testData2 = JSON.parse(JSON.stringify(testData)); - model.upsert(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.length).to.be.equal(2); - - model.upsert(testData2, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.length).to.be.equal(2); - - model.find({}, bootstrap.defaultContext, function (err, res2) { - if (err) { - done(err); - } else { - // console.log('-------', res2); - expect(res2).not.to.be.null; - expect(res2).not.to.be.empty; - expect(res2).not.to.be.undefined; - expect(res2.length).to.be.equal(4); - done(); - } - }); - } - }); - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - updating an already updated record ', function (done) { - var testData = { - 'name': 'Ooty', - '_newVersion': 'o1' - }; - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - // console.log('-------', res); - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('Ooty'); - - var updateData1 = {}; - updateData1.name = 'Udhagamandalam'; - updateData1._newVersion = 'o2'; - updateData1._version = res._version; - updateData1.id = res.id; - - model.upsert(updateData1, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - // console.log('-------', res1); - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.name).to.be.equal('Udhagamandalam'); - - var updateData2 = {}; - updateData2.name = 'Udhagamandalam'; - updateData2._newVersion = 'o3'; - updateData2._version = res._version; - updateData2.id = res.id; - model.upsert(updateData2, bootstrap.defaultContext, function (err, res2) { - if (err) { - // console.log('-------------', err); - expect(err.type).to.be.equal('DataModifiedError'); - done(); - } else { - done(new Error('Should not update the record which is already updated')); - } - }); - - } - }); - } - }); - }); - - it('- Test for Idempotent behaviour - updating a deleted record ', function (done) { - var testData = { - 'name': 'Cochin', - '_newVersion': 'co1' - }; - - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.create(testData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('Cochin'); - - model.destroyById(res.id, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1).not.to.be.null; - expect(res1).not.to.be.empty; - expect(res1).not.to.be.undefined; - expect(res1.count).to.be.equal(1); - var updateData = res; - updateData._newVersion = 'co2'; - updateData.name = 'Kochi'; - updateData._version = 'co1'; - - model.upsert(updateData, bootstrap.defaultContext, function (err, res2) { - if (err) { - expect(err.type).to.be.equal('DataDeletedError'); - done(); - } else { - done('Error: Should not be bale to modify deleted record'); - } - }); - - } - }); - } - }); - }); -}); diff --git a/test/idempotent-mixin-test.js b/test/idempotent-mixin-test.js deleted file mode 100644 index 25c9a20..0000000 --- a/test/idempotent-mixin-test.js +++ /dev/null @@ -1,1029 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This is a collection of tests that make sure that the idempotent behaviour work. - * - * @author Karin Angel - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('idempotent-mixin-test'); - -var api = bootstrap.api; - -var accessToken; - -var modelName = 'Vehicle'; -var pluralModelName = 'Vehicles'; - -var modelNameNoInjection = 'VehicleNoIdInjection'; -var pluralModelNameNoInjection = 'VehicleNoIdInjections'; - -function apiPost(url, postData) { - var promise = new Promise(function (resolve, reject) { - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - return reject(err || (new Error(JSON.stringify(res.body.error)))); - } - return resolve(res.body); - }); - }); - return promise; -} - -function apiPut(url, putData) { - var promise = new Promise(function (resolve, reject) { - // console.log('put url ', url, putData); - api - .set('Accept', 'application/json') - .put(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(putData) - .end(function (err, res) { - if (err || res.body.error) { - return reject(err || (new Error(JSON.stringify(res.body.error)))); - } - return resolve(res.body); - }); - }); - return promise; -} - -function apiDelete(url, deleteData) { - var promise = new Promise(function (resolve, reject) { - api - .set('Accept', 'application/json') - .delete(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(deleteData) - .end(function (err, res) { - if (err || res.body.error) { - return reject(err || (new Error(JSON.stringify(res.body.error)))); - } - return resolve(res.body); - }); - }); - return promise; -} - -describe(chalk.blue('idempotent-mixin-test'), function () { - this.timeout(30000); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - return done(err); - } - accessToken = res.body.id; - return done(); - }); - }); - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': modelName, - 'base': 'BaseEntity', - 'idInjection': true, - 'properties': { - 'color': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, createSecondModel); - - function createSecondModel(err, result) { - if (err) { - done(err); - } else { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': modelNameNoInjection, - 'base': 'BaseEntity', - 'idInjection': false, - 'properties': { - 'color': { - 'type': 'string' - } - } - }; - modelDefinition.create(data, bootstrap.defaultContext, done); - } - } - }); - - - after('delete all the Vehicle records', function (done) { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - vehicleModel.destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - return done(err); - } - return done(); - }); - }); - - after('delete all the VehicleNoIdInjection records', function (done) { - var vehicleNoIdInjectionModel = loopback.getModel('VehicleNoIdInjection', bootstrap.defaultContext); - vehicleNoIdInjectionModel.destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - return done(err); - } - return done(); - }); - }); - - describe(chalk.yellow('Create/Post tests'), function () { - it('Create with id injection, id + _version supplied, _newVersion not supplied --> object saved, result._oldVersion===result._version===request._version, request.id===result.id', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var id = uuidv4(); - var createData = { 'id': id, '_version': version }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.be.equal(createData.id); - expect(result._version).to.be.equal(version); - }); - }); - - it('Rest Post with id injection, id + _version supplied, _newVersion not supplied --> object saved, result._oldVersion===result._version===request._version, request.id===result.id', function () { - var version = uuidv4(); - var id = uuidv4(); - var postData = { 'id': id, '_version': version }; - return apiPost('/' + pluralModelName, postData).then(function (result) { - expect(result.id).to.be.equal(postData.id); - expect(result._version).to.be.equal(version); - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0].id).to.be.equal(postData.id); - expect(res[0]._version).to.be.equal(version); - }); - }); - }); - - it('Create with id injection, id + _newVersion supplied, _version not supplied --> object saved, result._oldVersion===result._version===request._newVersion, request.id===result.id', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var newVersion = uuidv4(); - var id = uuidv4(); - var createData = { 'id': id, '_newVersion': newVersion }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.be.equal(createData.id); - }); - }); - - it('Rest Post with id injection, id + _newVersion supplied, _version not supplied --> object saved, result._oldVersion===result._version===request._newVersion, request.id===result.id', function () { - var newVersion = uuidv4(); - var id = uuidv4(); - var postData = { 'id': id, '_newVersion': newVersion }; - return apiPost('/' + pluralModelName, postData).then(function (result) { - expect(result.id).to.be.equal(postData.id); - expect(result._version).to.be.equal(newVersion); - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0].id).to.be.equal(postData.id); - }); - }); - }); - - it('Create with id injection, id supplied, _version + _newVersion not supplied --> object saved, result._oldVersion===result._version and not empty, request.id===result.id', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var id = uuidv4(); - var createData = { 'id': id }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.be.equal(createData.id); - expect(result._version).to.be.ok; - }); - }); - - it('Rest Post with id injection, id supplied, _version + _newVersion not supplied --> object saved, result._oldVersion===result._version and not empty, request.id===result.id', function () { - var id = uuidv4(); - var postData = { 'id': id }; - return apiPost('/' + pluralModelName, postData).then(function (result) { - expect(result.id).to.be.equal(postData.id); - expect(result._version).to.not.equal(undefined); - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0].id).to.be.equal(postData.id); - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Create no id injection, _version supplied, id + _newVersion not supplied --> object saved, result._oldVersion===result._version===request._version, id not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.equal(version); - }); - }); - - it('Rest Post no id injection, _version supplied, id + _newVersion not supplied --> object saved, result._oldVersion===result._version===request._version, id not empty', function () { - var version = uuidv4(); - var postData = { '_version': version }; - return apiPost('/' + pluralModelNameNoInjection, postData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.equal(version); - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.be.equal(res[0].id.toString()); - }); - }); - }); - - - it('Create no id injection, _newVersion + id + _version not supplied --> object saved, result._oldVersion===result._version and not empty, id not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = {}; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.ok; - }); - }); - - it('Rest Post no id injection, _newVersion + id + _version not supplied --> object saved, result._oldVersion===result._version and not empty, id not empty', function () { - var postData = {}; - return apiPost('/' + pluralModelNameNoInjection, postData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.not.equal(undefined); - var version = result.version; - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(res._version).to.be.equal(version); - }); - }); - }); - - it('Create no id injection, _newVersion _version supplied and same --> object saved, result._oldVersion===result._version===request._newVersion', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var newVersion = uuidv4(); - var version = newVersion; - var createData = { '_newVersion': newVersion, '_version': version }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Post no id injection, _newVersion _version supplied and same --> object saved, result._oldVersion===result._version===request._newVersion', function () { - var newVersion = uuidv4(); - var version = newVersion; - var postData = { '_newVersion': newVersion, '_version': version }; - return apiPost('/' + pluralModelNameNoInjection, postData).then(function (result) { - expect(result._version).to.be.equal(newVersion); - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - xit('2 parallel creates, no id injection, _newVersion not supplied, _version supplied and same in both creates and the data is different --> one create succeeds, the second fails with error', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var version = uuidv4(); - var createData1 = { '_version': version, 'color': 'blue' }; - var createData2 = { '_version': version, 'color': 'yellow' }; - - var dataItems = []; - dataItems.push(createData1); - dataItems.push(createData2); - - return Promise.all(dataItems.map(function (dataItem) { - return vehicleModel.create(dataItem, bootstrap.defaultContext).then(function (result) { - return result; - }); - })).then(function (results) { - throw new Error('Promise was unexpectedly fulfilled. Result: ' + results); - }, function (error) { - expect(error.message).to.be.equal('Cannot create a record with version ' + version); - }); - }); - - xit('2 parallel Rest Posts, no id injection, _newVersion not supplied, _version supplied and same in both creates and the data is different --> one create succeeds, the second fails with error', function () { - var version = uuidv4(); - var createData1 = { '_version': version, 'color': 'blue' }; - var createData2 = { '_version': version, 'color': 'yellow' }; - - var dataItems = []; - dataItems.push(createData1); - dataItems.push(createData2); - - return Promise.all(dataItems.map(function (dataItem) { - return apiPost('/' + pluralModelNameNoInjection, dataItem).then(function (result) { - return result; - }); - })).then(function (results) { - throw new Error('Promise was unexpectedly fulfilled. Result: ' + results); - }, function (error) { - expect(JSON.parse(error.message).status).to.be.equal(500); - expect(JSON.parse(error.message).message).to.be.equal('Cannot create a record with version ' + version); - }); - }); - - it('2 parallel creates, no id injection, _newVersion not supplied, _version and data supplied and same in both creates --> one create succeeds, the second succeeds with version as the first result version', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var version = uuidv4(); - var createData1 = { '_version': version, 'color': 'blue' }; - var createData2 = { '_version': version, 'color': 'blue' }; - - var dataItems = []; - dataItems.push(createData1); - dataItems.push(createData2); - - return Promise.all(dataItems.map(function (dataItem) { - return vehicleModel.create(dataItem, bootstrap.defaultContext).then(function (result) { - return result; - }); - })).then(function (results) { - var result1 = results[0]; - var result2 = results[1]; - - expect(result1.id).to.not.equal(undefined); - expect(result1.id.toString()).to.be.equal(result2.id.toString()); - expect(result1._version).to.be.equal(version); - expect(result1._version).to.be.equal(result2._version); - expect(result1.color).to.be.equal(result2.color); - expect(result1.color).to.be.equal('blue'); - expect(result1._oldVersion).to.not.be.ok; - expect(result2._oldVersion).to.not.be.ok; - expect(result1._newVersion).to.not.be.ok; - expect(result2._newVersion).to.not.be.ok; - }); - }); - - it('2 parallel Rest Posts, no id injection, _newVersion not supplied, _version and data supplied and same in both creates --> one create succeeds, the second succeeds with version as the first result version', function () { - var version = uuidv4(); - var createData1 = { '_version': version, 'color': 'blue' }; - var createData2 = { '_version': version, 'color': 'blue' }; - - var dataItems = []; - dataItems.push(createData1); - dataItems.push(createData2); - - return Promise.all(dataItems.map(function (dataItem) { - return apiPost('/' + pluralModelNameNoInjection, dataItem).then(function (result) { - return result; - }); - })).then(function (results) { - var result1 = results[0]; - var result2 = results[1]; - expect(result1.id).to.not.equal(undefined); - expect(result1.id).to.be.equal(result2.id); - expect(result1._version).to.be.equal(result2._version); - expect(result1._version).to.be.equal(version); - expect(result1.color).to.be.equal(result2.color); - expect(result1.color).to.be.equal('blue'); - expect(result1._oldVersion).to.not.be.ok; - expect(result2._oldVersion).to.not.be.ok; - expect(result1._newVersion).to.not.be.ok; - expect(result2._newVersion).to.not.be.ok; - }); - }); - }); - - describe(chalk.yellow('Upsert/Put tests'), function () { - it('Upsert no id injection, id + _newVersion not supplied, _version supplied --> object saved, result._oldVersion===result._version===request._version, id not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var version = uuidv4(); - var upsertData = { '_version': version }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.ok; - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put no id injection, id + _newVersion not supplied, _version supplied --> object saved, result._oldVersion===result._version===request._version, id not empty', function () { - var version = uuidv4(); - var putData = { '_version': version }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.ok; - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.be.equal(res[0].id.toString()); - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Upsert no id injection, id + _version not supplied, _newVersion supplied --> object saved, object saved, result._oldVersion===result._version===request._newVersion, id not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var newVersion = uuidv4(); - var upsertData = { '_newVersion': newVersion }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put no id injection, id + _version not supplied, _newVersion supplied --> object saved, object saved, result._oldVersion===result._version===request._newVersion, id not empty', function () { - var newVersion = uuidv4(); - var putData = { '_newVersion': newVersion }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.be.equal(newVersion); - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0].id.toString()).to.be.equal(result.id); - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Upsert no id injection, id + _version + _newVersion not supplied --> object saved, result._oldVersion===result._version and not empty, id not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var upsertData = {}; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put no id injection, id + _version + _newVersion not supplied --> object saved, result._oldVersion===result._version and not empty, id not empty', function () { - var putData = {}; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0]._version).to.be.equal(result._version); - }); - }); - }); - - it('Upsert no id injection, object exist, id of the object is supplied + _version of the object supplied + _newVersion not supplied --> object updated, result._oldVersion===request._version, result._version!==result._oldVersion and not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = { 'firm': 'Hyundai' }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - var upsertData = { 'id': result.id, '_version': result._version, 'firm': 'BMW' }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.be.equal(res.id); - expect(res.firm).to.be.equal('BMW'); - expect(res._oldVersion).to.be.equal(result._version); - expect(res._version).to.not.equal(undefined); - expect(res._version).to.not.equal(res._oldVersion); - expect(res._newVersion).to.be.oneOf([null, undefined]); - }); - }); - }); - - it('Rest Put no id injection, object exist, id of the object is supplied + _version of the object supplied + _newVersion not supplied --> object updated, result._oldVersion===request._version, result._version!==result._oldVersion and not empty', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = { 'firm': 'Hyundai' }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - var putData = { 'id': result.id, '_version': result._version, 'firm': 'BMW' }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (res) { - expect(result.id.toString()).to.be.equal(res.id); - expect(res.firm).to.be.equal('BMW'); - expect(res._version).to.not.equal(undefined); - expect(res._newVersion).to.be.oneOf([null, undefined]); - var version = res._version; - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res2) { - expect(res2[0]._oldVersion).to.be.equal(result._version); - expect(res2[0]._oldVersion).to.not.equal(undefined); - expect(version).to.not.equal(res2._oldVersion); - expect(res2[0]._newVersion).to.be.oneOf([null, undefined]); - }); - }); - }); - }); - - it('Rest Put no id injection, object exist, id of the object is supplied, _version not supplied --> error', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = { 'firm': 'Hyundai' }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - var putData = { 'id': result.id, 'firm': 'BMW' }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (res) { - throw new Error('Promise was unexpectedly fulfilled. Result: ' + res); - }, function (error) { - expect(JSON.parse(error.message).status).to.be.equal(422); - // expect(JSON.parse(error.message).message).to.contain(' field is mandatory'); - // expect(JSON.parse(error.message).errors[0].code).to.be.equal('validation-err-004'); - // expect(JSON.parse(error.message).errors[0].path).to.be.equal(modelNameNoInjection + ' -> _version'); - }); - }); - }); - - it('Upsert no id injection, object exist, id of the object is supplied + _version of the object supplied + _newVersion supplied and different from objects _version --> object updated, result._oldVersion===request._version, result._version===request._newVersion', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = { 'firm': 'Hyundai' }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - var newVersion = uuidv4(); - var upsertData = { 'id': result.id, '_version': result._version, '_newVersion': newVersion, 'firm': 'BMW' }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.be.equal(res.id); - expect(res.firm).to.be.equal('BMW'); - expect(res._oldVersion).to.be.equal(result._version); - expect(res._oldVersion).to.not.equal(undefined); - expect(res._version).to.be.equal(newVersion); - expect(res._newVersion).to.be.oneOf([null, undefined]); - }); - }); - }); - - it('Rest Put no id injection, object exist, id of the object is supplied + _version of the object supplied + _newVersion supplied and different from objects _version --> object updated, result._oldVersion===request._version, result._version===request._newVersion', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var createData = { 'firm': 'Hyundai' }; - return vehicleModel.create(createData, bootstrap.defaultContext).then(function (result) { - var newVersion = uuidv4(); - var putData = { 'id': result.id, '_version': result._version, '_newVersion': newVersion, 'firm': 'BMW' }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (res) { - expect(result.id.toString()).to.be.equal(res.id); - expect(res.firm).to.be.equal('BMW'); - expect(res._version).to.be.equal(newVersion); - expect(res._newVersion).to.be.oneOf([null, undefined]); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res2) { - expect(res2[0]._oldVersion).to.be.equal(result._version); - expect(res2[0]._oldVersion).to.not.equal(undefined); - expect(res2[0]._newVersion).to.be.oneOf([null, undefined]); - }); - }); - }); - }); - - it('Upsert no id injection, object does not exist, id supplied --> object created with id given', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var id = uuidv4(); - var upsertData = { 'id': id }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.be.equal(id); - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put no id injection, object does not exist, id supplied --> object created with id given', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var id = uuidv4(); - var putData = { 'id': id }; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (result) { - expect(result.id).to.be.equal(id); - expect(result._version).to.not.equal(undefined); - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - return vehicleModel.find({ where: { id: id } }, bootstrap.defaultContext).then(function (res) { - expect(res[0].id).to.be.equal(id); - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Upsert no id injection, object does not exist, id not supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var upsertData = {}; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put no id injection, object does not exist, id not supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelNameNoInjection, bootstrap.defaultContext); - var putData = {}; - return apiPut('/' + pluralModelNameNoInjection, putData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.not.equal(undefined); - expect(result._oldVersion).to.be.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.not.equal(undefined); - - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Upsert with id injection, object does not exist, id supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var id = uuidv4(); - var upsertData = { 'id': id }; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - // interesting, Loopback 3 does not allow id to be generated - // expect(result.id).to.not.equal(id); - expect(result.id).to.not.equal(undefined); - - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put with id injection, object does not exist, id supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var id = uuidv4(); - var putData = { 'id': id }; - return apiPut('/' + pluralModelName, putData).then(function (result) { - // expect(result.id).to.not.equal(id); - expect(result.id).to.not.equal(undefined); - expect(result._version).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - // expect(res[0].id).to.not.equal(id); - - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - - it('Upsert with id injection, object does not exist, id not supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var upsertData = {}; - return vehicleModel.upsert(upsertData, bootstrap.defaultContext).then(function (result) { - // expect(result.id).to.not.equal(undefined); - // expect(result._oldVersion).to.be.equal(result._version); - // expect(result._oldVersion).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - }); - }); - - it('Rest Put with id injection, object does not exist, id not supplied --> object created with id generated', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var putData = {}; - return apiPut('/' + pluralModelName, putData).then(function (result) { - expect(result.id).to.not.equal(undefined); - expect(result._version).to.not.equal(undefined); - expect(result._newVersion).to.be.equal(undefined); - return vehicleModel.find({ where: { id: result.id } }, bootstrap.defaultContext).then(function (res) { - expect(result.id).to.not.equal(undefined); - - expect(res._newVersion).to.be.equal(undefined); - }); - }); - }); - }); - - describe(chalk.yellow('UpdateAttributes tests'), function () { - describe(chalk.green('Functional UpdateAttributes tests'), function () { - it('Should succeed on functional updateAttributes with correct _version and new _newVersion ', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var inst; - var v2; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - inst = res; - var data = res.toObject(true); - data.color = 'yellow'; - data._newVersion = v2 = uuidv4(); - return data; - }) - .then(function (data) { - return inst.updateAttributes(data, bootstrap.defaultContext); - }) - .then(function (res) { - expect(res).to.be.ok; - expect(res.color).to.be.equal('yellow'); - expect(res._version).to.be.equal(v2); - expect(res._oldVersion).to.be.ok; - expect(res._version).to.not.be.equal(res._oldVersion); - }); - }); - - // PKGTODO fix this.. - xit('Should succeed on functional updateAttributes with _newVersion that is current _version in server but should not update data', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - res._newVersion = res._version; - return res; - }) - .then(updateAttributes) - .then(function (res) { - expect(res).to.be.ok; - expect(res._version).to.be.ok; - expect(res.color).to.be.equal('blue'); - expect(res._oldVersion).to.be.ok; - }); - }); - - // PKG this should be actuall rest test case - // When you modify instance values...itself then it should be allowed - // PKGTODO unqiue check in hostory on _version should fail - xit('Should succeed on functional updateAttributes with _newVersion that is in model history table but should not update data', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var afterUpdateVersion; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - afterUpdateVersion = res._newVersion = uuidv4(); - return res; - }) - .then(updateAttributes) - .then(function (res) { - res.color = 'red'; - res._version = version; - res._newVersion = version; - return res; - }) - .then(updateAttributes) - .then(function (res) { - expect(res).to.be.ok; - res.findById(res.id, bootstrap.defaultContext, function (err, res) { - expect(res.color).to.be.equal('yellow'); - expect(res._version).to.be.ok; - expect(res._version).to.equal(afterUpdateVersion); - expect(res._oldVersion).to.be.ok; - expect(res._oldVersion).to.be.equal(version); - }); - }); - }); - - // this should be rest test case - xit('Should fail on functional updateAttributes with _version that is not in model history table or in current instance', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var failVersion; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - res._newVersion = uuidv4(); - return res; - }) - .then(updateAttributes) - .then(function (res) { - res.color = 'red'; - res._version = uuidv4(); - failVersion = res._newVersion = uuidv4(); - return res; - }) - .then(updateAttributes) - .then(function (res) { - expect(res).to.not.be.ok; - }).catch(function (err) { - expect(err).to.be.ok; - expect(err.name).to.be.equal('Data Error'); - expect(err.message).to.be.equal('Cannot create a record with version ' + failVersion); - expect(err.code).to.be.equal('DATA_ERROR_071'); - expect(err.type).to.be.equal('DataModifiedError'); - }); - }); - - xit('2 parallel updateAttributes, same id and version, diffrent newVersion one should succeed the other should fail', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - var dataItems = []; - var data1 = res.toObject(); - data1.color = 'yellow'; - data1._newVersion = 'abc'; - data1.id = data1.id.toString(); - dataItems.push(data1); - - var data2 = res.toObject(); - data2.color = 'red'; - data2._newVersion = 'abcd'; - data2.id = data2.id.toString(); - dataItems.push(data2); - - var promiseArray = dataItems.map(data => new Promise(function (resolve, reject) { - res.updateAttributes(data, bootstrap.defaultContext).then(function (data) { - resolve(data); - }).catch(function (err) { - resolve(err); - }); - })); - return Promise.all(promiseArray) - .then(function (results) { - var wasError = false; - results.forEach(function (data) { - if (wasError) { - expect(data.color).to.eql(data2.color); - expect(data.version).to.eql(data2.newVersion); - } else if (data instanceof Error) { - wasError = true; - expect(data.message).to.be.equal(modelName + ' instance is already locked'); - } else { - expect(data.color).to.eql(data1.color); - expect(data.version).to.eql(data1.newVersion); - } - }); - }); - }); - }); - }); - - describe(chalk.green('REST UpdateAttributes tests'), function () { - it('Should fail on REST put without _version', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version }; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - delete res.__data._oldVersion; - delete res.__data._version; - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - expect(res).to.not.be.ok; - }) - .catch(function (err) { - expect(err).to.be.ok; - expect(JSON.parse(err.message).status).to.be.equal(422); - // expect(JSON.parse(err.message).message).to.be.equal(' field is mandatory'); - // expect(JSON.parse(err.message).errors[0].code).to.be.equal('validation-err-004'); - // expect(JSON.parse(err.message).errors[0].path).to.be.equal(modelName + ' -> _version'); - }); - }); - - it('Should succeed on REST put with correct _version and new _newVersion ', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var versionAfterPut; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - versionAfterPut = res._newVersion = uuidv4(); - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - expect(res).to.be.ok; - expect(res._version).to.be.equal(versionAfterPut); - expect(res.color).to.be.equal('yellow'); - expect(res._version).to.not.be.equal(res._oldVersion); - }); - }); - - // TODO actually it should be an error, as data is different - it('Should succeed on REST put with _newVersion that is current _version in server but should not update data', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var data; - var version2, version3; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - version1 = res._version; - data = res.toObject(true); - data.color = 'yellow'; - data._newVersion = uuidv4(); - return apiPut('/' + pluralModelName + '/' + res.id, data); - }) - .then(function (res) { - data.color = 'black'; - version2 = res._version; - return apiPut('/' + pluralModelName + '/' + res.id, data); - }) - .then(function (res) { - expect(res).to.be.ok; - expect(res._version).to.be.ok; - expect(res.color).to.be.equal('yellow'); - expect(res._version).to.be.equal(version2); - }); - }); - - it('Should succeed on REST put with _newVersion that is in model history table but should not update data', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var afterUpdateVersion; - var expectedRecord; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - id = res.id; - afterUpdateVersion = res._newVersion = uuidv4(); - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - expectedRecord = res; - res.color = 'yellow'; - res._version = version; - res._newVersion = afterUpdateVersion; - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - expect(res).to.be.ok; - var filter = { _modelId: expectedRecord.id }; - vehicleModel._historyModel.count(bootstrap.defaultContext, function (err, count) { - console.log('history count ', count); - // PKGTODO this will work when lock is in place - // expect(count).to.be.equal(1); - }); - }); - }); - - it('Should fail on REST updateAttributes with _version that is not in model history table or in current instance', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - var failVersion; - var expectedRecord; - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - res.color = 'yellow'; - res._newVersion = uuidv4(); - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - res.color = 'red'; - expectedRecord = res; - // set wrong version - res._version = uuidv4(); - failVersion = res._newVersion = uuidv4(); - return apiPut('/' + pluralModelName + '/' + res.id, res); - }) - .then(function (res) { - expect(res).to.not.be.ok; - }).catch(function (err) { - expect(err).to.be.ok; - expect(JSON.parse(err.message).status).to.be.equal(422); - expect(JSON.parse(err.message).code).to.be.equal('DATA_ERROR_071'); - }); - }); - - xit('2 parallel updateAttributes, same id and version, diffrent newVersion one should succeed the other should fail', function () { - var vehicleModel = loopback.getModel(modelName, bootstrap.defaultContext); - var version = uuidv4(); - var createData = { '_version': version, 'color': 'blue' }; - - return vehicleModel.create(createData, bootstrap.defaultContext) - .then(function (res) { - var dataItems = []; - var data1 = res.toObject(); - data1.color = 'yellow'; - data1._newVersion = 'abc'; - data1.id = data1.id.toString(); - dataItems.push(data1); - - var data2 = res.toObject(); - data2.color = 'red'; - data2._newVersion = 'abcd'; - data2.id = data2.id.toString(); - dataItems.push(data2); - - var promiseArray = dataItems.map(data => new Promise(function (resolve, reject) { - var result = {}; - apiPut('/' + pluralModelName + '/' + data.id, data).then(function (data) { - result.type = 'data'; - result.payload = data; - resolve(result); - }).catch(function (err) { - result.type = 'error'; - result.payload = err; - resolve(result); - }); - })); - - return Promise.all(promiseArray) - .then(function (results) { - var wasError = false; - results.forEach(function (data) { - if (wasError) { - expect(data.type).to.be.equal('data'); - expect(data.payload.color).to.eql(data2.color); - expect(data.payload.version).to.eql(data2.newVersion); - } else if (data.type === 'error') { - wasError = true; - var error = JSON.parse(data.payload.message); - expect(error.status).to.be.equal(500); - expect(error.message).to.be.equal(modelName + ' instance is already locked'); - } else { - expect(data.type).to.be.equal('data'); - expect(data.payload.color).to.be.eql(data1.color); - expect(data.payload.version).to.be.eql(data1.newVersion); - } - }); - }); - }); - }); - }); - }); -}); diff --git a/test/inheritance-util-tests.js b/test/inheritance-util-tests.js deleted file mode 100644 index c6a86f7..0000000 --- a/test/inheritance-util-tests.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var loopback = require('loopback'); -var oeUtil = require('./../lib/common/util'); -var async = require('async'); - -describe('inheritance util tests', function() { - it('should create a litter of inherited models', done => { - var names = ['X', 'Y', 'Z', 'A']; - // debugger; - var ins = (name, idx) => new Promise((resolve, reject) => { - - var r; - - if(idx === 0) { - r = { - name, - properties:{ - [`a${name}${idx}`] : 'string' - } - } - } - else { - r = { - name, - base: names[idx - 1], - properties:{ - [`a${name}${idx}`] : 'string' - } - } - } - - models.ModelDefinition.create(r, bootstrap.defaultContext, err => { - if (err) { - reject(err) - } - else { - resolve(); - } - }); - }); - - var tasks = names.map((n,i) => cb => { - ins(n, i).then(() => cb()).catch(err => cb(err)); - }); - - async.series(tasks, (err) => err ? done(err) : done()); - }).timeout(10000); - - it('should assert that traverseInheritanceTree util works correctly', () => { - var model = loopback.findModel('A', bootstrap.defaultContext); - var results = []; - // debugger; - expect(model.modelName).to.equal('A-test-tenant'); - debugger; - oeUtil.traverseInheritanceTree(model, bootstrap.defaultContext, base => { - results.push(base.modelName); - }); - - expect(results).to.eql(['X', 'Y', 'Z'].reverse().map(n => `${n}-test-tenant`)); - - }); -}); diff --git a/test/instance-caching-test.js b/test/instance-caching-test.js deleted file mode 100644 index f513a64..0000000 --- a/test/instance-caching-test.js +++ /dev/null @@ -1,1193 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This test is for unit-testing the Instance result caching feature in datasource juggler. - * The test involves creating a test model, inserting a record into it, fetching the - * record (so that it caches), deleting the record from the database by directly accessing - * the DB (bypassing the framework, so that cache is not ecicted), fetching the - * record again to see that the records are still fetched (from cache). - * - * Author: Lior Schindler - */ - - -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = bootstrap.chai; -var expect = chai.expect; -var app = bootstrap.app; -var models = bootstrap.models; -var loopback = require('loopback'); -var async = require('async'); -var api = bootstrap.api; -var debug = require('debug')('caching-test'); -var config = require('../server/config'); -var MongoClient = require('mongodb').MongoClient; -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var pg = require('pg'); -var postgresHost = process.env.POSTGRES_HOST || 'localhost'; -var logger = require('oe-logger'); -var log = logger('instance-caching-test'); -var oracleHost = process.env.ORACLE_HOST || 'localhost'; -var oraclePort = process.env.ORACLE_PORT || 1521; -var oracleService = process.env.ORACLE_SID || 'orclpdb.ad.infosys.com'; -var oracleUser = process.env.ORACLE_USERNAME || 'oeadmin'; -var oraclePassword = process.env.ORACLE_PASSWORD || 'oeadmin'; -var originalConsistentHash = process.env.CONSISTENT_HASH; - -var defaultContext = { - ctx: { - tenantId: 'limits' - } -}; -var altContext = { - ctx: { - tenantId: 'gravity' - } -}; -var modelName = 'InstanceCachingTest'; -var modelNameNoInstanceCache = 'ICTNoInstanceCache'; -var modelNameQueryAndInstanceCache = 'ICTQueryAndInstanceCache'; -var modelNameQueryAndInstanceCacheShortExp = 'ICTQueryAndInstanceCacheShortExp'; -var consistentHashModelName = 'ICTConsistentHashFalse'; -var dsName = 'db'; -var dbname = process.env.DB_NAME || 'db'; -var dataSource; -var accessToken = null; - -function apiPostRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .set('x-ci-erase-cache', '1') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -function apiGetRequest(url, callback, done) { - api - .set('Accept', 'application/json') - .set('x-ci-erase-cache', '1') - .get(bootstrap.basePath + url + '?access_token=' + accessToken) - .send() - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -function mongoDeleteById(id, newModelName, cb) { - if (dataSource.name === 'mongodb') { - var url = 'mongodb://' + mongoHost + ':27017/' + dbname; - MongoClient.connect(url, function (err, db) { - if (err) { - return cb(err); - } else { - db.collection(newModelName).deleteOne({ _id: id }, function (err, numberRemoved) { - if (err) { - return cb(err); - } - debug("Number of records removed " + numberRemoved); - cb(); - }); - } - }); - } else if (dataSource.name === 'oe-connector-oracle') { - var oracledb = require('oracledb'); - oracledb.autoCommit = true; - var loopbackModelNoCache = loopback.getModel(newModelName, bootstrap.defaultContext); - var idFieldName = loopbackModelNoCache.definition.idName(); - oracledb.getConnection({ - "password": oraclePassword, - "user": oracleUser, - "connectString": oracleHost + ":" + oraclePort + "/" + oracleService - }, function (err, connection) { - if (err) { - return cb(err); - } - connection.execute( - "DELETE from \"" + loopbackModelNoCache.modelName.toUpperCase() + "\" WHERE " + idFieldName + " = '" + id + "'", - function (error, result) { - if (err) { - return cb(err); - } - debug("Number of records removed " + result.rowsAffected); - cb(); - }); - }); - } else { - var loopbackModelNoCache = loopback.getModel(newModelName, bootstrap.defaultContext); - var idFieldName = loopbackModelNoCache.definition.idName(); - var connectionString = "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbname; - var client = new pg.Client(connectionString); - client.connect(function (err) { - if (err) - cb(err); - else { - var query = client.query("DELETE from \"" + loopbackModelNoCache.modelName.toLowerCase() + "\" WHERE " + idFieldName + " = '" + id + "'", function (err, result) { - if (err) { - return cb(err); - } - debug("Number of records removed " + result.rowCount); - cb(); - }); - } - }); - } - -} - -describe('Instance Caching Test', function () { - var TestModel = null; - var TestModelNoInstanceCache = null; - var TestModelQueryAndInstanceCache = null; - var TestModelQueryAndInstanceCacheShortExp = null; - this.timeout(20000); - before('login using testuser', function fnLogin(done) { - dataSource = app.datasources[dsName]; - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .set('x-ci-erase-cache', '1') - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - before('Create Test Model', function (done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - dataSource = app.datasources[dsName]; - var data = { - 'name': modelName, - 'base': 'BaseEntity', - 'idInjection': true, - 'options': { - instanceCacheSize: 2000, - instanceCacheExpiration: 100000, - queryCacheSize: 2000, - queryCacheExpiration: 1000, - disableManualPersonalization: true, - disableInstanceCache: false - }, - 'properties': { - 'name': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, model) { - if (err) { - return done(err); - } else { - TestModel = loopback.getModel(modelName, bootstrap.defaultContext); - TestModel.destroyAll({}, defaultContext, function (err, info) { - if (err) { - return done(err); - } else { - done(); - } - }); - } - }); - }); - - before('Create Test Model with No InstanceCache', function (done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - dataSource = app.datasources[dsName]; - var data = { - 'name': modelNameNoInstanceCache, - 'base': 'BaseEntity', - 'idInjection': true, - 'options': { - instanceCacheSize: 2000, - instanceCacheExpiration: 100000, - queryCacheSize: 2000, - queryCacheExpiration: 1000, - disableManualPersonalization: true, - disableInstanceCache: true - }, - 'properties': { - 'name': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, model) { - if (err) { - return done(err); - } else { - TestModelNoInstanceCache = loopback.getModel(modelNameNoInstanceCache, bootstrap.defaultContext); - TestModelNoInstanceCache.destroyAll({}, defaultContext, function (err, info) { - if (err) { - return done(err); - } else { - done(); - } - }); - } - }); - }); - - before('Create Test Model with query cache and instance cache', function (done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - dataSource = app.datasources[dsName]; - var data = { - 'name': modelNameQueryAndInstanceCache, - 'plural': modelNameQueryAndInstanceCache + 's', - 'base': 'BaseEntity', - 'idInjection': true, - 'options': { - instanceCacheSize: 2000, - instanceCacheExpiration: 100000, - queryCacheSize: 2000, - queryCacheExpiration: 100000, - disableManualPersonalization: true, - disableInstanceCache: false, - cacheable: true - }, - 'properties': { - 'name': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, model) { - if (err) { - return done(err); - } else { - TestModelQueryAndInstanceCache = loopback.getModel(modelNameQueryAndInstanceCache, bootstrap.defaultContext); - TestModelQueryAndInstanceCache.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - return done(err); - } else { - done(); - } - }); - } - }); - }); - - before('Create Test Model with query cache and instance cache', function (done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - dataSource = app.datasources[dsName]; - var data = { - 'name': modelNameQueryAndInstanceCacheShortExp, - 'base': 'BaseEntity', - 'idInjection': true, - 'options': { - instanceCacheSize: 2000, - instanceCacheExpiration: 100000, - queryCacheSize: 2000, - queryCacheExpiration: 1000, - disableManualPersonalization: true, - disableInstanceCache: false, - cacheable: true - }, - 'properties': { - 'name': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, model) { - if (err) { - return done(err); - } else { - TestModelQueryAndInstanceCacheShortExp = loopback.getModel(modelNameQueryAndInstanceCacheShortExp, bootstrap.defaultContext); - TestModelQueryAndInstanceCacheShortExp.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - return done(err); - } else { - done(); - } - }); - } - }); - }); - - describe('CRUD tests', function () { - dataSource = app.datasources[dsName]; - it('Should NOT cache the Test instance after create', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: "Lior", - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - result1 = Object.assign({}, data.toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done(); - } - return done(new Error('should not cache instance')); - }) - }); - } - }); - }); - - it('Should cache the Test instance after findById', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: "Lior", - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 1) { - return done('find should return one instance'); - } - result1 = Object.assign({}, data[0].toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached'); - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - return done(); - }); - }); - }); - } - }); - }); - - it('Should cache the Test instance after findById, for system generated id', function (done) { - var result1, result2; - TestModel.create({ - name: "Lior", - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - var id = data.id; - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 1) { - return done('find should return one instance'); - } - result1 = Object.assign({}, data[0].toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached') - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - return done(); - }); - }); - }); - } - }); - }); - - xit('Should cache the Test instance after upsert', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Lior', - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - data.name = 'karin'; - TestModel.upsert(data, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } - result1 = Object.assign({}, data.toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached') - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1.__data === result2.__data).to.be.true; - return done(); - }); - }); - }); - } - }); - }); - - xit('Should cache the Test instance after save', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Lior', - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - data.name = 'Tamar'; - data.save(defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } - result1 = Object.assign({}, data.toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached') - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - return done(); - }) - }); - }); - } - }); - }); - - xit('Should cache the Test instance after updateAttributes', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Lior', - assign: { - change: 'this field should be deleted' - }, - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - data.updateAttributes({ name: 'Eduardo', assign: { new: 'should only see this field' } }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } - result1 = Object.assign({}, data.toObject(), { name: 'Eduardo', assign: { new: 'should only see this field' } }); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached') - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - return done(); - }) - }); - }); - } - }); - }); - - it('Should clear instance cache after destroyAll', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Ori', - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.destroyAll({}, defaultContext, function (err) { - if (err) { - console.log(err); - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } - expect(data2.length).to.be.equal(0); - return done(); - }); - }); - } - }); - }); - - it('Should delete the Test instance from cache after deleteByid', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Tamar', - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.destroyById(id, defaultContext, function (err) { - if (err) { - console.log(err); - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } - expect(data2.length).to.be.equal(0); - return done(); - }); - }); - } - }); - }); - - it('Should delete the Test instance from cache after deleteByid and version', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: 'Tamar', - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.destroyById(id, data._version, defaultContext, function (err) { - if (err) { - console.log(err); - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } - expect(data2.length).to.be.equal(0); - return done(); - }); - }); - } - }); - }); - - it('Should clear cache after update', function (done) { - var id = uuidv4(); - TestModel.create({ - name: "Praveen", - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } - expect(data2.length).to.be.equal(0); - return done(); - }); - }); - } - }); - }); - - it('Should delete empty instance in cache after create', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 0) { - return done('There should not be an instance yet'); - } - TestModel.create({ - name: "Lior", - id: id - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 1) { - return done('find should return one instance'); - } - result1 = Object.assign({}, data[0].toObject()); - mongoDeleteById(id, TestModel.modelName, function (err) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } else if (data2.length === 0) { - return done('instance not cached') - } - result2 = Object.assign({}, data2[0].toObject()); - expect(models[TestModel.modelName]).not.to.be.null; - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - return done(); - }); - }); - }); - } - }); - }); - }); - - it('Should not cache id queries with operators', function (done) { - var id1 = uuidv4(); - var id2 = uuidv4(); - var result1, result2; - TestModel.create({ - name: "Atul", - id: id1 - }, defaultContext, function (err, data) { - if (err) { - console.log(err); - return done(err); - } else { - TestModel.find({ "where": { "id": { "inq": [id1, id2] } } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 1) { - return done('find should return one instance'); - } - TestModel.create({ - name: "Ramesh", - id: id2 - }, defaultContext, function (err, data) { - TestModel.find({ "where": { "id": { "inq": [id1, id2] } } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } else if (data.length !== 2) { - return done('find should return two instances'); - } - return done(); - }); - }); - }); - } - }); - }); - - it('Should not cache in instance cache if disableInstanceCache flag is on, test1', function (done) { - /** - * 1. create new modle instance - * 2. run a find query - * 3. at this point, in a normal case, the instance should be cached - * 4. change the db directly in the DB. - * 5. comper the record by quering again, at this point if the record is cached the result should e not updated. - */ - var id = uuidv4(); - - apiPostRequest('/' + modelNameNoInstanceCache + 's/', { "name": "value1", "id": id }, apiRequest_find, done); - - function apiRequest_find(result, callback) { - apiGetRequest('/' + modelNameNoInstanceCache + 's/' + id, callback ? callback : dbQuery_update, done); - } - - function dbQuery_update(result) { - var loopbackModelNoCache = loopback.getModel(modelNameNoInstanceCache, bootstrap.defaultContext); - if (dataSource.name === 'mongodb') { - MongoClient.connect('mongodb://' + mongoHost + ':27017/'+dbname, function (err, db) { - if (err) return done(err); - else { - db.collection(loopbackModelNoCache.modelName).update({ "_id": id }, { $set: { name: "value2" } }, { upsert: true }, function (err) { - if (err) return done(err); - else apiRequest_find(result, comperCacheToDb); - }); - } - }); - } else if (dataSource.name === 'oe-connector-oracle') { - var oracledb = require('oracledb'); - oracledb.autoCommit = true; - var idFieldName = loopbackModelNoCache.definition.idName(); - oracledb.getConnection({ - "password": oraclePassword, - "user": oracleUser, - "connectString": oracleHost + ":" + oraclePort + "/" + oracleService - }, function (err, connection) { - if (err) { - return done(err); - } - connection.execute( - "UPDATE \"" + loopbackModelNoCache.modelName.toUpperCase() + "\" SET name = 'value2' WHERE " + idFieldName + " = '" + id + "'", - function (error, result) { - if (err) { - return done(err); - } else { - apiRequest_find(result, comperCacheToDb); - } - }); - }); - } else { - var idFieldName = loopbackModelNoCache.definition.idName(); - var connectionString = "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbname; - var client = new pg.Client(connectionString); - client.connect(function (err) { - if (err) - done(err); - else { - var query = client.query("UPDATE \"" + loopbackModelNoCache.modelName.toLowerCase() + "\" SET name = 'value2' WHERE " + idFieldName + " = '" + id + "'", function (err, result) { - if (err) { - return done(err); - } - else { - apiRequest_find(result, comperCacheToDb); - } - }); - } - }); - } - } - - function comperCacheToDb(result) { - if (result.body.name === "value2") return done(); - else return done(new Error("Model cached to instance cache, although disableInstanceCache flag is on")); - } - }); - - it('Should not cache in instance cache if disableInstanceCache flag is on, test2', function (done) { - var id = uuidv4(); - TestModelNoInstanceCache.create({ "name": modelNameNoInstanceCache, "id": id }, defaultContext, function (err, result) { - if (err) { - return done(err); - } else { - result.name = 'karin'; - TestModelNoInstanceCache.upsert(result, defaultContext, function (err, data) { - if (err) { - return done(err); - } else { - if (global.evDisableInstanceCache[modelNameNoInstanceCache] - && !global.instanceCache[modelNameNoInstanceCache] - && !global.queryCache[modelNameNoInstanceCache]) { - return done(); - } else { - return done(err); - } - } - }); - } - }); - }); - }); - - describe('noInstanceCache option on request test', function () { - it('Programatically - Should bring data from db on an instance query when noInstanceCache is on and insert the new value to instacne cache', function (done) { - var id = uuidv4(); - TestModelQueryAndInstanceCache.create({ - name: "noInstanceCacheTest", - id: id - }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - TestModelQueryAndInstanceCache.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelQueryAndInstanceCache.modelName, function (err) { - if (err) { - return done(err); - } - var defaultContext2 = defaultContext; - defaultContext2.noInstanceCache = true; - TestModelQueryAndInstanceCache.find({ "where": { "id": id } }, defaultContext2, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - delete defaultContext.noInstanceCache; - TestModelQueryAndInstanceCache.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - return done(); - }); - }); - }); - }); - }); - }); - - it('Rest - Should bring data from db on an instance query when noInstanceCache is on and insert the new value to instacne cache', function (done) { - var id = uuidv4(); - TestModelQueryAndInstanceCache.create({ - name: "noInstanceCacheTestRest", - id: id - }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - TestModelQueryAndInstanceCache.find({ "where": { "id": id } }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelQueryAndInstanceCache.modelName, function (err) { - if (err) { - return done(err); - } - var url = '/' + TestModelQueryAndInstanceCache.pluralModelName; - var filter = {where: {id: id}}; - api - .set('Accept', 'application/json') - .set('x-ci-erase-cache', '0') - .get(bootstrap.basePath + url + '?filter='+JSON.stringify(filter)+'&noInstanceCache=' + 1 + '&access_token=' + accessToken) - .send() - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } - expect(res.body.length).to.be.equal(0); - TestModelQueryAndInstanceCache.find({ "where": { "id": id } }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - return done(); - }); - }); - }); - }); - }); - }); - }); - - describe('noQueryCache option on request test', function () { - it('Programatically - Should bring data from db on an ordinary query when noQueryCache is on and insert the new value to query cache', function (done) { - var id = uuidv4(); - TestModelQueryAndInstanceCache.create({ - name: "noQueryCacheTest", - id: id - }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - TestModelQueryAndInstanceCache.find({ "where": { "name": "noQueryCacheTest" } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelQueryAndInstanceCache.modelName, function (err) { - if (err) { - return done(err); - } - var defaultContext2 = defaultContext; - defaultContext2.noQueryCache = true; - TestModelQueryAndInstanceCache.find({ "where": { "name": "noQueryCacheTest" } }, defaultContext2, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - delete defaultContext.noQueryCache; - TestModelQueryAndInstanceCache.find({ "where": { "name": "noQueryCacheTest" } }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - return done(); - }); - }); - }); - }); - }); - }); - - it('Rest - Should bring data from db on an ordinary query when noQueryCache is on and insert the new value to quey cache', function (done) { - var id = uuidv4(); - TestModelQueryAndInstanceCache.create({ - name: "noQueryCacheTestRest", - id: id - }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - TestModelQueryAndInstanceCache.find({ "where": { "name": "noQueryCacheTestRest" } }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelQueryAndInstanceCache.modelName, function (err) { - if (err) { - return done(err); - } - var url = '/' + TestModelQueryAndInstanceCache.pluralModelName; - var filter = {where: {name: "noQueryCacheTestRest"}}; - api - .set('Accept', 'application/json') - .set('x-ci-erase-cache', '0') - .get(bootstrap.basePath + url + '?filter='+JSON.stringify(filter)+'&noQueryCache=' + 1 + '&access_token=' + accessToken) - .send() - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - expect(res.body.length).to.be.equal(0); - TestModelQueryAndInstanceCache.find({ "where": { "name": "noQueryCacheTestRest"} }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - return done(); - }); - } - }); - }); - }); - }); - }); - }); - - describe('check short expiration on query cache', function () { - it('Should bring data from db after item has expired in query cache', function (done) { - var id = uuidv4(); - TestModelQueryAndInstanceCacheShortExp.create({ - name: "shortExpirationTest", - id: id - }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - TestModelQueryAndInstanceCacheShortExp.find({ "where": { "name": "shortExpirationTest" } }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelQueryAndInstanceCacheShortExp.modelName, function (err) { - if (err) { - return done(err); - } - setTimeout(function() { - TestModelQueryAndInstanceCacheShortExp.find({ "where": { "name": "shortExpirationTest" } }, bootstrap.defaultContext, function (err, data) { - if (err) { - return done(err); - } - expect(data.length).to.be.equal(0); - return done(); - }); - }, 1000); - }); - }); - }); - }); - }); - - describe('check CONSISTENT_HASH env variable set to false', function () { - it('Should bring data from db on instance queries when CONSISTENT_HASH is false', function (done) { - process.env.CONSISTENT_HASH = false; - var TestModelConsistentHashOff = null; - var modelDefinition = loopback.findModel('ModelDefinition'); - dataSource = app.datasources[dsName]; - var data = { - 'name': consistentHashModelName, - 'base': 'BaseEntity', - 'idInjection': true, - 'disableInstanceCache' : false, - 'options': { - instanceCacheSize: 2000, - instanceCacheExpiration: 100000, - queryCacheSize: 2000, - queryCacheExpiration: 1000, - disableManualPersonalization: true, - disableInstanceCache: false - }, - 'properties': { - 'name': { - 'type': 'string' - } - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, model) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } else { - TestModelConsistentHashOff = loopback.getModel(consistentHashModelName, bootstrap.defaultContext); - TestModelConsistentHashOff.destroyAll({}, defaultContext, function (err, info) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } else { - var id = uuidv4(); - TestModelConsistentHashOff.create({ - name: "consistentHashOffTest", - id: id - }, defaultContext, function (err, data) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } - TestModelConsistentHashOff.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } - expect(data.length).to.be.equal(1); - mongoDeleteById(id, TestModelConsistentHashOff.modelName, function (err) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } - TestModelConsistentHashOff.find({ "where": { "id": id } }, defaultContext, function (err, data) { - if (err) { - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(err); - } - expect(data.length).to.be.equal(0); - process.env.CONSISTENT_HASH = originalConsistentHash; - return done(); - }); - }); - }); - }); - } - }); - } - }); - }); - }); - - describe('Personalization tests', function () { - xit('Should create two instances with the same id and diffrenet scope, find from cache should still work', function (done) { - var id = uuidv4(); - var result1, result2; - TestModel.create({ - name: "limits", - id: id - }, defaultContext, function (err, data) { - if (err) { - return done(err); - } - result1 = Object.assign({}, data.toObject()); - TestModel.create({ - name: "gravity", - id: id - }, altContext, function (err, data) { - if (err) { - return done(err); - } - TestModel.find({ "where": { "id": id } }, defaultContext, function (err, data2) { - if (err) { - return done(err); - } - result2 = Object.assign({}, data2.toObject()); - expect(result1).not.to.be.null; - expect(result2).not.to.be.null; - expect(result1).to.deep.equal(result2); - expect(result1.__data === result2.__data).to.be.true; - }); - }); - }); - }); - }); - - after('change settings back', function(done) { - process.env.CONSISTENT_HASH = originalConsistentHash; - done(); - }); -}); diff --git a/test/integration-tests/integration-test-model.js b/test/integration-tests/integration-test-model.js deleted file mode 100644 index f2ab68a..0000000 --- a/test/integration-tests/integration-test-model.js +++ /dev/null @@ -1,423 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -//This File contains tests related to Model Definitions -//var chai = require('chai'); -//var expect = chai.expect; -var chalk = require('chalk'); -var app_url = process.env.APP_URL || 'http://localhost:3000/'; -process.env.NODE_TLS_REJECT_UNAUTHORIZED=0; -var request = require('supertest')(app_url); -//var mongoHost = process.env.MONGO_HOST || 'localhost'; -var accessToken; -var testUser = { - 'username': 'testadmin', - 'email': 'test@admin.com', - 'password': 'testadmin' -}; - -var testRoleMapping = { - "id": "testadmin", - "principalType": "testadmin", - "principalId": "testadmin", - "_isDeleted": false, - "roleId": "admin" -}; - -var testModel = { - 'name': 'StarWars', - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'cacheable': true, - 'properties': { - 'name': { - 'type': 'string', - 'unique': true, - 'min': 4, - 'max': 7 - }, - 'numericField1': { - 'type': 'number', - 'numericality': 'integer' - }, - 'numericField2': { - 'type': 'number', - 'absence': true - }, - 'clan': { - 'type': 'string', - 'required': true - }, - 'country': { - 'type': 'string', - 'notin': ['England'], - 'is': 8 - }, - 'gender': { - 'type': 'string', - 'in': ['Male', 'Female'], - 'required': false - }, - 'shipName': { - 'type': 'string', - 'pattern': '^[A-Za-z0-9-]+$' - } - }, - 'id': 'StarWars', - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} -}; - -var testData = [{ - 'name': 'Anakin', - 'numericField1': 10, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi', - 'shipName': 'Delta-7B' -}, { - 'name': 'Amidala', - 'numericField1': 10, - 'gender': 'Female', - 'country': 'Tatooine', - 'clan': 'Jedi' -}, { - 'name': 'Doku', - 'numericField1': 10, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Sith' -}]; - -var testModifiedModel = { - 'name': 'StarWars', - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'cacheable': true, - 'properties': { - 'model-name': { - 'type': 'string', - 'unique': true, - 'min': 4, - 'max': 7 - }, - 'numericField1': { - 'type': 'number', - 'numericality': 'integer' - }, - 'numericField2': { - 'type': 'number', - 'absence': true - }, - 'clan': { - 'type': 'string', - 'required': true - }, - 'country': { - 'type': 'string', - 'notin': ['England'], - 'is': 8 - }, - 'gender': { - 'type': 'string', - 'in': ['Male', 'Female'], - 'required': false - }, - 'shipName': { - 'type': 'string', - 'pattern': '^[A-Za-z0-9-]+$' - }, - 'reviewed': { - 'type': 'boolean' - } - }, - 'id': 'StarWars', - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} -}; - -describe(chalk.blue('integration-test-model'), function() { - this.timeout(60000); - - it('Checks server', function(done) { - request - .get('/') - .expect(200) - .end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Checks User Creation', function(done) { - var sendData = testUser; - request - .post('api/BaseUsers') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Checks Base Role Mapping Creation', function(done) { - var sendData = testRoleMapping; - request - .post('api/BaseRoleMappings') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('login using test-admin', function(done) { - var sendData = { - 'username': testUser.username, - 'password': testUser.password - }; - - request - .post('api/BaseUsers/login') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - accessToken = resp.body.id; - done(); - } - }); - }); - - it('Checks Model Creation with Property Validations and cacheable true', function(done) { - var sendData = testModel; - request - .post('api/ModelDefinitions?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should be able to post data successfully', function(done) { - var sendData = testData; - request - .post('api/StarWars?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should fail because data is invalid', function(done) { - var sendData = { - 'name': 'Anakin', - 'numericField1': 10, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi', - 'shipName': 'Delta-7B#' - }; - - request - .post('api/StarWars?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(422).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Should create the model with custom validations successfully', function(done) { - var sendData = { - 'name': 'ActiveShips', - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - }, - 'active': { - 'type': 'boolean', - 'required': true - } - }, - 'validations': [], - 'oeValidations': { - 'nameCheck': { - 'validateWhen': {}, - 'type': 'reference', - 'errorCode': 'ship-err-001', - 'refModel': 'StarWars', - 'refWhere': '{\"shipName\":\"{{name}}\"}' - } - }, - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - request - .post('api/ModelDefinitions?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should successfully insert the data in Child model', function(done) { - var sendData = { - 'name': 'Delta-7B', - 'active': true - }; - - request - .post('api/ActiveShips?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should fail to insert the data in child model', function(done) { - var sendData = { - 'name': 'Delta-8B', - 'active': true - }; - - request - .post('api/ActiveShips?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(422).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should create a personalization rule', function(done) { - var sendData = { - 'modelName': 'StarWars', - 'personalizationRule': { - 'sort': { - 'name': 'desc' - }, - 'filter': { - 'clan': 'Jedi' - }, - 'fieldValueReplace': { - 'gender': { - 'Male': 'M', - 'Female': 'F' - } - }, - 'fieldReplace': { - 'numericField1': 'field1' - } - }, - 'scope': { - 'clanType': 'Jedi' - } - }; - - request - .post('api/PersonalizationRules?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should be able to modify model at runtime-Step1:delete', function(done) { - request - .delete('api/ModelDefinitions/' + testModel.id + '?access_token=' + accessToken) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - xit('should be able to modify model at runtime-Step2:Create modified model', function(done) { - var sendData = testModifiedModel; - request - .post('api/ModelDefinitions?access_token=' + accessToken) - .set('tenant_id', 'test-tenant') - .set('REMOTE_USER', 'testUser') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - -}); \ No newline at end of file diff --git a/test/integration-tests/integration-test-user.js b/test/integration-tests/integration-test-user.js deleted file mode 100644 index 19c1ff7..0000000 --- a/test/integration-tests/integration-test-user.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -//This File contains tests related to Base Users -var chai = require('chai'); -var expect = chai.expect; -var chalk = require('chalk'); -var app_url = process.env.APP_URL || 'http://localhost:3000/'; -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; -var request = require('supertest')(app_url); -//var mongoHost = process.env.MONGO_HOST || 'localhost'; -var accessToken; -var invalidUser = { - 'username': 'test', - 'password': 'test', -}; -var adminUser = { - 'username': 'adminUser', - 'password': 'adminUser', - 'email': 'testadmin@ev.com', -}; - -describe(chalk.blue('integration-test-User'), function() { - this.timeout(60000); - - it('Check invalid User Creation', function(done) { - var sendData = invalidUser; - request - .post('api/BaseUsers') - .send(sendData) - .expect(422).end(function(err, resp) { - done(); - }); - }); - it('Check User Creation', function(done) { - var sendData = adminUser; - createdUser = adminUser; - request - .post('api/BaseUsers') - .send(sendData) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - userid = resp.body.id; - // createdUser = resp.body; - done(); - } - }); - }); - - it('Check invalid login', function(done) { - var postData = { - 'username': invalidUser.username, - 'password': invalidUser.password - }; - request - .post('api/BaseUsers/login') - .send(postData) - .expect(401).end(function(err, response) { - accessToken = response.body.id; - done(); - }); - }); - it('Check login', function(done) { - var postData = { - 'username': adminUser.username, - 'password': adminUser.password - }; - request - .post('api/BaseUsers/login') - .send(postData) - .expect(200).end(function(err, response) { - accessToken = response.body.id; - if (err) { - done(err); - } else { - done(); - } - }); - }); - xit('Check User Updation', function(done) { - createdUser.email = 'newemail@test.com' - request - .put('api/BaseUsers') - .send(createdUser) - .end(function(err, resp) { - if (err) { - done(err); - } else { - var updatedUser = resp.body; - expect(updatedUser.email).to.equal('newemail@test.com'); - done(); - } - }); - }); - - it('Check User Deletion', function(done) { - request - .delete('api/BaseUsers') - .send(userid) - .end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Check invalid User Deletion', function(done) { - request - .delete('api/BaseUsers') - .send(userid) - .end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Check invalid logout', function(done) { - request - .post('api/BaseUsers/logout') - .send() - .expect(500).end(function(err, response) { - done(); - }); - }); - it('Check logout', function(done) { - - request - .post('api/BaseUsers/logout?access_token=' + accessToken) - .send() - .expect(204).end(function(err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - xit('Check password complexity', function(done) { - request - .post('api/BaseUsers') - .send(invalidPwdUser) - .expect(200).end(function(err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - -}); \ No newline at end of file diff --git a/test/job-scheduler-service-tests/oe-cloud.yml b/test/job-scheduler-service-tests/oe-cloud.yml deleted file mode 100644 index 8c2de56..0000000 --- a/test/job-scheduler-service-tests/oe-cloud.yml +++ /dev/null @@ -1,93 +0,0 @@ -version: "3" -services: - mongo: - image: ${REGISTRY}/alpine-mongo:latest - networks: - - ${NETWORK_NAME} - - broadcaster: - image: ${REGISTRY}/oecloudio-broadcaster-server:latest - environment: - SERVICE_PORTS: "2345" - networks: - - ${NETWORK_NAME} - - routerservice: - image: ${REGISTRY}/evfpaas-router-service:latest - environment: - HASH_RING_REFRESH_TIME_INTERVAL: "10000" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - SERVICE_PORTS: "3000" - BROADCASTER_HOST : "broadcaster" - ROUTER_HOST: ${ROUTER} - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - - rabbitmq: - image: ${REGISTRY}/rabbitmq:3.6-management-alpine - environment: - VIRTUAL_HOST: "https://rabbitmq.${DOMAIN_NAME},rabbitmq.$DOMAIN_NAME" - SERVICE_PORTS: "15672" - networks: - - ${NETWORK_NAME} - - router_network - - scheduler: - entrypoint: ["node", "."] - image: ${REGISTRY}/oecloudio-oescheduler:latest - depends_on: - - mongo - - rabbitmq - environment: - VIRTUAL_HOST: "https://${SCHEDULER_NAME}.${DOMAIN_NAME},${SCHEDULER_NAME}.${DOMAIN_NAME}" - SERVICE_PORTS: "3001" - FORCE_SSL: "yes" - DISABLE_JOB_RUNNER: "true" - RABBITMQ_HOSTNAME: "rabbitmq" - JWT_FOR_ACCESS_TOKEN: "true" - extra_hosts: - - "${SCHEDULER_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - - web: - entrypoint: ["node", "."] - image: ${REGISTRY}/${ORIG_APP_IMAGE_NAME}:oe-scheduler-test - depends_on: - - mongo - - broadcaster - - scheduler - - routerservice - - rabbitmq - deploy: - mode: replicated - replicas: 1 - update_config: - delay: 60s - environment: - VIRTUAL_HOST: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME},${APP_IMAGE_NAME}.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - SCHEDULER_HOST: "scheduler" - CONSISTENT_HASH: "true" - SERVICE_NAME: "${APP_IMAGE_NAME}" - BROADCASTER_HOST: "broadcaster" - ORCHESTRATOR: "dockerSwarm" - APP_URL: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/api" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - ROUTER_HOST: ${ROUTER} - RABBITMQ_HOSTNAME: "rabbitmq" - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - -networks: - $NETWORK_NAME: - router_network: - external: true \ No newline at end of file diff --git a/test/job-scheduler-service-tests/prepare-test.js b/test/job-scheduler-service-tests/prepare-test.js deleted file mode 100644 index f527ddf..0000000 --- a/test/job-scheduler-service-tests/prepare-test.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var fs = require('fs'); - -var testNoteJs = 'module.exports = function (Model) { Model.prototype.changeTitle = function (title, ctx, callback) { Model.find({}, ctx, (err, notes) => { if (err) { return callback(err); } var counter = 0; notes.forEach(function (note) { note.updateAttribute("title", title, ctx, function (err) { if (err) { console.log(err); } counter = counter + 1; if (counter === 10) { callback(err); } }); }, this); }); }; Model.prototype.changeContentWithFail = function (content, ctx, callback) { var err = new Error("failing on purpose"); callback(err); }; };'; - -fs.writeFile("common/models/framework/test-note.js", testNoteJs, function(err) { - if(err) { - return process.exit(-1); - } -}); - -var testNoteJson = '{ "name": "TestNote", "base" : "BaseEntity", "strict" : false, "properties": { "title": { "type": "string" }, "content": { "type": "string" } } }'; - -fs.writeFile("common/models/framework/test-note.json", testNoteJson, function(err) { - if(err) { - return process.exit(-1); - } -}); - -fs.writeFileSync('server/model-config.orig', fs.readFileSync('server/model-config.json')); - -fs.readFile('server/model-config.json', 'utf8', function(oErr, sText) { - var r1 = sText.substr(sText.length - 4, sText.length - 1); - var result = sText.replace(r1, '},"TestNote": {"public": true,"dataSource": "db"}}'); - fs.writeFile('server/model-config.json', result, function (err) { - if(err) { - return process.exit(-1); - } - }); -}); diff --git a/test/job-scheduler-service-tests/scheduler-test.js b/test/job-scheduler-service-tests/scheduler-test.js deleted file mode 100644 index 1004c68..0000000 --- a/test/job-scheduler-service-tests/scheduler-test.js +++ /dev/null @@ -1,253 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var request = require('request'); -var loopback = require('loopback'); - -var chai = require('chai'); -var expect = chai.expect; -var chalk = require('chalk'); -var async = require('async'); - -const domainName = process.env.DOMAIN_NAME; - -const serviceName = process.env.APP_IMAGE_NAME; -const serviceHost = serviceName + '.' + domainName; -const baseUrl = 'https://' + serviceHost + '/api/'; - -const schedulerName = process.env.SCHEDULER_NAME; -const schedulerHost = schedulerName + '.' + domainName; -const baseSchedulerUrl = 'https://' + schedulerHost + '/api/'; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; - -var token; - -var defaultContext = { - ctx: { - tenantId: 'default', - remoteUser: 'admin' - } -}; - -describe(chalk.blue(''), function () { - before('login using admin', function (done) { - var loginUser = function (cb) { - var loginData = {'username':'admin','password':'admin'}; - request.post( - baseUrl + 'BaseUsers/login', { - json: loginData - }, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return cb(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('login using admin - success - ', body.id); - token = body.id; - return cb(); - } - ); - }; - - var createNotes = function (cb) { - var data = { - 'title': 'noteTitle', - 'content': 'noteContent' - }; - var instances = [1,2,3,4,5,6,7,8,9,10]; - async.each(instances, function (instance, eachCb) { - request.post( - baseUrl + 'TestNotes?access_token=' + token, { - json: data - }, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return eachCb(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('TestNote instances creation - success'); - return eachCb(); - } - ); - }, function (err) { - if (err) { - console.log('error in creating TestNote instance: ', err); - return cb(err); - } - console.log('all TestNote instances created successfully'); - return cb(); - }); - }; - - loginUser(function (err) { - if (err) { - console.log('err in login: ', err); - return done(err); - } - console.log('login using admin - success'); - createNotes(function (err) { - if (err) { - console.log('err in creating TestNote instances: ', err); - return done(err); - } - console.log('creating TestNote instances: success'); - return done(); - }); - }); - }); - - it('create one time job that is supposed to succeed and check it finished successfully', function (done) { - var date = new Date(); - var jobData = { - "jobModelName": "TestNote", - "jobFnName": "changeTitle", - "jobFnParams": ["My new Title"], - "frequency": "Once", - "jobDate": date, - "timeZone":"Asia/Jerusalem", - "priority": "1" - }; - request.post( - baseSchedulerUrl + 'Jobs?access_token=' + token, { - json: jobData - }, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Job instance creation - success', body.id); - return continueLogic(body.id, done); - } - ); - - var retries = 0; - var continueLogic = function (jobId, done) { - request.get( - baseSchedulerUrl + 'Monitorings?filter={"where":{"jobId": "' + jobId + '"}}&access_token=' + token, - {json: {}}, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Getting monitoring instances - success'); - if (body[0].status === 'Finished Processing') { - return finalCheck(done); - } - if (retries < 10) { - retries = retries + 1; - return setTimeout(continueLogic, 3000, jobId, done); - } - var err = new Error('too many checks'); - console.log(err); - return done(err); - } - ); - }; - - var finalCheck = function (done) { - request.get( - baseUrl + 'TestNotes?access_token=' + token, - {json: {}}, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Getting note instances - success'); - async.each(body, function (note, cb) { - expect(note.title).to.equal('My new Title'); - return cb(); - }, function (err) { - return done(err); - }); - } - ); - }; - }); - - it('create one time job that is supposed to fail and check it failed and the correct error is saved', function (done) { - var date = new Date(); - var jobData = { - "jobModelName": "TestNote", - "jobFnName": "changeContentWithFail", - "jobFnParams": ["My new Content"], - "frequency": "Once", - "jobDate": date, - "timeZone":"Asia/Jerusalem", - "priority": "1" - }; - request.post( - baseSchedulerUrl + 'Jobs?access_token=' + token, { - json: jobData - }, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Job instance creation - success', body.id); - return continueLogic(body.id, done); - } - ); - - var retries = 0; - var continueLogic = function (jobId, done) { - request.get( - baseSchedulerUrl + 'Monitorings?filter={"where":{"jobId": "' + jobId + '"}}&access_token=' + token, - {json: {}}, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Getting monitoring instances - success'); - if (body[0].status === 'Failed Processing') { - expect(body[0].errorMsg).to.equal('failing on purpose'); - return finalCheck(done); - } - if (retries < 10) { - retries = retries + 1; - return setTimeout(continueLogic, 3000, jobId, done); - } - var err = new Error('too many checks'); - console.log(err); - return done(err); - } - ); - }; - - var finalCheck = function (done) { - request.get( - baseUrl + 'TestNotes?access_token=' + token, - {json: {}}, - function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - return done(error || body.error); - } - expect(response.statusCode).to.equal(200); - console.log('Getting note instances - success'); - async.each(body, function (note, cb) { - expect(note.content).to.equal('noteContent'); - return cb(); - }, function (err) { - return done(err); - }); - } - ); - }; - }); -}); diff --git a/test/jwt-for-access-token-test.js b/test/jwt-for-access-token-test.js deleted file mode 100644 index 1fa31c7..0000000 --- a/test/jwt-for-access-token-test.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -chai.use(require('chai-things')); -var expect = chai.expect; -var app = bootstrap.app; -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -let api = defaults(supertest(app)); - -describe('JWT For Access Token', function(){ - - this.timeout(20000); - let accessToken; - before('Setting up JWT_FOR_ACCESS_TOKEN env variable', (done) => { - process.env.JWT_FOR_ACCESS_TOKEN = true; - done(); - }); - - before('Login', (done) => { - let testUser = { - username: 'testuser', - password: 'testuser123' - }; - bootstrap.login(testUser, (returnedToken) => { - expect(returnedToken).to.be.defined; - accessToken = returnedToken; - done(); - }); - }); - - it('Access User with id with JWT token passed as access_token query', (done) => { - expect(accessToken).to.be.defined; - // 20 is the userId of the test-user created in bootstrap. - let url = bootstrap.basePath + '/BaseUsers' + '/20' + '?access_token=' + accessToken; - api - .get(url) - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .expect(200) - .end((err, res) => { - if (err) return done(err); - expect(res).to.be.defined; - expect(res.body).to.be.defined; - expect(res.body.id).to.be.defined; - expect(res.body.username).to.be.defined; - // Converting to String & comparing the same, since id fields are string type in Postgres, Oracle. - expect(res.body.id.toString()).to.be.equal('20'); - expect(res.body.username).to.be.equal('testuser'); - done(); - }); - }); - - it('Access User with id with JWT token passed as x-jwt-assertion header', (done) => { - expect(accessToken).to.be.defined; - // 20 is the userId of the test-user created in bootstrap. - let url = bootstrap.basePath + '/BaseUsers' + '/20'; - api - .get(url) - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('x-jwt-assertion', accessToken) - .expect(200) - .end((err, res) => { - if (err) return done(err); - expect(res).to.be.defined; - expect(res.body).to.be.defined; - expect(res.body.id).to.be.defined; - expect(res.body.username).to.be.defined; - // Converting to String & comparing the same, since id fields are string type in Postgres, Oracle. - expect(res.body.id.toString()).to.be.equal('20'); - expect(res.body.username).to.be.equal('testuser'); - done(); - }); - }); - - after('Removing the JWT_FOR_ACCESS_TOKEN env variable', (done) => { - delete process.env.JWT_FOR_ACCESS_TOKEN; - done(); - }); - -}); \ No newline at end of file diff --git a/test/ldap-auth-tests/ldap-authentication-test.js b/test/ldap-auth-tests/ldap-authentication-test.js deleted file mode 100644 index 2cf392b..0000000 --- a/test/ldap-auth-tests/ldap-authentication-test.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var request = require('request'); - -var chai = require('chai'); -var expect = chai.expect; -var chalk = require('chalk'); -var async = require('async'); - -const serviceName = process.env.APP_IMAGE_NAME; -const domainName = process.env.DOMAIN_NAME; - -const serviceHost = serviceName + '.' + domainName; -const baseUrl = 'https://' + serviceHost; - -const johnLoginData = {'username': 'john', 'password': 'johnpass'}; - -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; - -var token; -var johnUserId; - -function login(loginData, cb) { - request.post(baseUrl + '/auth/ldap', { json: loginData }, function (error, response, body) { - if (error || body.error) { - console.log('error:', error || body.error); - cb(error || body.error); - } else { - expect(response.statusCode).to.equal(200); - expect(body.access_token).to.be.ok; - expect(body.userId).to.be.ok; - token = body.access_token; - cb(null, body.userId); - } - }); -} - -describe(chalk.blue(''), function () { - it('should login through ldap', function (done) { - this.timeout(10000); - console.log('Base Url is ', baseUrl); - login(johnLoginData, function(err, userId) { - done(err); - }); - }); - - it('should create roleMapping based on ldapRoleMapping', function (done) { - this.timeout(10000); - async.series([createRole, createLdapRoleMapping, johnLogin, assertRoleMapping], (err)=>{ - return done(err); - }); - }); -}); - -function createRole(cb) { - var role = { - name: 'customer', - id: 'customer' - }; - request.post(baseUrl + '/api/BaseRoles/?access_token=' + token, { json: role }, (error, response, body) => { - if (error || body.error) { - console.log('error:', error || body.error); - return cb(error || body.error); - } - expect(response.statusCode).to.equal(200); - cb(); - }); -} - -function createLdapRoleMapping(cb) { - var ldapRoleMapping = { - groupName: 'cn=bank-customers,ou=groups,dc=example,dc=org', - roles: ['customer'] - }; - request.post(baseUrl + '/api/LdapRoleMappings?access_token=' + token, { json: ldapRoleMapping }, (error, response, body) => { - if (error || body.error) { - console.log('error:', error || body.error); - return cb(error || body.error); - } - expect(response.statusCode).to.equal(200); - cb(); - }); -} - -function johnLogin(cb){ - login(johnLoginData, (err, userId) => { - if (err) { - return cb(err); - } - johnUserId = userId; - cb(); - }); -} - -function assertRoleMapping(cb) { - var queryString = { - access_token: token, - filter: { - where: { - roleId: 'customer' - } - } - }; - request.get(baseUrl + '/api/BaseRoleMappings', { qs: queryString, json: true }, (error, response, body) => { - if (error || body.error) { - console.log('error:', error || body.error); - return cb(error || body.error); - } - expect(response.statusCode).to.equal(200); - expect(body.length).to.equal(1); - expect(body[0].roleId).to.equal('customer'); - expect(body[0].principalId).to.equal(johnUserId); - return cb(); - }); -} diff --git a/test/ldap-auth-tests/oe-cloud.yml b/test/ldap-auth-tests/oe-cloud.yml deleted file mode 100644 index db5575d..0000000 --- a/test/ldap-auth-tests/oe-cloud.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: "3" -services: - mongo: - image: ${REGISTRY}/alpine-mongo:latest - networks: - - ${NETWORK_NAME} - - broadcaster: - image: ${REGISTRY}/evf-broadcaster-server:latest - environment: - SERVICE_PORTS: "2345" - networks: - - ${NETWORK_NAME} - - routerservice: - image: ${REGISTRY}/evfpaas-router-service:latest - environment: - HASH_RING_REFRESH_TIME_INTERVAL: "10000" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - SERVICE_PORTS: "3000" - BROADCASTER_HOST : "broadcaster" - ROUTER_HOST: ${ROUTER} - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - - web: - image: $REGISTRY/${APP_IMAGE_NAME}:latest - depends_on: - - mongo - - broadcaster - deploy: - mode: replicated - replicas: 1 - update_config: - delay: 60s - environment: - VIRTUAL_HOST: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME},${APP_IMAGE_NAME}.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - CONSISTENT_HASH: "true" - SERVICE_NAME: "${APP_IMAGE_NAME}" - BROADCASTER_HOST: "broadcaster" - ORCHESTRATOR: "dockerSwarm" - APP_URL: "https://${APP_IMAGE_NAME}.${DOMAIN_NAME}/api" - NODE_TLS_REJECT_UNAUTHORIZED: "0" - ROUTER_HOST: ${ROUTER} - NODE_ENV: "env" - DATASOURCES: '{"db": {"host": "mongo","port": 27017,"url": "mongodb://mongo:27017/db","database": "db","name": "db","connector": "mongodb","connectionTimeout": 50000}}' - PROVIDERS: '{"ldap": {"provider": "ldap","module": "passport-ldapauth","authScheme": "ldap","server": {"url": "ldap://ldap:389","bindDN":"cn=admin,dc=example,dc=org","bindCredentials": "admin","searchBase":"dc=example,dc=org","searchAttributes":["memberOf","cn","mail","givenName","sn","dn","uid"],"searchFilter": "(uid={{"{{username}}"}})"},"authPath": "/auth/ldap","setAccessToken": true,"json": true, "session":false}}' - - extra_hosts: - - "${APP_IMAGE_NAME}.${DOMAIN_NAME}:${HAPROXY}" - networks: - - ${NETWORK_NAME} - - router_network - - ldap: - image: $REGISTRY/openldap:cassibank - networks: - - ${NETWORK_NAME} - - -networks: - $NETWORK_NAME: - router_network: - external: true diff --git a/test/literal-test.js b/test/literal-test.js deleted file mode 100644 index c7f15c7..0000000 --- a/test/literal-test.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var async = require('async'); - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -function deleteAndCreate(model, items, callback) { - model.destroyAll({}, bootstrap.defaultContext, function () { - async.forEachOf(items, - function (item, m, callback2) { - model.create(item, bootstrap.defaultContext, function (e, rec) { - if (e) { - console.error(e.message); - } - callback2(); - }); - }, - function (err) { - if (err) { - throw err; - } - callback(); - }); - }); -} - -describe(chalk.blue('literal test'), function () { - this.timeout(20000); - - var data = [ - { - "key": "Auth Scheme", - "value": "Auth Scheme", - "scope": { - "lang": "en-US" - } - }, - { - "key": "External Id", - "value": "External Id", - "scope": { - "lang": "en-US" - } - }, - { - "key": "Profile", - "value": "Profile", - "scope": { - "lang": "en-US" - } - }, - { - "key": "rangeOverflow", - "value": "Must be max $max$ characters", - "scope": { - "lang": "en-US" - } - }, - { - "key": "rangeUnderflow", - "value": "Min $min$ allowed for $field$", - "placeholders": ["field", "min"], - "scope": { - "lang": "en-US" - } - } - ]; - - before('setup test data', function (done) { - deleteAndCreate(models.Literal, data, function (err1) { - done(err1); - }); - }); - - it('get locale data', function (done) { - - api.get('/api/Literals/render/en-US') - .set('Accept', 'application/json') - // .set('TENANT_ID', tenantId) - // .set('REMOTE_USER', 'testUser') - // .set('DEVICE_TYPE', 'android') - .expect(200).end(function (err, resp) { - if (err) { - return done(err); - } - var result = resp.body; - expect(result['Auth Scheme']).not.to.be.null; - done(); - }); - }); - - it('translates $xxx$ into placeholders', function (done) { - - api.get(bootstrap.basePath + '/Literals/render/en-US') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('lang', 'en-US') - .expect(200).end(function (err, resp) { - if (err) { - return done(err); - } - var result = resp.body; - - var record = result['rangeOverflow']; - expect(record).to.exist; - expect(record.placeholders).to.exist; - expect(record.placeholders.max).to.exist; - expect(record.placeholders.max.content).to.equal("$1"); - done(); - }); - }); - - it('If placeholders ordering is defined they are honoured', function (done) { - - api.get(bootstrap.basePath + '/Literals/render/en-US') - .set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .set('lang', 'en-US') - .expect(200).end(function (err, resp) { - if (err) { - return done(err); - } - var result = resp.body; - - var record = result['rangeUnderflow']; - expect(record).to.exist; - expect(record.placeholders).to.exist; - expect(record.placeholders.min).to.exist; - expect(record.placeholders.min.content).to.equal("$2"); - expect(record.placeholders.field).to.exist; - expect(record.placeholders.field.content).to.equal("$1"); - done(); - }); - }); - after('clean up', function (done) { - models.Literal.destroyAll({}, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete literals ', err, d); - return done(); - } - done(); - }); - }); - - - -}); \ No newline at end of file diff --git a/test/manual-scope-update.js b/test/manual-scope-update.js deleted file mode 100644 index 2b10f90..0000000 --- a/test/manual-scope-update.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var api = bootstrap.api; -var models = bootstrap.models; -var logger = require('oe-logger'); -var log = logger('data-personalization-test'); -var loopback = require('loopback'); -var async = require('async'); -var app = bootstrap.app; - -describe(chalk.blue('Manual Scope Update'), function () { - this.timeout(400000); - var modelName = 'ManualScopeUpdate'; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - options: { - "disableManualPersonalization":false - }, - properties: { - 'name': { - 'type': 'string', - 'unique': true - }, - 'description': { - 'type': 'string' - }, - 'discount': { - 'type': 'number', - 'default': 10 - } - }, - strict: false, - idInjection: true, - plural: modelName - }; - - var TestModel; - - var defaultTenantContext = { - ctx: { - tenantId: 'default', - remoteUser: 'system' - } - }; - - var allScopes = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - }, - fetchAllScopes: true - }; - - var accessToken; - - before('Create model', function (done) { - var query = { - where: { - name: modelName - } - }; - models.ModelDefinition.findOrCreate(query, modelDetails, defaultTenantContext, function (err, res, created) { - TestModel = loopback.findModel(modelName, bootstrap.defaultContext); - TestModel.purge({}, allScopes, function (err, info) { - done(); - }); - }); - }); - - before('Create Access Token', function (done) { - // accessToken belongs to test-tenant - // createAccessToken uses test-tenant - var user = bootstrap.defaultContext.ctx.remoteUser.username; - bootstrap.createAccessToken(user, function (err, token) { - accessToken = token; - done(); - }); - }); - - it('Create and then update manual scope ', function (done) { - var data = { - name: 'Test1', - description: 'No Scope', - discount: 20 - } - var url = bootstrap.basePath + '/' + modelName + '?access_token=' + accessToken; - api.post(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, result) { - expect(result.status).to.be.equal(200); - var rec1 = result.body; - url = bootstrap.basePath + '/' + modelName + '/' + rec1.id + '?access_token=' + accessToken; - var changedData = { - discount: 40, - _version: rec1._version, - scope: { - dimension: 'long' - } - }; - api.put(url) - .send(changedData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.status).to.be.equal(200); - expect(result.body).to.be.ok; - var rec2 = result.body; - expect(rec2.id).to.be.equal(rec1.id); - expect(rec2.scope.dimension).to.be.equal('long'); - changedData = { - discount: 60, - _version: rec2._version, - scope: { - dimension: 'small' - } - }; - api - .put(url) - .send(changedData) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('dimension', 'long') - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.status).to.be.equal(200); - expect(result.body).to.be.ok; - var rec3 = result.body; - expect(rec3.id).to.be.equal(rec1.id); - expect(rec3.scope.dimension).to.be.equal('small'); - - done(); - } - }); - }; - }); - }); - }); -}); diff --git a/test/middleware.json b/test/middleware.json new file mode 100644 index 0000000..f781bc3 --- /dev/null +++ b/test/middleware.json @@ -0,0 +1,37 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + } + }, + "session": { + }, + "auth": { + }, + "parse": { + }, + "routes:before": { + "loopback#rest": { + "paths": ["${restApiRoot}"] + } + }, + "files": { + "serve-static": { + "params": "$!../client" + } + }, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "strong-error-handler": {} + } +} diff --git a/test/misclaneous-test.js b/test/misclaneous-test.js deleted file mode 100644 index 52e021b..0000000 --- a/test/misclaneous-test.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -describe(chalk.blue('misclaneous-test'), function() { - this.timeout(10000); - - var accessToken = ''; - - before('prepare test data', function(done) { - var postData = { - 'username': 'admin', - 'password': 'admin' - }; - - var postUrl = baseUrl + '/BaseUsers/login'; - - // without jwt token - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'default') - .post(postUrl) - .send(postData) - .expect(200).end(function(err, response) { - - accessToken = response.body.id; - done(); - }); - }); - - it('switch tenant', function(done) { - var data = { - tenantId: 'new-tenant' - }; - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers/switch-tenant?access_token=' + accessToken; - api.set('Accept', 'application/json') - .post(postUrl) - .send(data) - .expect(200) - .end(function(err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.undefined; - expect(result.body.tenantId).to.be.equal('new-tenant'); - done(); - } - }); - }); -}); diff --git a/test/model-collection-test.js b/test/model-collection-test.js deleted file mode 100644 index e160e45..0000000 --- a/test/model-collection-test.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This Unit Test script tests the feature of the framework that allows - * a model variant to persist to the same mongodb collection as its parent variant. - * The test involves creating a parent model 'AAModel' which specifies its MongoDB - * collection as 'SomeCollection'. After this, another model 'BBModel' is created - * as a variant of AAModel, then another model 'CCModel' is created as a variant of - * BBModel, then another model 'DDModel' is created as a variant of AAModel, but - * with a MongoDB collection specified. - * The test passes if all models have a mongodb collection property automatically - * added (if not specified), and the collection of all variants is set to the - * parent's collection, if not specified, and remains the same if specified. - * - * - * Author: Ajith Vasudevan - */ - - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var async = require('async'); - -describe('Model Collection Test', function () { - - this.timeout(15000); - - var ModelDefinition = models.ModelDefinition; - var result1, result2, result3, result4; - - var TestModelDefinition1 = { - 'name': 'AAModel', - 'base': 'BaseEntity', - 'mongodb': { - 'collection': 'SomeCollection' - } - }; - - var TestModelDefinition2 = { - 'name': 'BBModel', - 'base': 'BaseEntity', - 'variantOf': 'AAModel' - }; - - var TestModelDefinition3 = { - 'name': 'CCModel', - 'base': 'BaseEntity' - }; - - var TestModelDefinition4 = { - 'name': 'DDModel', - 'base': 'BaseEntity', - 'variantOf': 'CCModel', - 'mongodb': { - 'collection': 'AnotherCollection' - } - }; - - before('Create Test Models and do model collection test', function (done) { - async.series([ - function clean(cb) { - deleteTestModelDefinitions(cb); - }, - function (cb) { - let context = { - ctx: { - tenantId: 'default' - } - } - ModelDefinition.create(TestModelDefinition1, context, function (err, data) { - if (err) { - console.log("Error creating TestModelDefinition1", err); - } - result1 = data; - cb(); - }); - }, - function (cb) { - ModelDefinition.create(TestModelDefinition2, bootstrap.defaultContext, function (err, data) { - if (err) { - console.log("Error creating TestModelDefinition2", err); - } - result2 = data; - cb(); - }); - }, - function (cb) { - let context = { - ctx: { - tenantId: 'default' - } - } - ModelDefinition.create(TestModelDefinition3, context, function (err, data) { - if (err) { - console.log("Error creating TestModelDefinition3", err); - } - result3 = data; - cb(); - }); - }, - function (cb) { - ModelDefinition.create(TestModelDefinition4, bootstrap.defaultContext, function (err, data) { - if (err) { - console.log("Error creating TestModelDefinition4", err); - } - result4 = data; - cb(); - }); - } - - - ], function () { - done(); - }); - }); - - - - after('Cleanup', function (done) { - deleteTestModelDefinitions(done); - }); - - - function deleteTestModelDefinitions(done) { - let context = { - ctx: { - tenantId: 'default' - } - } - async.parallel([ - function (cb) { - ModelDefinition.remove({ - 'clientModelName': 'AAModel' - }, context, function (err, info) { - if (err) { - console.log(err, info); - } - cb(); - }); - }, - function (cb) { - ModelDefinition.remove({ - 'clientModelName': 'BBModel' - }, bootstrap.defaultContext, function (err, info) { - if (err) { - console.log(err, info); - } - cb(); - }); - }, - function (cb) { - ModelDefinition.remove({ - 'clientModelName': 'CCModel' - }, context, function (err, info) { - if (err) { - console.log(err, info); - } - cb(); - }); - }, - function (cb) { - ModelDefinition.remove({ - 'clientModelName': 'DDModel' - }, bootstrap.defaultContext, function (err, info) { - if (err) { - console.log(err, info); - } - cb(); - }); - } - ], function () { - done(); - }); - - } - - it('Should automatically add the missing mongodb collection tags in a new model created using ModelDefinition API', function (done) { - expect(result1).not.to.be.null; - expect(result1.mongodb).not.to.be.null; - expect(result1.mongodb.collection).to.equal('SomeCollection'); - done(); - }); - - it('Should automatically add the missing mongodb collection tags in a new model variant of the first model', function (done) { - expect(result2).not.to.be.null; - expect(result2.mongodb).not.to.be.null; - expect(result2.mongodb.collection).to.equal('SomeCollection'); - done(); - }); - - it('Should automatically add the missing mongodb collection tags in a new model variant of the variant of the first model', function (done) { - expect(result3).not.to.be.null; - expect(result3.mongodb).not.to.be.null; - expect(result3.mongodb.collection).to.equal('CCModel'); - done(); - }); - - it('Should retain the mongodb collection tags as specified while creating a model', function (done) { - expect(result4).not.to.be.null; - expect(result4.mongodb).not.to.be.null; - expect(result4.mongodb.collection).to.equal('AnotherCollection'); - done(); - }); - -}); diff --git a/test/model-config.json b/test/model-config.json new file mode 100644 index 0000000..611894c --- /dev/null +++ b/test/model-config.json @@ -0,0 +1,45 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "./common/models", + "./test/common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "./common/mixins", + "../common/mixins", + "./mixins" + ] + }, + "User": { + "dataSource": "db" + }, + "AccessToken": { + "dataSource": "db", + "public": false + }, + "ACL": { + "dataSource": "db", + "public": false + }, + "RoleMapping": { + "dataSource": "db", + "public": false + }, + "Role": { + "dataSource": "db", + "public": false + }, + "Customer": { + "dataSource": "db", + "public": true + }, + "Spouse": { + "dataSource": "db", + "public": true + } +} diff --git a/test/model-definition-ACL-test.js b/test/model-definition-ACL-test.js deleted file mode 100644 index 8f6fb4e..0000000 --- a/test/model-definition-ACL-test.js +++ /dev/null @@ -1,426 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * UnitTest Cases for Dynamically Create Model with ACLs Using REST api's - * ACLs applied on model are: - * 1) User to be denied to findById - * 2) Users under Guest Role to be denied to create/insert data. - * 3) $everyone to deny get count. - * - * - * Steps involved : - * 1) Create two user. POST /BaseUsers - * 2) Create role : Guest POST /Roles - * 3) Map user2 to the Guest role using POST /Roles/{id}/principals api - * 4) Create model with above listed ACLs. POST /ModelDefinition - * 5) Login using User1 credentials. Post /BaseUser/login - * use the id(AccessToken). - * 6) Insert data into the model. use accessToken provided during login.POST /modelName?access_token= - * 7) Find the data Inserted using ID,should not be allowed asper ACL1. GET /modelName/{id}?access_token= - * 8) Logout User1 POST /BaseUsers/logout?access_token - * 9) Login User2 who is mapped to Guest role Post /BaseUser/login - * 10)Insert into model, should not be allowed asper ACL2 POST /modelName?access_token= - * 11)Logout user2 POST /BaseUsers/logout?access_token - * 12)Get the count of records in Model, should not allow asper ACL3 GET /modelName/counts - *@author sivankar jain - */ - -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var supertest = require('supertest'); -var defaults = require('superagent-defaults'); -var loopback = require('loopback'); -var baseUrl = bootstrap.basePath; -var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -var app = bootstrap.app; -var supertest = require('supertest'); -var api1 = supertest(app); - -var debug = require('debug')('model-definition-ACL-test'); - -describe(chalk.blue('model-definition-ACL'), function () { - after('destroy context', function (done) { - done(); - //commented as pre-defined roles and users gets deleted - /* - models.BaseUser.destroyAll(function () { - //console.log('model-definition-ACL-test clean up - BaseUser'); - }); - - var model = loopback.getModel('ModelDefinitionHistory'); - if (model) { - model.destroyAll(function (err, info) { - console.log('model-definition-ACL-test clean up - ModelDefinitionHistory'); - }); - } - - models.BaseRole.destroyAll(function () { - //console.log('model-definition-ACL-test clean up - ModelDefinition'); - }); - - models.BaseRoleMapping.destroyAll(function () { - //console.log('model-definition-ACL-test clean up - ModelDefinition'); - }); - - models.ModelDefinition.destroyAll(function () { - //console.log('model-definition-ACL-test clean up - ModelDefinition'); - done(); - }); - */ - - }); - - describe(chalk.yellow('Dynamically create model with ACL using ModelDefinition' + - ' and check if ACLs are applied --using REST APIs'), function () { - - this.timeout(20000); - var testUserAccessToken; - // Creating testuser access token since removed jwt-assertion middleware - // so that we can access the models which are created using bootstrap.defaultContext - // are based on testuesr and test-tenant. - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - var BaseUser = { - 'username': 'TestUser1', - 'email': 'TestUser1@ev.com', - 'tenantId': 'test-tenant', - 'password': 'password++' - }; - var userId1, userId2; - var modelName = 'TestRoleWithACLOne'; - var plural = 'TestRolesWithACLsss'; - var dataId; - var accessToken, accessToken2; - var roleId; - - var BaseUser2 = { - 'username': 'TestUser2', - 'email': 'TestUser2@ev.com', - 'tenantId': 'test-tenant', - 'password': 'password++' - }; - - it('create user1 -- using POST /BaseUsers ', function (done) { - api.post(baseUrl + '/BaseUsers') - .send(BaseUser) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.messages.name[0]))); - } else { - userId1 = res.body.id; - done(); - } - }); - }); - - it('create user2 -- using POST /BaseUsers ', function (done) { - - api.post(baseUrl + '/BaseUsers') - .send(BaseUser2) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.messages.name[0]))); - } else { - userId2 = res.body.id; - done(); - } - }); - }); - - it('Create Role ', function (done) { - var postData = { - name: 'Guest' - }; - var postUrl = baseUrl + '/BaseRoles'; - - api - .post(postUrl) - .send(postData) - .expect(200).end(function (err, res) { - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message))); - } else { - roleId = res.body.id; - done(); - } - }); - }); - - it('Map user2 to the Role', function (done) { - - var postData = { - 'principalType': 'USER', - 'principalId': userId2 - }; - - var postUrl = baseUrl + '/BaseRoles/' + roleId + '/principals'; - - api - .post(postUrl) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message))); - } else { - done(); - } - }); - }); - - it('Dynamically Create model with ACL for user ', function (done) { - - var aclUser = { - 'principalType': 'USER', - 'principalId': userId1, - 'permission': 'DENY', - 'property': 'findById' - }; - - var aclRole = { - 'principalType': 'ROLE', - 'principalId': 'Guest', - 'permission': 'DENY', - 'property': 'create' - }; - - var aclDenyEveryone = { - 'principalType': 'ROLE', - 'principalId': '$everyone', - 'permission': 'DENY', - 'property': 'count' - }; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': 'string' - }, - plural: plural, - acls: [] - }; - postData.acls.push(aclUser); - postData.acls.push(aclRole); - postData.acls.push(aclDenyEveryone); - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message))); - } - else { - var model = res.body; - expect(model).not.to.be.null; - expect(model.acls).to.deep.include.members(postData.acls); //.to.be.eql(postData.acls); - expect(model.properties).not.to.be.undefined; - - return done() - }; - }); - }); - - it('Login Test user1 to check for ACLs for type USER ', function (done) { - var postData = { - 'username': BaseUser.username, - 'password': BaseUser.password - }; - var postUrl = baseUrl + '/BaseUsers/login'; - - api - .post(postUrl) - .send(postData) - .expect(200).end(function (err, res) { - //console.log('respose data - ',res.body,res.header); - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - accessToken = res.body.id; - done(); - } - }); - - }); - - it('Insert into recently created model By USER 1', function (done) { - var postData = { - 'name': 'TestDataOne' - }; - var postUrl = baseUrl + '/' + plural + '?access_token=' + accessToken; - //console.log('postUrl - ',postUrl); - api - .post(postUrl) - .send(postData) - .end(function (err, res) { - expect(res.status, 200); - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - dataId = res.body.id; - done(); - } - }); - }); - - it('shoult not be Authorized to Find records for USER 1', function (done) { - var postUrl = baseUrl + '/' + plural + '/' + dataId + '?access_token=' + accessToken; - console.log(postUrl); - api1 - .get(postUrl) - .set('tenant_id', 'test-tenant') - .set('remote_user', BaseUser.username) - .send() - .end(function (err, res) { - expect(res.status, 401); - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - - }); - - it('Logout User 1', function (done) { - var postUrl = baseUrl + '/BaseUsers/logout?access_token=' + accessToken; - var api1 = defaults(supertest(app)); - api1 - .post(postUrl) - .expect(204).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - done(); - } - }); - - }); - - it('Login Test user2 to check for ACLs for type ROLE ', function (done) { - var postData = { - 'username': BaseUser2.username, - 'password': BaseUser2.password - }; - var postUrl = baseUrl + '/BaseUsers/login'; - - api - .post(postUrl) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - accessToken2 = res.body.id; - done(); - } - }); - - }); - - it('Should not be allowed to Insert data into model for USER 2 Guest Role ', function (done) { - var postData = { - 'name': 'TestDataTwoThree' - }; - var postUrl = baseUrl + '/' + plural + '?access_token=' + accessToken2; - - api1 - .post(postUrl) - .set('tenant_id', 'test-tenant') - .set('remote_user', BaseUser2.username) - .send(postData) - .expect(401).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Logout User 2', function (done) { - - var postUrl = baseUrl + '/BaseUsers/logout?access_token=' + accessToken2; - - api - .post(postUrl) - .expect(204).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - done(); - } - }); - }); - - it('Should not be allowed any one to get Count ', function (done) { - - var postUrl = baseUrl + '/' + plural + '/count'; - - api - .get(postUrl) - .set('tenant_id', 'test-tenant') - .set('remote_user', BaseUser2.username) - .send() - .expect(401).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - }); - - xit('create user using post /BaseUsers/signUp', function (done) { - var BaseUser = { - 'email': 'TestAdmin2@mycompany.com', - 'password': 'Admin2', - 'subscriptionType': 'Standard', - 'organizationName': 'infy' - }; - - api.post(baseUrl + '/BaseUsers/signUp') - .send(BaseUser) - .expect(200).end(function (err, res) { - //console.log('response body : ' + JSON.stringify(res.body,null,4)); - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.message))); - } else { - done(); - } - }); - }); - }); -}); \ No newline at end of file diff --git a/test/model-definition-inheritance-test.js b/test/model-definition-inheritance-test.js deleted file mode 100644 index e32d69b..0000000 --- a/test/model-definition-inheritance-test.js +++ /dev/null @@ -1,831 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - *This test file is for Inheritance testing for the model created using - * ModelDefinition with some Base model. - *@author sivankar jain - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; -var loopback = require('loopback'); -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -var debug = require('debug')('model-definition-test'); -var accessToken; -describe(chalk.blue('REST APIs - model-definition-Inheritance'), function () { - this.timeout(20000); - - after('destroy context', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - - describe(chalk.yellow('creating model with no properties and base as BaseEntity,' + - ' check for presense of BaseEntity properties and enter data into newly created model.'), function () { - before('login', function (done) { - bootstrap.createAccessToken(bootstrap.defaultContext.ctx.remoteUser.username, function (err, token) { - accessToken = token; - done(); - }); - }); - - it('creating model with no properties and base as BaseEntity, check for presense of BaseEntity properties ', - function (done) { - - var modelName = 'NoProps'; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {} - }; - api - .set('Accept', 'application/json') - .post(modelDefitnionUrl + "?access_token=" + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.messages.name[0]))); - } - var modelCreated = loopback.findModel(modelName, bootstrap.defaultContext); - expect(modelCreated).not.to.be.null; - expect(modelCreated.settings.acls).to.have.length(1); - expect(modelCreated.definition.properties).not.to.be.undefined; - expect(Object.keys(modelCreated.definition.properties)).to.include. - members(Object.keys(models.BaseEntity.definition.properties)); - return done(); - }); - - }); - - it('add empty data to newly created Model with no properties and get the same.', function (done) { - - var modelName = 'NoProps'; - var postUrl = bootstrap.basePath + '/' + modelName + "?access_token=" + accessToken; - var postData = {}; - - api - .post(postUrl) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.messages.name[0]))); - } else { - - var dataId = res.body.id; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.find({ - where: { - ID: dataId - } - }, bootstrap.defaultContext, function (err, data) { - - if (data && data.ID === dataId) { - done(); - } else { - done(err); - } - }); - } - }); - }); - - it('Test delete functionality of model Definition ', function (done) { - var modelName = 'NoProps'; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - var modelId = modeldefinition[0].id; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () {}); - - api - .del(modelDefitnionUrl + '/' + modelId + "?access_token=" + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - }); - - }); - - }); - - - describe(chalk.yellow('should create a new model with properties and use it as a base model,' + - ' check for presence of base model properties and its own properties'), function () { - - it('should create a new model to use as a base model with properties', function (done) { - - var modelName = 'TestMyCart'; - - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'id': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - debug('model ' + modelName + ' is available now, test case passed.'); - var modelCreated = loopback.findModel(modelName, bootstrap.defaultContext); - expect(modelCreated.definition.properties).not.to.be.null; - expect(modelCreated.definition.properties).not.to.be.undefined; - expect(Object.keys(modelCreated.definition.properties)).to.include. - members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(modelCreated.definition.properties)).to.include. - members(Object.keys(postData.properties)); - done(); - }); - - - api - .post(modelDefitnionUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - - it('create a new model with new properties not existing in base and base as TestMyCart,' + - '\n\t check for inherit properties and its own properties ', - function (done) { - - var modelName = 'MyCart'; - var baseModel = 'TestMyCart'; - var postData = { - name: modelName, - base: baseModel, - properties: { - 'describtion': { - 'type': 'string', - 'required': true - }, - 'price': { - 'type': 'number', - 'required': true - } - }, - filebased: false - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - debug('model ' + modelName + ' is available now, test case passed.'); - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model.definition.properties).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(model.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - - done(); - }); - - - api - .post(modelDefitnionUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - if (res && res.body) debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - - }); - - - it('Delete MyCart model', function (done) { - - var modelName = 'MyCart'; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err || modeldefinition.length === 0) { - done(err || new Error('unable to delete model ' + modelName + '. No model with such name Exist.')); - } else { - var modelId = modeldefinition[0].id; - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - } - }); - - }); - - it('Delete TestMyCart model ', function (done) { - - var modelName = 'TestMyCart'; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err || modeldefinition.length === 0) { - done(err || new Error('unable to delete model ' + modelName + '. No model with such name Exist.')); - } else { - var modelId = modeldefinition[0].id; - //console.log('model details',modelDetails); - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - } - }); - - }); - - }); - - describe(chalk.yellow('Create model with same existing property of base'), function (done) { - - it('should create a new model to use as a base model with properties', function (done) { - - var modelName = 'TestMyCartOne'; - - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'age': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - debug('model ' + modelName + ' is available now, test case passed.'); - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model.definition.properties).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - done(); - }); - - - api - .post(modelDefitnionUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - - it('create a new model with new properties not existing in base and base as TestMyCart1,' + - '\n\t check for inherit properties and its own properties ', - function (done) { - - - var modelName = 'MyCartOne'; - var baseModel = 'TestMyCartOne'; - var postData = { - name: modelName, - base: baseModel, - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'age': { - 'type': 'number', - 'required': true - } - }, - filebased: false - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - debug('model ' + modelName + ' is available now, test case passed.'); - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model.definition.properties).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(model.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - - done(); - }); - - - api - .post(modelDefitnionUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - if (res && res.body) debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - - }); - - - it('Delete MyCartOne model', function (done) { - - var modelName = 'MyCartOne'; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err || modeldefinition.length === 0) { - done(err || new Error('unable to delete model ' + modelName + '. No model with such name Exist.')); - } else { - var modelId = modeldefinition[0].id; - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - } - }); - - }); - - it('delete TestMyCartOne model ', function (done) { - - var modelName = 'TestMyCartOne'; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err || modeldefinition.length === 0) { - done(err || new Error('unable to delete model ' + modelName + '. No model with such name Exist.')); - } else { - var modelId = modeldefinition[0].id; - //console.log('model details',modelDetails); - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - } - }); - - }); - - }); - - describe(chalk.yellow('creating model Non existing base'), function (done) { - - it('should not allow, creating model with non existing base ', function (done) { - var modelName = 'NoBaseModel'; - var baseModel = 'NoModel'; - var postData = { - name: modelName, - base: baseModel, - properties: {} - }; - api - .post(modelDefitnionUrl + '?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - - }); - - }); - -}); - -describe(chalk.blue('model-definition-Inheritance Programmatically'), function () { - - //var loopbackContext; - this.timeout(20000); - var modelName = 'TestTable'; - var postData = { - name: modelName, - base: 'BaseEntity', - plural: modelName + 's', - properties: {} - }; - - var modelDetails; - - describe(chalk.yellow('creating model with no properties and base as BaseEntity,' + - ' check for presense of BaseEntity properties and enter data into newly created model.'), function () { - - after('destroy context', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - - it('creating model with no properties and base as BaseEntity, check for presense of BaseEntity properties ', - function (done) { - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - var modelCreated = loopback.findModel(modelName, bootstrap.defaultContext); - expect(modelCreated).not.to.be.null; - expect(modelCreated.definition.properties).not.to.be.undefined; - expect(Object.keys(modelCreated.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - done(); - } - }); - }); - - it('add empty data to newly created Model with no properties and get the same.', function (done) { - - var postData = {}; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - var dataId = res.id; - model.findById(dataId, bootstrap.defaultContext, function (err, data) { - if (data && data.id === dataId) { - done(); - } else { - done(err); - } - }); - } - }); - }); - - it('Delete model ' + modelName, function (done) { - var modelId; - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - modelId = modeldefinition[0].id; - var version = modeldefinition[0]._version; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) {}); - - models.ModelDefinition.destroyById(modelId, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - models.ModelDefinition.findById(modelId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - } - }); - }); - }); - - describe(chalk.yellow('should create a new model with properties and use it as a base model,' + - ' check for presence of base model properties and its own properties'), function () { - - after('destroy context', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - var modelName1 = 'TestTableOne'; - - it('should create a new model to use as a base model with properties', function (done) { - - postData.properties.name = 'string'; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - done(); - } - }); - }); - - it('create a new model with new properties not existing in base and base as TestMyCart,' + - '\n\t check for inherit properties and its own properties ', - function (done) { - - var postData1 = { - name: modelName1, - base: modelName, - plural: modelName1 + 's', - properties: { - 'age': 'Number' - } - }; - - models.ModelDefinition.create(postData1, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - var model1 = loopback.findModel(modelName1, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(model.definition.properties)); - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(postData1.properties)); - done(); - } - }); - }); - - it('Delete model ' + modelName, function (done) { - - var modelId; - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - modelId = modeldefinition[0].id; - var version = modeldefinition[0]._version; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) {}); - - models.ModelDefinition.destroyById(modelId, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - models.ModelDefinition.findById(modelId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - } - }); - }); - }); - - describe(chalk.yellow('Create model with same existing property of base '), function (done) { - - after('destroy context', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - var modelName1 = 'TestTableOne'; - - it('should create a new model to use as a base model with properties', function (done) { - - postData.properties.name = 'string'; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - console.log(err); - done(err); - } else { - modelDetails = res; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - done(); - } - }); - }); - - it('create a new model with new properties not existing in base and base as TestMyCart,' + - '\n\t check for inherit properties and its own properties ', - function (done) { - - var postData1 = { - name: modelName1, - base: modelName, - plural: modelName1 + 's', - properties: { - 'name': 'string' - } - }; - - models.ModelDefinition.create(postData1, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - console.log('xxx', JSON.stringify(err)); - done(err); - } else { - modelDetails = res; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - var model1 = loopback.findModel(modelName1, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(model.definition.properties)); - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(postData1.properties)); - done(); - } - }); - }); - - - it('Delete model ' + modelName1, function (done) { - - var modelId; - models.ModelDefinition.find({ - where: { - name: modelName1 - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - modelId = modeldefinition[0].id; - var version = modeldefinition[0]._version; - var model1 = loopback.findModel(modelName1, bootstrap.defaultContext); - model1.destroyAll({}, bootstrap.defaultContext, function (err, res) {}); - - models.ModelDefinition.destroyById(modelId, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - done(err); - } else { - models.ModelDefinition.findById(modelId, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - } - }); - - }); - }); - - describe(chalk.yellow('creating model Non existing base'), function (done) { - - it('should not allow to creating model with non existing base ', function (done) { - var modelName = 'NoBaseModel'; - var baseModel = 'NoModel'; - var postData = { - name: modelName, - base: baseModel, - properties: {} - }; - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(); - /*should not allow, therefore it will throw error when - someone is trying to create model with non existing base.*/ - } else { - done(new Error('model with non existing base, Created')); - } - }); - }); - - }); - -}); \ No newline at end of file diff --git a/test/model-definition-relation-test.js b/test/model-definition-relation-test.js deleted file mode 100644 index 3b2c104..0000000 --- a/test/model-definition-relation-test.js +++ /dev/null @@ -1,504 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * Test cases for Relationship testing between two models, models are created by - * ModelDefiniton model. - * @author sivankar jain - */ - - -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; - -var baseUrl = bootstrap.basePath; -var modelDefitnionUrl = baseUrl + '/ModelDefinitions'; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -var debug = require('debug')('model-definition-relation-test'); - -describe(chalk.blue('model-definition-relation using REST APIs'), function () { - this.timeout(20000); - - var testUserAccessToken; - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - after('destroy context', function (done) { - var model = loopback.getModel('ModelDefinitionHistory'); - if (model) { - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - //console.log('model-definition-ACL-test clean up - ModelDefinitionHistory'); - }); - } - - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - //console.log('model-definition-ACL-test clean up - ModelDefinition'); - done(); - }); - }); - - - describe(chalk.yellow('Create a model with Relation to a existing table, ' + - 'insert data into each table and retrive the same using API parentModel/{id}/relationModel'), function (done) { - var modelName = 'Category'; - var baseModel = 'BaseEntity'; - var dataID, subDataID; - var flag = 0; - - it('create model', function (done) { - - var modelName = 'subCategory'; - var postData = { - name: modelName, - base: baseModel, - plural: 'subCategories', - properties: { - 'subName': { - 'type': 'string', - 'required': true - }, - 'CategoryId': { - 'type': 'string', - 'required': true - } - } - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - //console.log(models[modelName].settings); - debug('model ' + modelName + ' is available now, test case passed.'); - flag++; - done(); - }); - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } - }); - - }); - - it('creating model with Relation to a existing table ', function (done) { - - if (flag && flag === 1) { - var postData = { - name: modelName, - base: baseModel, - plural: 'Categories', - properties: { - 'Name': { - 'type': 'string', - 'required': true - } - }, - relations: { - 'subCategories': { - 'type': 'hasMany', - 'model': 'subCategory', - 'foreignKey': 'CategoryId' - } - } - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - expect(Object.keys(model.settings.relations)). - to.include.members(Object.keys(postData.relations)); - //console.log(models[modelName].settings); - debug('model ' + modelName + ' is available now, test case passed.'); - flag++; - done(); - }); - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } - }); - } else { - done(new Error('Test case failed as the previous case failed')); - } - }); - - it('add data to the model', function (done) { - - if (flag && flag === 2) { - var postData = { - Name: 'Food_Items' - }; - var postUrl = baseUrl + '/Categories'; - api - .post(postUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - dataID = res.body.id; - done(); - flag++; - } - }); - } else { - done(new Error('Test case failed as the previous case failed')); - } - }); - - it('Should allow to add data using relation API model/{id}/relatedModel ', function (done) { - - if (dataID && flag && flag === 3) { - var postUrl = baseUrl + '/Categories/' + dataID + '/subCategories'; - var postData = { - 'subName': 'subItem1', - 'CategoryId': dataID - }; - //console.log('posturl', postUrl); - api - .post(postUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - //console.log('posturl',postUrl,'\nres.body',res.body,'\nerr',err); - if (err) { - done(err); - } else { - subDataID = res.body.id; - //console.log("---------------",res.body.error); - flag++; - done(); - } - }); - } else { - done(new Error('Test case failed as the previous case failed')); - } - }); - - it('Should get data using relation API model/{id}/relatedModel/{fk} ', function (done) { - - if (subDataID && flag && flag === 4) { - var postUrl = baseUrl + '/Categories/' + dataID + '/subCategories' + '/' + subDataID; - api - .get(postUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send() - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - //console.log('posturl',postUrl,'\nres.body',res.body,'\nerr',err); - if (err) { - done(err); - } else { - done(); - } - }); - } else { - done(new Error('Test case failed as the previous case failed')); - } - }); - - - it('Test delete functionality of model Definition ', function (done) { - var modelName = 'subCategory'; - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - return done(err); - } - var modelId = modeldefinition[0].id; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - //console.log('Clearing data from ' + modelName + ' model.'); - }); - //console.log('model details',modelDetails); - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + testUserAccessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - }); - - }); - - it('Test delete functionality of model Definition ', function (done) { - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (err) { - return done(err); - } - var modelId = modeldefinition[0].id; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - //console.log('Clearing data from ' + modelName + ' model.'); - }); - //console.log('model details',modelDetails); - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + testUserAccessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message.name[0]))); - } - }); - }); - }); - - }); - }); - -}); - - -describe(chalk.blue('model-definition-relation Programmatically'), function () { - this.timeout(20000); - - describe(chalk.yellow('Create a model with Relation to a existing table,' + - ' insert data into each table and retrive the same '), function (done) { - - after('destroy context', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - - var modelName = 'Category'; - var modelName1 = 'subCategory'; - var baseModel = 'BaseEntity'; - var dataID, subDataID; - - it('create submodel', function (done) { - - var postData = { - name: modelName1, - base: baseModel, - plural: 'subCategories', - properties: { - 'subName': { - 'type': 'string', - 'required': true - }, - 'CategoryId': { - 'type': 'string', - 'required': true - } - } - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - var model1 = loopback.findModel(modelName1, bootstrap.defaultContext); - expect(model1).not.to.be.null; - expect(model1.definition.properties).not.to.be.undefined; - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model1.definition.properties)). - to.include.members(Object.keys(postData.properties)); - done(); - } - }); - - }); - - it('creating model with Relation to a existing table ', function (done) { - - var postData = { - name: modelName, - base: baseModel, - plural: 'Categories', - properties: { - 'Name': { - 'type': 'string', - 'required': true - } - }, - relations: { - 'subCategories': { - 'type': 'hasMany', - 'model': 'subCategory', - 'foreignKey': 'CategoryId' - } - } - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - expect(Object.keys(model.settings.relations)). - to.include.members(Object.keys(postData.relations)); - done(); - } - }); - }); - - it('add data to the model', function (done) { - var postData = { - Name: 'Food_Items' - }; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - - dataID = res.id; - model.findById(dataID, bootstrap.defaultContext, function (err, data) { - debug('response body : ' + JSON.stringify(data, null, 4)); - // console.log(data); - if (data && data.id === dataID) { - done(); - } else { - done(err); - } - }); - } - }); - - }); - - it('Should allow to add data using relation API model/{id}/relatedModel ', function (done) { - var postData = { - 'subName': 'subItem1', - 'CategoryId': dataID - }; - var model1 = loopback.findModel(modelName1, bootstrap.defaultContext); - model1.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - - subDataID = res.id; - model1.findById(subDataID, bootstrap.defaultContext, function (err, data) { - debug('response body : ' + JSON.stringify(data, null, 4)); - if (data && data.id === subDataID) { - done(); - } else { - done(err); - } - }); - } - }); - }); - - it('Should get data for given relation ', function (done) { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.find({ - include: 'subCategories' - }, bootstrap.defaultContext, function (err, data) { - debug('response body : ' + JSON.stringify(data, null, 4)); - //console.log(JSON.stringify(data,null,'\t')); - if (data) { - expect(data).not.to.be.null; - expect(data).not.to.be.undefined; - expect(data).not.to.be.empty; - expect(data[0]['subCategories']).not.to.be.null; - expect(data[0]['subCategories']).not.to.be.empty; - expect(data[0]['subCategories']).not.to.be.undefined; - done(); - } else { - done(err); - } - }); - }); - - - it('Delete test data', function (done) { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - model.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - //console.log('Clearing data from ModelDefinition model.'); - done(); - }); - }); - }); - - }); - }); - -}); diff --git a/test/model-definition-test.js b/test/model-definition-test.js deleted file mode 100644 index f3b9e73..0000000 --- a/test/model-definition-test.js +++ /dev/null @@ -1,637 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * this file test, basic Post and update of a model using model definition. the - * model could be with or without properties, can two model with same name or - * plural be posted etc. - * - * @author sivankar jain - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; -var log = require('oe-logger')('model-definition-test'); - - -var chai = require('chai'); -chai.use(require('chai-things')); -var api = bootstrap.api; - -var debug = require('debug')('model-definition-test'); - -describe(chalk.blue('model-definition-test'), function () { - this.timeout(20000); - - var testUserAccessToken; - - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - after('destroy context', function (done) { - var model = loopback.getModel('ModelDefinitionHistory'); - if (model) { - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - log.debug(log.defaultContext(), 'model-definition-ACL-test clean up - ModelDefinitionHistory'); - }); - } - - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - log.debug(log.defaultContext(), 'model-definition-ACL-test clean up - ModelDefinition'); - done(); - }); - - }); - - describe(chalk.yellow('Dynamically creating model using REST APIs '), function (done) { - - var tenantId = 'test-tenant'; - - it('should allow creating model with no properties', function (done) { - var modelName = 'NoProps'; - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - debug('model ' + modelName + ' is available now, test case passed.'); - done(); - }); - var postData = { - name: modelName, - base: 'BaseEntity', - plural: modelName, - properties: {} - }; - - api - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error))); - } - }); - }); - - it('should not allow creating duplicate model (model with same name) ', function (done) { - - var modelName = 'NoProps'; - //var list = []; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {} - }; - - api - .set('TENANT_ID', tenantId) - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(422).end(function (err, res) { - if (err) { - debug('response body.error details : ' + JSON.stringify(err, null, 4)); - done(err); - } else { - debug('response body.error details : ' + JSON.stringify(res.body, null, 4)); - done(); - } - - }); - }); - - it('should not allow creating two model with same Plural ', function (done) { - var modelName = 'NoProps'; - //var list = []; - var postData = { - name: modelName + 'sss', - base: 'BaseEntity', - plural: modelName, - properties: {} - }; - - api - .set('TENANT_ID', tenantId) - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(422).end(function (err, res) { - debug('response body.error details : ' + JSON.stringify(res.body.error.details, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - }); - - xit('should update model using ModelDefinition try to add ACLs ', function (done) { - // commented as model definition is not even created and we are trying to fin logic needs change or will refer to old code TODO on monday - var modelName = 'NoProps'; - var modelId; - var modelDetails = {}; - - var acl = { - 'principalType': 'ROLE', - 'principalId': 'Admin', - 'permission': 'ALLOW', - 'property': 'create' - }; - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.settings.acls).to.deep.include.members([acl]); - expect(model.properties).to.be.undefined; - debug('model ' + modelName + ' is available now, test case passed.'); - done(); - }); - - models.ModelDefinition.find({ - where: { - name: 'NoProps' - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - modelId = modeldefinition[0].id; - modelDetails = modeldefinition[0]; - modelDetails.acls = []; - modelDetails.acls.push(acl); - - api - .set('TENANT_ID', tenantId) - .put(modelDefitnionUrl + '/' + modelId + '?access_token=' + testUserAccessToken) - .send(modelDetails) - .expect(200).end(function (err, res) { - //console.log(err,res.body); - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error.details.message))); - } - - }); - }); - - }); - - it('Test delete functionality of model Definition ', function (done) { - //var modelName = 'NoProps'; - - models.ModelDefinition.find({ - where: { - name: 'NoProps' - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - var modelId = modeldefinition[0].id; - var version = modeldefinition[0]._version; - //console.log('model details',modelDetails); - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + testUserAccessToken) - .expect(200).end(function (err, res) { - console.log('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error))); - } - }); - }); - }); - }); - - it('should not allow creating model propertie filebased = true ', function (done) { - var modelName = 'NoProps'; - //var list = []; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {}, - filebased: true - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send(postData) - .expect(500).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should not allow overriding filebased model', function (done) { - var modelName = 'ModelDefinition'; // ModelDefinition itself is file based model. - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {} - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'test-tenant') - .set('remote_user', 'unitTest') - .send(postData) - .expect(422).end(function (err, res) { - if (err) { - done(err); - } else { - expect(res.body).not.to.be.null; - expect(res.body).not.to.be.undefined; - var responseBody = JSON.parse(JSON.stringify(res.body)); - expect(responseBody.error).not.to.be.null; - expect(responseBody.error).not.to.be.undefined; - debug('response body : ' + JSON.stringify(res.body, null, 4)); - done(); - } - }); - }); - - it('should create a new model with properties and make it available in app', function (done) { - - var modelName = 'TestMyCart'; - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - debug('model ' + modelName + ' is available now, test case passed.'); - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model.definition.properties).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - //debug('model '+modelName+' properties.',models[modelName].definition.properties); - done(); - }); - - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'id': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - }); - - }); - - it('delete TestMyCart model details from ModelDefinition db ', function (done) { - - var modelName = 'TestMyCart'; - //var list = []; - - models.ModelDefinition.find({ - where: { - name: modelName - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - var modelId = modeldefinition[0].id; - - api - .del(modelDefitnionUrl + '/' + modelId + '?access_token=' + testUserAccessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - // after delete find same model with ID it should not be present. - models.ModelDefinition.find({ - where: { - id: modelId - } - }, bootstrap.defaultContext, function (err, modeldefinition) { - if (!err && (modeldefinition.length === 0)) { - done(); - } else if (err || res.body.error) { - done(err || (new Error(res.body.error))); - } - }); - }); - }); - }); - - var excludedModelList = ['Model', 'PersistedModel', 'User', 'AccessToken', 'ACL', 'RoleMapping', 'Role']; - - excludedModelList.forEach(function (modelName) { - it('should not allow creating model with name - ' + modelName, function (done) { - - var postData = { - name: modelName, - base: 'BaseEntity', - plural: modelName, - properties: {} - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(422).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err) { - done(err); - } else { - done(); - } - }); - }); - }); - - }); - - describe(chalk.yellow('Dynamically creating model -- Programmatically '), function (done) { - - var modelName = 'NoPropP'; - var postData = { - name: modelName, - base: 'BaseEntity', - plural: modelName + 's', - properties: {} - }; - var modelDetails; - - it('should allow creating model with no properties', function (done) { - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - modelDetails = res; - done(); - } - }); - }); - - - it('should not allow creating duplicate model (model with same name) ', function (done) { - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(err, null, 4)); - if (err) { - done(); //if err test case pass. - } - //should throw err when trying to create duplicate model. - else { - if (JSON.stringify(modelDetails.id) !== JSON.stringify(res.id)) - done(new Error('Model created with same name. Test Case failed. ')); - else - done(); - } - }); - }); - - it('should not allow creating two model with same Plural ', function (done) { - var modelName = 'TestTableOneTwo'; - var postData = { - name: modelName, - base: 'BaseEntity', - plural: 'NoPropPs', - properties: {} - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(err, null, 4)); - if (err) { - done(); //if err test case pass. - } else { - done(new Error('Model created with same Plural. Test Case failed. ')); - } - }); - }); - - it('should not allow overriding filebased model', function (done) { - var modelName = 'ModelDefinition';// ModelDefinition itself is file based model. - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {} - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('err body : ' + JSON.stringify(err, null, 4)); - if (err) { - done(); //if err test case pass. - } else { - done(new Error('Model created with same Plural. Test Case failed. ')); - } - }); - }); - - xit('Update model using ModelDefinition try to add ACLs ', function (done) { - - var acl = { - 'principalType': 'ROLE', - 'principalId': 'Admin', - 'permission': 'ALLOW', - 'property': 'create' - }; - modelDetails.acls = []; - modelDetails.acls.push(acl); - - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.settings.acls).to.deep.include.members([acl]); - expect(model.properties).to.be.undefined; - debug('model ' + modelName + ' is available now, test case passed.'); - done(); - }); - - models.ModelDefinition.upsert(modelDetails, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - } - }); - }); - - xit('Test delete functionality of model Definition destoryById ', function (done) { - - var id = modelDetails.id; - var version = modelDetails._version; - models.ModelDefinition.destroyById(id, bootstrap.defaultContext, function (err) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('should allow creating model propertie filebased = true ', function (done) { - var modelName = 'fileBaseTrueP'; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: {}, - filebased: true - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - expect(res).not.to.be.null; - expect(res.properties).not.to.be.undefined; - //expect(Object.keys(models[modelName].definition.properties)).to.include.members(Object.keys(models.BaseEntity.definition.properties)); - done(); - - } - }); - }); - - it('should create a new model with properties and make it available in app', function (done) { - - var modelName = 'TestMyCartP'; - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'id': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(err); - } else { - modelDetails = res; - var model = loopback.findModel(modelName, bootstrap.defaultContext); - expect(model).not.to.be.null; - expect(model.definition.properties).not.to.be.undefined; - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(models.BaseEntity.definition.properties)); - expect(Object.keys(model.definition.properties)). - to.include.members(Object.keys(postData.properties)); - done(); - } - }); - }); - - it('Find all model details in ModelDefinition ', function (done) { - models.ModelDefinition.find({}, bootstrap.defaultContext, function (err, models) { - if (err) { - done(err); - } else { - expect(models).not.to.be.null; - expect(models).not.to.be.undefined; - done(); - } - }); - }); - - xit('delete model details from ModelDefinition using DestroyAll ', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - - var excludedModelList = ['Model', 'PersistedModel', 'User', 'AccessToken', 'ACL', 'RoleMapping', 'Role']; - - excludedModelList.forEach(function (modelName) { - it('should not allow creating model with name - ' + modelName, function (done) { - - var postData = { - name: modelName, - base: 'BaseEntity', - plural: modelName + 's', - properties: {} - }; - - models.ModelDefinition.create(postData, bootstrap.defaultContext, function (err, res) { - debug('response body : ' + JSON.stringify(res, null, 4)); - if (err) { - done(); //if err test case pass. - } - //should throw err when trying to create duplicate model. - else { - done(new Error('Model created with name Model. Test Case failed. ')); - } - }); - }); - }); - }); -}); - -xdescribe(chalk.blue('model-definition-test variantOf'), function () { - - var modelSchema1 = { - name: 'hello', - base: 'BaseEntity' - }; - - var modelSchema2 = { - name: 'hey', - base: 'BaseEntity', - variantOf: 'hello' - }; - - before('Create variant models', function (done) { - models.ModelDefinition.create([modelSchema1, modelSchema2], bootstrap.defaultContext, function (err, res) { - done(err); - }); - }); - - it('test getVariantOf', function (done) { - models.ModelDefinition.getVariantOf('hello', function (err, model) { - if (err) { - done(err); - } else { - expect(typeof model).to.be.equal('function'); - done(); - } - }); - }); - -}); diff --git a/test/model-definition-test2.js b/test/model-definition-test2.js deleted file mode 100644 index 424cf52..0000000 --- a/test/model-definition-test2.js +++ /dev/null @@ -1,269 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var api = bootstrap.api; -var debug = require('debug')('model-definition-test'); - - -describe(chalk.blue('model-definition - Programmatic'), function () { - after('cleaning up', function (done) { - this.timeout(20000); - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function (err, result) { - console.log('Clean Up - Destroyed ModelDefinition'); - done(); - }); - }); - - it('should programmatically create a new model and make it available in app', function (done) { - - var modelName = 'ShoppingCartTestP'; - - models.ModelDefinition.create({ - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - }, - 'id': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - done(err); - } else { - // There should not be any error while adding the new model metadata to the ModelDefinition table - expect(err).to.be.not.ok; - // The new model should be created in Loopback - expect(app.models[modelName]).not.to.be.null; - done(); - } - }); - - }); -}); - - -describe(chalk.blue('model-definition - REST'), function () { - - var testUserAccessToken; - - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - after('cleaning up', function (done) { - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function (err, result) { - console.log('Clean Up - Destroyed ModelDefinition'); - done(); - }); - }); - - it('should RESTfully create a new model and make it available in app', function (done) { - var modelName = 'ShoppingCartTestR'; - var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; - - var postData = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - } - } - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .send(postData) - .expect(200).end(function (err, res) { - if (err) { - console.log(err); - done(err); - } else { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - expect(res.body.name).to.be.equal(modelName); - done(); - } - }); - }); - -}); - - -describe(chalk.blue('tenant-model-definition - REST'), function () { - - //var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; - var tenantID; - var tenant = 'tenantTest'; - var modelName = 'ShoppingCartTestR'; - var plural = 'ShoppingCartTestRs'; - - this.timeout(15000); - var testUserAccessToken; - - before('Create Test User Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUserAccessToken = returnedAccesstoken; - done(); - }); - }); - - before('setup --for tenant name added with model name', function (done) { - var url = bootstrap.basePath + '/Tenants' + '?access_token='+ testUserAccessToken; - var tenantDetails = { - 'tenantId': tenant, - 'tenantName': 'Testtenant', - 'datasourceName': 'db' - }; - api - .post(url) - .send(tenantDetails) - .expect(200).end(function (err, res) { - if (err) { - console.log(err); - done(err); - } else { - tenantID = res.body.id; - done(); - } - }); - }); - - after('cleaning up', function (done) { - var model = loopback.getModel('ModelDefinitionHistory'); - if (model) { - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - //console.log('model-definition-ACL-test clean up - ModelDefinitionHistory'); - }); - } - var context = { - ctx: { - tenantId: tenant - } - }; - var ShoppingCartModel = loopback.getModel(modelName, bootstrap.defaultContext); - var model2 = loopback.getModel(ShoppingCartModel.modelName + 'History', bootstrap.defaultContext); - if (model2) { - model2.destroyAll({}, bootstrap.defaultContext, function (err, info) { - //console.log('model-definition-ACL-test clean up - '+modelName+'History'); - }); - } - - models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function (err, result) { - models.Tenant.destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - console.log(err); - } - ShoppingCartModel.destroyAll({}, bootstrap.defaultContext, function (err, info) { - done(); - - }); - }); - }); - }); - - it('should RESTfully create a new model with tenant as tenantId and make it available in app', function (done) { - var modelDefitnionUrl = bootstrap.basePath + '/ModelDefinitions'; - - var postData = { - name: modelName, - base: 'BaseEntity', - plural: plural, - properties: { - 'name': { - 'type': 'string', - 'required': true - } - } - }; - - api - .post(modelDefitnionUrl + '?access_token=' + testUserAccessToken) - .set('tenant_id', tenant) - .send(postData) - .expect(200).end(function (err, res) { - if (err) { - console.log(err); - done(err); - } else { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - expect(res.body.name).to.be.equal(modelName); - //console.log(res.body); - done(); - } - }); - }); - - it('should insert data into new model with context set, and url without tenant', function (done) { - var Url = bootstrap.basePath + '/' + plural; - - var postData = { - 'name': 'test Data' - }; - - api - .post(Url + '?access_token=' + testUserAccessToken) - .set('tenant_id', tenant) - .send(postData) - .expect(200).end(function (err, res) { - if (err) { - console.log(err); - done(err); - } else { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - expect(res.body.name).to.be.equal(postData.name); - done(); - } - }); - }); - - it('should get all records without setting context and url with tenant', function (done) { - var Url = bootstrap.basePath + '/' + plural; - - api - .get(Url + '?access_token=' + testUserAccessToken) - .set('tenant_id', 'tenantTest') - .send() - .expect(200).end(function (err, res) { - if (err) { - console.log(err); - done(err); - } else { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - expect(res.body).to.have.length(1); - //console.log(res.body); - done(); - } - }); - }); - -}); diff --git a/test/model-definition-validation-test.js b/test/model-definition-validation-test.js deleted file mode 100644 index b09c8b8..0000000 --- a/test/model-definition-validation-test.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This Unit Test script tests the feature of the framework that - * checks the validity of a model before creating the model. - * If the model is valid then only it is created. - * Validity of model is checked by checking validity of all - * the expressions(validateWhen, mustache expression, expression language expression, etc.). - * Name of the properties of the model also can not be any of the following invalid values like['isValid'] - * - * Author: Sambit Kumar Patra - */ - - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = bootstrap.chai; -var expect = chai.expect; -var models = bootstrap.models; -var ModelDefinition = models.ModelDefinition; - -describe(chalk.blue('Model Definition Validation Test'), function () { - - this.timeout(20000); - - it('Should fail to create a new model using ModelDefinition as oeValidation rule has wrong validateWhen expression as well as wrong mustache query and prop1 has wrong validateWhen expression', function (done) { - var TestModelDefinition = { - 'name': 'ModelA', - 'base': 'BaseEntity', - 'plural': 'ModelAs', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'prop1': { - 'type': 'string', - 'required': true, - 'validateWhen': { - 'required' : '@ = j' - } - } - }, - 'validations': [], - 'oeValidations': { - 'exprCheck': { - 'validateWhen': '@p = @i.prop1', - 'type': 'reference', - 'errorCode': 'expr-err-001', - 'refModel': 'ModelB', - 'refWhere': '{\'a\' : \'b\'};{p = y}' - } - }, - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - ModelDefinition.create(TestModelDefinition, bootstrap.defaultContext, function (err, model) { - expect(err).not.to.be.undefined; - expect(err).to.have.lengthOf(3); - done(); - }); -}); - - it('Should fail to create a new model using ModelDefinition as oeValidation rule has wrong expression clause as well as prop1 has wrong validateWhen expression', function (done) { - var TestModelDefinition = { - 'name': 'ModelB', - 'base': 'BaseEntity', - 'plural': 'ModelBs', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'prop1': { - 'type': 'string', - 'required': true, - 'validateWhen': { - 'required' : 'm = j < ' - } - } - }, - 'validations': [], - 'oeValidations': { - 'exprCheck': { - 'validateWhen': '@i.prop1 !== undefined', - 'type': 'custom', - 'errorCode': 'expr-err-001', - 'expression': '@mLocation.companyCode == @i.buyerCompanyCode' - } - }, - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - ModelDefinition.create(TestModelDefinition, bootstrap.defaultContext, function (err, model) { - expect(err).not.to.be.undefined; - expect(err).to.have.lengthOf(2); - done(); - }); - }); - - it('Should fail to create a new model using ModelDefinition as property name is an invalid value', function (done) { - var TestModelDefinition = { - 'name': 'ModelC', - 'base': 'BaseEntity', - 'plural': 'ModelCs', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'isValid': { - 'type': 'string', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - ModelDefinition.create(TestModelDefinition, bootstrap.defaultContext, function (err, model) { - expect(err).not.to.be.undefined; - expect(err).to.have.lengthOf(1); - done(); - }); - }); -}); diff --git a/test/model-feel-belongs-to-relation-test.js b/test/model-feel-belongs-to-relation-test.js deleted file mode 100644 index 5408f30..0000000 --- a/test/model-feel-belongs-to-relation-test.js +++ /dev/null @@ -1,194 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('model-rule-belongsTo-relation-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); -var models = bootstrap.models; -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; -var fs = require('fs'); - -var Customer, Order; - -describe(chalk.blue('model-rule-belongsTo-relation-test'), function() { - before('creating the parent model', function(done) { - var data = { - name: 'XCustomer', - properties: { - name: { - type: 'string', - id: true - }, - amount: { - type: 'object' - } - } - }; - - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - var testModel = loopback.getModel(data.name, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - expect(testModel.modelName).to.equal('XCustomer-test-tenant'); - Customer = testModel; - - done(err); - }); - }); - - before('creating the child model', function(done) { - var data = { - name: 'XOrder', - properties: { - gate: { - type: 'string' - } - }, - relations: { - cust: { - type: 'belongsTo', - model: 'XCustomer' - } - } - }; - - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - var testModel = loopback.getModel(data.name, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - expect(testModel.modelName).to.equal('XOrder-test-tenant'); - Order = testModel; - done(err); - }); - }); - - var custId; - // var orderId; - before('creating some dummy data in XCustomer', function(done) { - var data = { - name: 'Ankur', - amount: { - value: 100, - unit: 'INR' - } - }; - - Customer.create(data, bootstrap.defaultContext, function(err, result) { - // expect(err).to.be.null; - // expect(result.name).to.equal(data.name); - // custId = result.id; - // done(); - if (err) { - done(err) - } - else { - expect(err).to.be.null; - expect(result.name).to.equal(data.name); - custId = result.id; - done(); - } - }); - }); - - before('creating some dummy data in Order', function(done) { - var data = { - gate: 'foo', - custId: 'Ankur' - }; - - Order.create(data, bootstrap.defaultContext, function(err, result) { - - if (err) { - done(err) - } - else { - // console.dir(err); - expect(err).to.be.null; - expect(result.date).to.equal(data.date); - // console.dir(result); - // orderId = result.id; - done(); - } - }); - }); - - before('creating a decision table', function(done) { - var DecisionTable = models.DecisionTable; - var data = { - name: 'TestDecision', - document: { - documentName: 'fetch_relations.xlsx', - // documentData: 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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' - // documentData : 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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' - // documentData: 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,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' - documentData : prefix + fs.readFileSync(__dirname + '/model-rule-data/fetch_relations.xlsx').toString('base64') - } - }; - - DecisionTable.create(data, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err); - } - else { - expect(result).to.not.be.null; - expect(result.name).to.equal(data.name); - done(); - } - }) - }); - - it('should fetch the relation in the decision table correctly', function(done){ - // debugger; - var DecisionTable = models.DecisionTable; - Order.findOne({ gate: 'foo' }, bootstrap.defaultContext, function(err, orderRecord) { - if (err) { - done(err) - } - else { - // console.log('foo'); - expect(orderRecord).to.not.be.null; - expect(orderRecord.gate).to.equal('foo'); - expect(orderRecord.cust).to.not.be.null; - orderRecord.cust(bootstrap.defaultContext, function(err, relatedRecord) { - // console.dir(relatedRecord); - expect(relatedRecord).to.not.be.null; - expect(relatedRecord.name).to.equal('Ankur'); - // done(); - // console.dir(bootstrap.defaultContext); - // orderRecord.options = bootstrap.defaultContext; - // orderRecord.options.modelName = Order.modelName; - var payload = orderRecord.__data; - payload.options = bootstrap.defaultContext; - payload.options.modelName = Order.modelName; - - DecisionTable.exec('TestDecision', payload, bootstrap.defaultContext, function(err, dtResult) { - if (err) { - done(err) - } - else { - // console.dir(dtResult); - expect(dtResult["customer name"]).to.equal(100); - done(); - } - }); - }); - // done(); - } - }); - }); - - // after(function(){ - // //model-feel-belongs-to-relation.js - // // debugger; - // }); -}); diff --git a/test/model-feel-belongs-to-self-relation-test.js b/test/model-feel-belongs-to-self-relation-test.js deleted file mode 100644 index d254259..0000000 --- a/test/model-feel-belongs-to-self-relation-test.js +++ /dev/null @@ -1,162 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect -var util = require('util'); -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('model-rule-belongsTo-self-relation-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); -var models = bootstrap.models; -var fs = require('fs'); -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; - -var People; - -describe(chalk.blue('model-rule-belongsTo-self-relation-test'), function() { - before('creating the parent model', function(done) { - var data = { - name: 'People', - properties: { - name: 'string', - age: 'number', - department: 'string', - designation: 'string', - pid: { - type: 'number', - id: true - } - }, - relations: { - manager: { - type: 'belongsTo', - model: 'People' - } - } - }; - - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - var testModel = loopback.getModel(data.name, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - expect(testModel.modelName).to.equal('People-test-tenant'); - People = testModel; - - done(err); - }); - }); - - var custId; - // var orderId; - before('creating some dummy data in People', function(done) { - var data = JSON.parse(fs.readFileSync(__dirname + '/model-rule-data/peoples.json', { encoding: 'utf8'})); - - People.create(data, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err) - } - else { - expect(err).to.be.null; - // console.dir(result); - expect(result.length).to.equal(9); - // custId = result.id; - done(); - } - }); - }); - - before('creating a decision table that validates this', function(done) { - - var docData = prefix + fs.readFileSync(__dirname + '/model-rule-data/validate_entry.xlsx').toString('base64'); - - var data = { - name: 'validateEntry', - document: { - documentName: 'validate_entry.xlsx', - documentData: docData - } - }; - - var DecisionTable = models.DecisionTable; - - DecisionTable.create(data, bootstrap.defaultContext, function(err) { - done(err); - }); - - }); - - - before('attach the model rule', function(done) { - var data = { - modelName: People.modelName, - validationRules: ['validateEntry'] - }; - - var ModelRule = models.ModelRule; - - ModelRule.create(data, bootstrap.defaultContext, function(err) { - done(err); - }); - }); - - it('should disallow the entry for an invalid people record', function(done){ - var data = { - name: 'Rijesh', - age: 26, - designation: 'manager', - department: 'hr', - managerId: 3, - pid: 10 - }; - // debugger; - People.create(data, bootstrap.defaultContext, function(err) { - // debugger; - expect(err).to.not.be.null; - // console.dir(err); - // console.log(util.inspect(err, { showHidden: true, depth: null })); - expect(err.details).to.be.defined; - expect(err.details.messages).to.be.defined; - expect(err.details.messages.DecisionTable).to.be.array; - expect(err.details.messages.DecisionTable.length).to.equal(1); - var errMessage = err.details.messages.DecisionTable[0]; - expect(errMessage).to.be.string; - expect(errMessage).to.equal('manager should not be: josie'); - done(); - }); - }); - - it('should allow the entry for a valid people record', function(done){ - var data = { - name: 'Rijesh', - age: 26, - designation: 'manager', - department: 'hr', - managerId: 2, - pid: 10 - }; - // debugger; - People.create(data, bootstrap.defaultContext, function(err, result) { - // debugger; - // done(err); - if (err) { - done(err) - } - else { - expect(result.name).to.equal(data.name); - done(); - } - }); - }); - // after(function(){ - // //model-feel-belongs-to-self-relation.js - // // debugger; - // }); -}); diff --git a/test/model-feel-decision-table-blank-object-payload.js b/test/model-feel-decision-table-blank-object-payload.js deleted file mode 100644 index 1017abe..0000000 --- a/test/model-feel-decision-table-blank-object-payload.js +++ /dev/null @@ -1,278 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('model-rule-belongsTo-relation-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); -var models = bootstrap.models; -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; -var fs = require('fs'); -var util = require('util'); -var inspect = (obj) => util.inspect(obj, { showHidden: true, depth: null }) -var Customer; - -describe(chalk.blue('model-feel-decision-table-blank-object-payload-test'), function() { - before('creating the model', function(done) { - var data = { - name: 'Customer2', - properties: { - name: { - type: 'string', - id: true - }, - amount: { - type: 'object' - } - } - }; - - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - var testModel = loopback.getModel(data.name, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - expect(testModel.modelName).to.equal('Customer2-test-tenant'); - Customer = testModel; - - done(err); - }); - }); - - before('inserting some initial data', function(done) { - var data = [ - { - name: 'foo', - amount: { - value: 250, - currency: 'INR' - } - }, - { - name: 'foo1', - amount: { - value: 250, - currency: 'US' - } - }, - { - name: 'foo3', - amount: {} - } - ]; - - Customer.create(data, bootstrap.defaultContext, done); - }); - - before('get convinced that model data insertion does not throw an error for insertion of record with amount as blank string', function(done) { - var data = { - name: 'foo4', - amount: "" - }; - - Customer.create(data, bootstrap.defaultContext, function(err) { - // console.dir(err); - expect(err).to.be.null; - done(); - }); - }); - - before('create a decision table', function(done) { - debugger; - var binData = fs.readFileSync(__dirname + '/model-rule-data/blank_object.xlsx'); - var docData = prefix + binData.toString('base64'); - var binData2 = fs.readFileSync(__dirname + '/model-rule-data/blank_object2.xlsx'); - var docData2 = prefix + binData2.toString('base64'); - - var binData3 = fs.readFileSync(__dirname + '/model-rule-data/blank_object3.xlsx'); - var docData3 = prefix + binData3.toString('base64'); - - var data = [ - { - name: 'TestDecision2', - document: { - documentName: 'blank_object.xlsx', - documentData : docData - } - }, - { - name: 'TestDecision3', - document: { - documentName: 'blank_object2.xlsx', - documentData: docData2 - } - }, - { - name: 'TestDecision4', - document: { - documentName : 'blank_object3.xlsx', - documentData: docData3 - } - } - ]; - - var DecisionTable = models.DecisionTable; - // console.log(DecisionTable.modelName) - - // DecisionTable.create(data, bootstrap.defaultContext, function(err) { - // if (err) { - // // console.dir(err) - // done(err); - // } - // else { - // done(); - // } - // }); - - var promises = data.map(d => new Promise((resolve, reject) => { - DecisionTable.create(d, bootstrap.defaultContext, function(err) { - if (err) { - reject(err) - } - else { - resolve(); - } - }); - })); - - Promise.all(promises).then((results) => done()).catch(done); - }); - - it('should execute the decision table rule correctly - test 1', function(done) { - var DecisionTable = models.DecisionTable; - Customer.findOne({ where: { name: 'foo'}}, bootstrap.defaultContext, function(err, result) { - expect(result.name).to.equal('foo'); - expect(result.amount.value).to.equal(250); - expect(result.amount.currency).to.equal('INR'); - // console.dir(result); - // result.options = bootstrap.defaultContext; - // result.options.modelName = Customer.modelName; - // debugger; - var payload = result.__data; - payload.options = bootstrap.defaultContext; - payload.options.modelName = Customer.modelName; - - DecisionTable.exec('TestDecision2', payload, bootstrap.defaultContext, function(err, dtResult) { - if (err) { - // console.log('error') - // console.dir(err); - done(err) - } - else { - console.log('pass') - // console.dir(dtResult); - expect(dtResult).to.be.array; - expect(dtResult[0].errMessage).to.be.true; - done(); - } - // expect(dtResult) - - }); - }); - }); - - - it('should fail to execute the decision table rule correctly - test 2 - because we are trying to fetch non-existent property on an object', function(done) { - var DecisionTable = models.DecisionTable; - Customer.findOne({ where: { name: 'foo3'}}, bootstrap.defaultContext, function(err, result) { - - expect(result.name).to.equal('foo3'); - var payload = result.__data; - payload.options = bootstrap.defaultContext; - payload.options.modelName = Customer.modelName; - - DecisionTable.exec('TestDecision3', payload, bootstrap.defaultContext, function(err, dtResult) { - if (err) { - // console.log('error') - // console.dir(err); - done(err) - } - else { - // console.log('pass') - // console.dir(dtResult); - expect(dtResult).to.be.array; - expect(dtResult[0].errCode).to.equal('JS_FEEL_ERR'); - done(); - - // conclusion: we cannot find the presence/absence - // like this we need an external function for this - // use case. - } - // expect(dtResult) - - }); - }); - }); - - // note: add the necessary external function config. - // In this test, we have added this in test\bootstrap.js - - it('should execute the decision table correctly', function(done) { - var DecisionTable = models.DecisionTable; - var executor = function(payload) { - return new Promise((resolve, reject) => { - DecisionTable.exec('TestDecision4', payload, bootstrap.defaultContext, function(err, dtResult) { - if (err) { - // console.log('ValidationResult:', err); - reject(err) - } - else { - // console.log('reject') - // console.log(arguments); - resolve(dtResult); - } - }) - }) - }; - // debugger; - Customer.find({ }, bootstrap.defaultContext, function(err, results) { - // console.log(inspect(results)) - // expect(results.map).to.be.function; - if (err) { - done(err); - } - else { - - // expect(results.length).to.equal(4); - // console.log('records:', inspect(results)); - var promises = results.map( r => { - var pl = r.__data; - pl.options = bootstrap.defaultContext; - pl.options.modelName = Customer.modelName; - return executor(pl); - }); - - - Promise.all(promises).then(function(responses) { - - // console.log('responses:', inspect(responses)); - responses.forEach(resp => { - // expect(resp[0].errorCode).to.not.equal('JS_FEEL_ERR'); - if(resp.length) { - expect(resp[0].errCode).to.not.equal('JS_FEEL_ERR') - } - }); - done(); - }).catch(e => { - // console.dir(e); - done(e); - }); - } - - }); - }); - - // after(function(){ - // //model-feel-decision-table-blank-object-payload-test.js - // debugger; - // }); -}); diff --git a/test/model-feel-execution-logging.js b/test/model-feel-execution-logging.js deleted file mode 100644 index 072dc8e..0000000 --- a/test/model-feel-execution-logging.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('model-rule-execution-logging'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); -var models = bootstrap.models; -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; -var fs = require('fs'); -var util = require('util'); -var inspect = (obj) => util.inspect(obj, { showHidden: true, depth: null }) -var Customer; - -describe(chalk.blue('model-feel-execution-logging'), function() { - before('creating the model', function(done) { - var data = { - name: 'LoanBank', - properties: { - name: { - type: 'string', - id: true - }, - employment: { - type: 'object' - }, - amount: 'number', - type: 'string', // requested product - } - }; - - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - var testModel = loopback.getModel(data.name, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - expect(testModel.modelName).to.equal('LoanBank-test-tenant'); - Customer = testModel; - - done(err); - }); - }); - - before('create a decision table', function(done) { - // debugger; - var binData = fs.readFileSync(__dirname + '/model-rule-data/eligibilityUSA.xlsx'); - var docData = prefix + binData.toString('base64'); - - var data = [ - { - name: 'Eligibility', - document: { - documentName: 'eligibilityUSA.xlsx', - documentData : docData - } - } - ]; - - var DecisionTable = models.DecisionTable; - - DecisionTable.create(data, bootstrap.defaultContext, function(err) { - if (err) { - done(err); - } - else { - done(); - } - }); - }); - - it('positive case', function(done) { - var DecisionTable = models.DecisionTable; - var payload = { - name: 'Ashish', - employment : { - monthlyIncome: 200, - yearsOfExperience: 7 - }, - type: 'PERSONAL_LOAN', - amount: 950 - }; - - - DecisionTable.exec('Eligibility', payload, bootstrap.defaultContext, function(err, data) { - expect(data.eligibility).to.equal(payload.amount); - done(); - }); - }); - - // after(function(){ - // //model-feel-execution-logging.js - // // debugger; - // }) -}); diff --git a/test/model-personalization-test.js b/test/model-personalization-test.js deleted file mode 100755 index 6f1aca8..0000000 --- a/test/model-personalization-test.js +++ /dev/null @@ -1,580 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/*global - require,before,after,it,describe -*/ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var api = bootstrap.api; -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); - -/// This test case demonstrates model personalization -/// there are two models - Employee and EmployeeAddress where EmployeeAddress is child of Employee - -/// 1. We will create these two models by posting into ModelDefinition with global scope and create relations among them. -/// 2. We will create data in these models using implicite composte as icici user and citi user -/// 3. we will see data is exclusive so that citi will not see icici data and vice a versa -/// 4. we will personalized Employee model for icici -/// 5. we will see the effect of it as data will start going to another personalized model -/// 6. we will personalized EmployeeAddress model for citi -/// 7. we will see effect particulalry for related model personalization -/// 8. we will also check same through Web API. - - -describe(chalk.blue('Model Personalization test'), function () { - - this.timeout(60000); - // globalscope , iciciscope and citiscope - var globalCtx = { - ignoreAutoScope: true, - ctx: {} - }; - - var iciciCtx = { - ctx: { - tenantId: 'icici', - remoteUser: 'iciciuser' - } - }; - - var citiCtx = { - ctx: { - tenantId: 'citi', - remoteUser: 'citiuser2' - } - }; - - var citiUser = { - 'username': 'citiuser2', - 'password': 'password++', - 'email': 'asdsad@gmail.com', - 'tenantId': 'citi' - }; - - var token = bootstrap.createJWToken(citiUser); - - var citiapi = defaults(supertest(bootstrap.app)); - - var testUsertoken; - - before('Create testuser Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUsertoken = returnedAccesstoken; - done(); - }); - }); - - before('setup test data', function (done) { - bootstrap.createTestUser(citiUser, 'ev-admin', function () { - - // this will be in default tenant scope - models.ModelDefinition.create({ - 'name': 'Employee', - 'idInjection': false, - 'base': 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'required': true - } - }, - 'relations': { - 'address': { - 'type': 'hasMany', - 'model': 'EmployeeAddress', - 'foreignKey': 'EmployeeId' - } - }, - 'filebased': false, - 'acls': [{ - 'principalType': 'ROLE', - 'principalId': '$everyone', - 'permission': 'ALLOW', - 'accessType': '*' - }] - }, globalCtx, function (err, model) { - expect(err).to.be.not.ok; - - models.ModelDefinition.create({ - name: 'EmployeeAddress', - 'idInjection': false, - base: 'BaseEntity', - properties: { - 'city': { - 'type': 'string', - 'required': true - } - }, - 'relations': {}, - filebased: false - }, globalCtx, function (err2, model2) { - expect(err2).to.be.not.ok; - done(); - }); - }); - }); - - - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: 'Employee' - }, bootstrap.defaultContext, function (err) { - if (err) { - return done(err); - } - models.ModelDefinition.destroyAll({ - name: 'EmployeeAddress' - }, globalCtx, function (err) { - if (err) { - return done(err); - } - loopback.findModel('Employee', iciciCtx).destroyAll({}, globalCtx, function (err) { - if (err) { - return done(err); - } - loopback.findModel('EmployeeAddress', iciciCtx).destroyAll({}, globalCtx, function (err) { - if (err) { - return done(err); - } - models.ModelDefinition.destroyAll({}, globalCtx, function (err) { - if (err) { - return done(err); - } - return done(); - }); - }); - }); - }); - }); - }); - - it('Populate data as Icicic - 2 Employee record should be created and 2 address records each should be created', function (done) { - var Employee = loopback.getModel('Employee', bootstrap.defaultContext); - Employee.create([{ - 'name': 'Tom', - 'id': 1, - 'address': [{ - 'city': 'Denver', - 'id': 11 - }, { - 'id': 12, - 'city': 'Frankfort' - }] - }, { - 'name': 'Harry', - 'id': 2, - 'address': [{ - 'city': 'London', - 'id': 21 - }, { - 'id': 22, - 'city': 'Paris' - }] - }], iciciCtx, function (err, results) { - if (err) { - return done(err); - } - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0].__data).to.have.property('address'); - expect(results[0].name).to.equal('Tom'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Denver'); - expect(results[0].__data.address[1].city).to.equal('Frankfort'); - expect(results[1]).to.have.property('name'); - expect(results[1]).to.have.property('id'); - expect(results[1].__data).to.have.property('address'); - expect(results[1].name).to.equal('Harry'); - expect(results[1].__data.address[0]).to.have.property('city'); - expect(results[1].__data.address[0].city).to.equal('London'); - expect(results[1].__data.address[1].city).to.equal('Paris'); - done(); - }); - }); - - it('Model Personalization Populate data as Citi - 1 Employee record should be created and 2 address records should be created', function (done) { - var Employee = loopback.getModel('Employee', bootstrap.defaultContext); - Employee.create([{ - 'name': 'John', - 'id': 11, - 'address': [{ - 'city': 'Mumbai', - 'id': 111 - }, { - 'id': 112, - 'city': 'Delhi' - }] - } - ], citiCtx, function (err, results) { - if (err) { - return done(err); - } - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0].__data).to.have.property('address'); - expect(results[0].name).to.equal('John'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Mumbai'); - expect(results[0].__data.address[1].city).to.equal('Delhi'); - done(); - }); - }); - - - it('Model Personalization Test - Fetch data as Citi - should return ONE Employees and two addresses for it', function (done) { - var Employee = loopback.getModel('Employee', citiCtx); - Employee.find({ - include: 'address' - }, citiCtx, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - //console.log(JSON.stringify(results)); - expect(results.length).to.equal(1); - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0]).to.have.property('address'); - expect(results[0].name).to.equal('John'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Mumbai'); - - done(); - }); - }); - - - it('Model Personalization Test - Fetch data as Icici - should return TWO Employees and ONE addresses for each', function (done) { - var Employee = loopback.getModel('Employee', iciciCtx); - Employee.find({ - include: 'address' - }, iciciCtx, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - //console.log(JSON.stringify(results)); - expect(results.length).to.equal(2); - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0]).to.have.property('address'); - expect(results[0].name).to.equal('Tom'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Denver'); - done(); - }); - }); - - - - it('Model Personalization Test - Personalized Employee model for icici', function (done) { - // new Employee model will b created in mongo - // mongo:true is set so that new collection will be used - models.ModelDefinition.create({ - 'name': 'Employee', - 'variantOf': 'Employee', - 'idInjection': false, - 'base': 'Employee', - mongodb: { - collection : 'employee-icici' - }, - properties: { - 'age': { - 'type': 'number' - } - }, - 'acl': [] - }, iciciCtx, function (err, m) { - if (err) { - console.log(err); - return done(err); - } - var Employee = loopback.getModel('Employee', iciciCtx); - Employee.create([{ - 'name': 'Icici Tom', - 'age': 10, - 'id': 31, - 'address': [{ - 'city': 'Bangalore', - 'id': 311 - }] - }], iciciCtx, function (err, results) { - if (err) { - console.log(JSON.stringify(err)); - return done(err); - } - - expect(results.length).to.equal(1); - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0].__data).to.have.property('address'); - expect(results[0].name).to.equal('Icici Tom'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Bangalore'); - // previous records for icici are still retain as new records are will use same collection - // user can have new collection if he/she wants - Employee.find({ - include: 'address' - }, iciciCtx, function (err, results) { - expect(results.length).to.equal(3); - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0].__data).to.have.property('address'); - //expect(results[0]).to.have.property('age'); - var doneFlag = false; - for (var i = 0; i < results.length && !doneFlag; ++i) { - if (results[i].name === 'Icici Tom' ) { - for (var j = 0; j < results[i].__data.address.length; ++j) { - if (results[i].__data.address[j].city === 'Bangalore') { - expect(results[i].age).to.equal(10); - doneFlag = true; - break; - } - } - } - } - expect(doneFlag).to.be.true; - //expect(results[2].name).to.equal('Icici Tom'); - //expect(results[2].__data.address[0]).to.have.property('city'); - //expect(results[2].__data.address[0].city).to.equal('Bangalore'); - - done(); - }); - - }); - - }); - }); - - - it('Model Personalization Test - Address is not personalized and it should return 5 records for icici', function (done) { - // two records from 1st testcase and other is just when we personalized - var address = loopback.getModel('EmployeeAddress', iciciCtx); - - address.find({}, iciciCtx, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - expect(results.length).to.equal(5); - expect(results[0].city).to.equal('Denver'); - done(); - }); - }); - - - it('Model Personalization Test - Fetch data as Citi - should still return ONE Employees and two addresses for it', function (done) { - // demonstrating that for citi - nothing yet affected - var Employee = loopback.getModel('Employee', citiCtx); - Employee.find({ - include: 'address' - }, citiCtx, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - //console.log(JSON.stringify(results)); - expect(results.length).to.equal(1); - expect(results[0]).to.have.property('name'); - expect(results[0]).to.have.property('id'); - expect(results[0]).to.have.property('address'); - expect(results[0].name).to.equal('John'); - expect(results[0].__data.address[0]).to.have.property('city'); - expect(results[0].__data.address[0].city).to.equal('Mumbai'); - - done(); - }); - }); - - - it('Model Personalization Test - Personalized Address model for citi', function (done) { - //EmployeeAddress model is personalized and new model with random number will be created - //mongodb: true is set thus it will create new collection - models.ModelDefinition.create({ - 'name': 'EmployeeAddress', - 'variantOf': 'EmployeeAddress', - 'idInjection': false, - 'base': 'EmployeeAddress', - mongodb: { - collection: 'employeeaddress-citi' - }, - properties: { - 'zip': { - 'type': 'string' - } - }, - 'filebased': false - }, citiCtx, function (err, m) { - if (err) { - console.log(err); - return done(err); - } - var Employee = loopback.getModel('Employee', citiCtx); - Employee.create([{ - 'name': 'Citi Tom', - 'age': 10, - 'id': 51, - 'address': [{ - 'city': 'Citi Bangalore', - 'zip': '560001', - 'id': 511 - }] - }], citiCtx, function (err, results) { - if (err) { - console.log(JSON.stringify(err)); - return done(err); - } - // will see this new record of address is created in newly created address collection - // while Employee will be in same old collection - expect(results.length).to.equal(1); - - Employee.find({ - include: 'address' - }, citiCtx, function (err, results) { - expect(results.length).to.equal(2); - var doneFlag = false; - for (var i = 0; i < results.length && !doneFlag; ++i) { - if (results[i].name === 'Citi Tom') { - for (var j = 0; j < results[i].__data.address.length; ++j) { - if (results[i].__data.address[j].city === 'Citi Bangalore') { - expect(results[i]).to.have.property('name'); - expect(results[i]).to.have.property('id'); - expect(results[i]).to.have.property('address'); - expect(results[i].__data.address[j]).to.have.property('zip'); - expect(results[i].name).to.equal('Citi Tom'); - doneFlag = true; - break; - } - } - } - } - expect(doneFlag).to.be.true; - ////expect(results[0].name).to.equal('John'); // Employee is still using base Employee - //expect(results[1].name).to.equal('Citi Tom'); - ////expect(results[0].__data.address.length).to.equal(0); // address got overriden so old records will not be available - //expect(results[1].__data.address[0]).to.have.property('city'); //address is coming from newer model - //expect(results[1].__data.address[0].city).to.equal('Citi Bangalore'); - //expect(results[1].__data.address[0].zip).to.equal('560001'); - - done(); - }); - - }); - }); - }); - - it('Model Personalization Test - Personalized Address model for citi should return 1 record from personalized address model. Sepeate collection for address', function (done) { - var address = loopback.getModel('EmployeeAddress', citiCtx); - address.find({}, citiCtx, function (err, results) { - if (err) { - console.log(err); - return done(err); - } - expect(results.length).to.equal(3); - var doneFlag = false; - for (var i = 0; i < results.length; ++i) { - if (results[i].city === 'Citi Bangalore') { - doneFlag = true; - break; - } - } - expect(doneFlag).to.be.true; - done(); - }); - }); - - xit('Model Personalization Test - AA Web API Employee model should return 2 records', function (done) { - citiapi.set('x-jwt-assertion', token) - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/Employees') - .expect(200).end(function (err, res) { - //console.log('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - done(err || (new Error(res.body.error))); - } - var results = res.body; - expect(results.length).to.equal(2); - expect(results[0].name).to.equal('John'); - expect(results[1].name).to.equal('Citi Tom'); - done(); - }); - }); - - - xit('Model Personalization Test - Web API Personalized Address model for citi should return 1 record from personalized address model', function (done) { - citiapi.set('x-jwt-assertion', token) - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/EmployeeAddresses') - .send() - .expect(200).end(function (err, res) { - //console.log('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - return done(err || (new Error(res.body.error))); - } - var results = res.body; - expect(results.length).to.equal(2); - expect(results[0].city).to.equal('Citi Bangalore'); - done(); - }); - }); - - it('Model Personalization Test - Personalized Employee model for citi', function (done) { - - var Employeemodel = { - 'name': 'Employee', - 'variantOf': 'Employee', - 'idInjection': false, - 'mongodb': {}, - properties: { - 'firstName': { - 'type': 'string' - } - }, - 'filebased': false - }; - - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/ModelDefinitions' + '?access_token='+ testUsertoken) - .send(Employeemodel) - .expect(200).end(function (err, res) { - //console.log('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - return done(err || (new Error(res.body.error))); - } - //var results = res.body; - done(); - }); - }); - - it('Model Personalization Test - Web API Personalized Employee model for citi should return 0 record from personalized address model', function (done) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/Employees') - .send() - .expect(200).end(function (err, res) { - //console.log('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - return done(err || (new Error(res.body.error))); - } - var results = res.body; - expect(results.length).to.equal(0); - done(); - }); - }); - -}); diff --git a/test/model-personalization-variant-test.js b/test/model-personalization-variant-test.js deleted file mode 100644 index 6ba0834..0000000 --- a/test/model-personalization-variant-test.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var supertest = require('supertest'); -var app = bootstrap.app; -var loopback = require('loopback'); -var async = require('async'); - -describe(chalk.blue('Model Personalizaton Test VariantOf'), function () { - before('before', function (done) { - done(); - }); - - after('after', function (done) { - done(); - }); - - it('Create personalized model of ModelDefinition for tenant wayne', function (done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var options = { - ctx: { - tenantId: "wayne" - } - } - var definition = {}; - definition.name = "ModelDefinition"; - definition.variantOf = "ModelDefinition"; - definition.autoscope = ["region"]; - modelDefinition.create(definition, options, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('ModelDefinition'); - done(); - } - }); - }); - - it('Create personalized model for tenant wayne', function (done) { - var options = { - ctx: { - tenantId: "wayne", - region: "dc" - } - } - var modelDefinition = loopback.findModel('ModelDefinition', options); - - var personalizedModel = { - name: "WayneEnterprize", - base: "BaseEntity", - properties: { - "p1": "string" - } - } - modelDefinition.create(personalizedModel, options, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('WayneEnterprize'); - expect(res.modelId).to.be.equal('WayneEnterprize-wayne-dc'); - done(); - } - }); - }); - - it('Create personalized model for tenant stark', function (done) { - var options = { - ctx: { - tenantId: "stark" - } - } - var modelDefinition = loopback.findModel('ModelDefinition', options); - - var personalizedModel = { - name: "StarkIndustries", - base: "BaseEntity", - properties: { - "p1": "string" - } - } - modelDefinition.create(personalizedModel, options, function (err, res) { - if (err) { - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.empty; - expect(res).not.to.be.undefined; - expect(res.name).to.be.equal('StarkIndustries'); - expect(res.modelId).to.be.equal('StarkIndustries-stark'); - done(); - } - }); - }); -}); diff --git a/test/model-rule-base-entity-inheritance-tests.js b/test/model-rule-base-entity-inheritance-tests.js deleted file mode 100644 index e052267..0000000 --- a/test/model-rule-base-entity-inheritance-tests.js +++ /dev/null @@ -1,406 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var models = bootstrap.models; -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var loopback = require('loopback'); -var async = require('async'); -var fs = require('fs'); - -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; - -var context = bootstrap.defaultContext; -var adminContext= {"ctx":{"tenantId":"default"}}; - -class TestCaseError extends Error { - constructor() { - super(); - this.message = "The test should not have hit this line. Check code."; - } -} - -describe('model rules with inherited models', function() { - var baseModel; - before('creating the base model', function(done){ - var EmployeeBase = { - name: 'XEmployee', - base: 'BaseEntity', - properties: { - name: 'string', - age: 'number', - gender: 'string', - qualification: 'object', - section: 'string' - } - }; - - models.ModelDefinition.create(EmployeeBase, adminContext, (err, model) => { - if (err) { - done(err); - } else { - baseModel = model; - expect(baseModel.name).to.equal(EmployeeBase.name); - done(); - } - }); - }); - - var DecisionTable = models.DecisionTable; - var insert = function(obj, ctx) { - return new Promise((resolve, reject) => { - DecisionTable.create(obj, ctx, (err, record) => { - if (err) { - reject(err) - } - else { - resolve(resolve); - } - }); - }); - }; - - before('Creating decisions for the base model as default tenant', function(done){ - var decisions = [ - { - name: 'd1', - document: { - documentName: 'employee_validation.xlsx', - documentData: prefix + fs.readFileSync(__dirname + '/model-rule-data/employee_validation.xlsx').toString('base64') - } - } - ]; - - Promise.all(decisions.map(d => insert(d, adminContext))) - .then(results => { - done(); - }) - .catch(done); - }); - - before('...wiring the model rule to run on base model insert (as default tenant)', (done) => { - var obj = { - modelName: 'XEmployee', - validationRules: ['d1'] - }; - - models.ModelRule.create(obj, adminContext, (err) => { - if(err) { - done(err) - } - else { - done(); - } - }); - }); - - before('...asserting the base model rules get invoked on insert', done => { - var records = [ - { - name: 'person1', - age: 23, - qualification: { - marks_10: 65, - marks_12: 65 - } - }, - { - name: 'person2', - age: 24, - qualification: { - marks_10: 65, - marks_12: 59 - } - } - ]; - var baseModel = loopback.findModel('XEmployee', adminContext); - - baseModel.create(records, context, err => { - expect(err).to.not.be.null; - baseModel.find({}, context, (errFind, data) => { - if(errFind) { - done(errFind) - } - else { - expect(data.length).to.equal(1); - done(); - } - }); - }); - }); //end ...before() - - it('should assert the order in which the hooks execute are as expected', done => { - - // The purpose of this test is to convince yourself of the order in - // which the hooks execute - var task1 = cb => { - // begin - creating a parent model from BaseEntity - var modelDef = { - name: 'A', - properties: { - a : 'string' - }, - base: 'BaseEntity' - }; - - models.ModelDefinition.create(modelDef, adminContext, (err, record) => { - if(err) { - cb(err) - } - else { - // expect(record.name).to.equal(modelDef.name); - cb(); - } - }); - // end - creating a parent model from BaseEntity - }; - - var task2 = cb => { - // begin - creating a derived model from A - var modelDef = { - name: 'B', - base: 'A', - properties: { - b: 'number' - } - }; - - models.ModelDefinition.create(modelDef, adminContext, (err, record) => { - if(err) { - cb(err) - } - else { - // expect(record.name).to.equal(modelDef.name); - cb(); - } - }); - // end - creating a derived model from A - }; - - var task3 = cb => { - // begin - create a derived model from B - var modelDef = { - name: 'C', - base: 'B', - properties: { - c: 'boolean' - } - }; - - models.ModelDefinition.create(modelDef, adminContext, (err, record) => { - if(err) { - cb(err) - } - else { - // expect(record.name).to.equal(modelDef.name); - cb(); - } - }); - // end - create a derived model from B - }; - var cache = []; - var task4 = cb => { - // begin - wiring before save hooks - var A = loopback.findModel('A'); - var B = loopback.findModel('B'); - - A.observe('before save', function _bsA(ctx, next) { - cache.push('A'); - // console.log('A ctx:', ctx); - next(); - }); - - B.observe('before save', function _bsB(ctx, next){ - cache.push('B'); - // console.log('B ctx:', ctx); - next(); - }); - // end - wiring before save hooks - - cb(); - }; - - var task5 = cb => { - // begin - creating a record in c - var C = loopback.findModel('C'); - var data = { - a: 'fooA', - b: 2, - c: false - }; - - C.create(data, adminContext, err => { - if(err) { - cb(err); - } - else { - cb(); - } - }); - // end - creating a record in c - }; - // debugger; - async.seq(task1, task2, task3, task4, task5)(err => { - if(err) { - done(err); - } - else { - expect(cache).to.eql(['A', 'B']); - done(); - } - }) - }); - - it('should create a derived employee (as test-tenant)', done => { - var derivedEmployee = { - name: 'BPOEmployee', - base: 'XEmployee', - properties: { - shift: 'string' - } - }; - - models.ModelDefinition.create(derivedEmployee, context, err => { - if (err) { - done(err) - } - else { - done(); - } - }); - - }); - - // this test should invoke the base model rule and deny - // insert of the invalid record - it('should deny insert of record on derived entity for an invalid record', done => { - var invalidRecord = { - name: 'Gropher', - qualification: { - marks_10: 65, - marks_12: 55 - }, - shift: 'night', - age: 34, - gender: 'M' - }; - // debugger; - var derivedModel = loopback.findModel('BPOEmployee', context); - // debugger; - derivedModel.create(invalidRecord, context, err => { - if (err) { - // debugger; - // console.log(err); - done(); - } - else { - done(new TestCaseError()); - } - }); - }); - - it('should also inherit the default populator rules defined on the base employee', done => { - var empModel; - - var createPopulatorDecision = cb => { - var data = { - name: 'Populator1', - document: { - documentName: 'populator1.xlsx', - documentData: prefix + fs.readFileSync(__dirname + '/model-rule-data/populator1.xlsx').toString('base64') - } - }; - models.DecisionTable.create(data, adminContext, err => { - if(err) { - cb(err); - } - else { - cb(); - } - }); - }; - - var findModelRuleForBaseEmployee = cb => { - models.ModelRule.findOne({ modelName: 'XEmployee' }, adminContext, (err, data) => { - if(err) { - cb(err) - } - else { - cb(null, data); - } - }); - }; - - var updateModelRuleForBaseEmployee = (record, cb) => { - //add a populator rule for employee - var data = record.__data; - data.defaultRules = ['Populator1'] - // console.log(data); - models.ModelRule.upsert(data, adminContext, err => { - if (err) { - cb(err); - } - else { - cb(); - } - }); - }; - - var record = { - name: 'Arif', - qualification: { - marks_10: 63, - marks_12: 65 - }, - gender: "M", - age: 23, - shift: 'night' - }; - - var insertBPOEmployeeRecord = cb => { - var bpoModel = loopback.findModel('BPOEmployee', context); - - // debugger; - bpoModel.create(record, context, (err, inst) => { - if (err) { - cb(err) - } - else { - cb(null, inst) - } - }); - - }; - - async.seq( - createPopulatorDecision, - findModelRuleForBaseEmployee, - updateModelRuleForBaseEmployee, - // cb => { - // setTimeout(cb, 3000); - // }, - insertBPOEmployeeRecord) - - ((err, result) => { - if(err){ - done(err) - } - else { - var data = result.__data; - expect(data).to.be.defined; - expect(typeof(data.section)).to.not.equal('undefined'); - expect(data.section).to.equal("junior men"); - done(); - } - }); - }); - - -}); diff --git a/test/model-rule-data/PropertyPopulator.xlsx b/test/model-rule-data/PropertyPopulator.xlsx deleted file mode 100644 index 489187bcc262aa24a1886af3c7a83d6c6d51bcd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9751 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 WDj>pa5CFh{eHdY8JV*E2=>Gt1g2_<; diff --git a/test/model-rule-data/PropertyPopulatorOne.xlsx b/test/model-rule-data/PropertyPopulatorOne.xlsx deleted file mode 100644 index 7bad6bd56494f303205a99277c1d5fedd08963b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9756 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','\"PERSONAL_LOAN\"','-','\"NOT APPROVED - >1000\"','False'],['-','\"PERSONAL_LOAN\"','>6','-','True'],['-','\"PERSONAL_LOAN\"','<=6','\"NOT APPROVED - <=6\"','False']],id : 'Approve',hit policy : 'V')"} \ No newline at end of file diff --git a/test/model-rule-data/approve-graph.json b/test/model-rule-data/approve-graph.json deleted file mode 100644 index d0693c9..0000000 --- a/test/model-rule-data/approve-graph.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"x":191.265625,"y":144.9296875,"nodeType":"DECISION_TABLE","data":{"inputExpressionList":["amount","type","experience"],"outputs":["errMessage","isValid"],"hitPolicy":"V","ruleList":[["<=1000","\"PERSONAL_LOAN\"","-","-","True"],[">1000","\"PERSONAL_LOAN\"","-","\"NOT APPROVED - >1000\"","False"],["-","\"PERSONAL_LOAN\"",">6","-","True"],["-","\"PERSONAL_LOAN\"","<=6","\"NOT APPROVED - <=6\"","False"]],"inputValues":["","",""],"outputValues":["",""]},"id":"gSjbne5yjv7","i":0,"name":"Approve"}],"connections":[]} \ No newline at end of file diff --git a/test/model-rule-data/blank_object.xlsx b/test/model-rule-data/blank_object.xlsx deleted file mode 100644 index 1e27fef33664e1dea33ed8b8107c140c4a2f9654..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8825 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 diff --git a/test/model-rule-data/blank_object2.xlsx b/test/model-rule-data/blank_object2.xlsx deleted file mode 100644 index 8af8f0781016c782f28320c3f649aaaf47ce3543..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8511 zcmeHMg;!Kt-yRz2Zb4E~7`hvzyO9p*?vx>A27#eOx+O(Y8l@W~rBg(jkq*D{#(VGe z`uzp(yJxMlXJ(z}x6ax7dG@a~l#!5$0H^?T002M>usq1JGerOZ5|9A^LI66Vv5c#W zmyL^;nU24kji<>YKWC>0`N)XOxd24?{r?;P#Vb&eJfzmeg(r8ea3izHp|n^diO#nV z>LXy)6z}X#>MgS}&bGJzp8fn9Pdbm-LEtUH*kT~}@rYfGi*o}grnf~43mn$pscT9p zz}eHc$Jjwi4)riHJj^AclmwESnZ?;=0A9K`c4_g-u1P6XP}mWZh(FuNn(x!YU+Hba zDl>X5)W7h4RZVXhUvRK=5^rOIaLU1+CD0VM!J&u@rVCso@i8sZpf%8DvD9kvxJpxQ z_hf#@Gi`2#xXcxm(1x=#x`Nu$Eo8VH^GsuBj`y`KnL>+@HdUCW`1Hj zGY;cy$uL^b$2ow8Be;&FlG|l0xLbm_KxOFY!-J)3#x+8!?iEVw=Iri zzHC`_ApurUNJ;;|JHbx%eJiRUm*NSuO=P)???h+#JI%08X}p0>T87~0{nh>*k-)w2 zE#vTm0dv0g(uOgKuS2+LgOE8Y`1Ws=Rx}S~tGW^y&0j`eP-3W}mir*7A3+ z^OOfx=;bGmW#8hBJ<_MRM_5D^MIx3SX4I={xNLb*g)l9xyF!gT&-)=r%;*^Cat+aM0L%ziC8 zUV;nmvB!Okxu>B3>OIn03E^4 ziSu`z__}&HTDrPA{_tLZY6b!B!r)f^yGMnVs&Y3MUK{3JG?#CN4>A5HFHX8W-CbPt zfjX82CR(15Z!46n%_asj%A829QT~S`eLm-WIO|A+r|;Qb#}Xh15IP--ptz2}Mv%}g z_baJEk5LE-_P*|x?_=V;@+52$hxW5)6A6C4U}hwbE-0px{nqp`iq~P3%bVN5bNv1w zoHIjpo6oKywD&4Ob&ZV=Ua{D2eQ%OkI77_=`O{3m$nPnNV*3LozD&`wUA&h&LqBP~ zu~qdSb$nlNg|$Uhu1sCVCl(at^&q1t{Q?E*fHK-zKW2i zdsU9~E;?X5O4>+?v)p86(?zHHw@&oHeHmf$#ED>pO611g^!c0zI- zfo~!sqQiVX%%9O~uMsOgw;p}Nx|YYfLsVjm+rcqc)%G=_dx>|fHli1tqqS(HB{d?v zbEp}CPmQYvV^HlUH9K6R64k!EH&e2%}p@ad#f z=>@sh3i^et>+=K5Rc*T$L?VyoGID{84R~MXGRpM1sNc9V31LvHgu1;N`8b(CLv@3; z9SD8`hK8uZHc!~ow=`>nvt%zX*}p6@JfeUhk)nLA+g-+Tv@}tq=#z6&TKTU>W>eW8wX)1af@<|}i zaA4BZu315kY<5a5Hv4B{^qQ>Q6fypzw!jRr@YQDxkN4U6+?b-guP-!s&=LbU1h}Hy z0#cwYiC8C#S^Z2lZp-Eh`k~1|ClO-tvhVGhD#{+(YZ&5Xf%$o>pM=He-OA1{cdnz% zztT01J{uyxax?PYHi~LUB@NIPFDSlG&Aats=96h;GBYXvl34TD5YeG#xp?2^tq5@A zE!oTD%3Vy+ycZ}`i2|RYH7|wHTQTS$)^bOQ$o5P?b79!mM*%seWd?)noDtS84Io6^ z(UhZS?a#EA13dlv?sz+x?)o}Z%t~&Nezlt3;_3ED@K6at@gq<9O}JimHqJJjzg>Yp z7IV+g7`z})6vFvU0=-S7U9qOEo6_l%+Wa09?QY?wyHkASzzbe^p5?fEVNlW2A`Jy) zGmPL}85V5b@$s~~myr?txA!U^BeAkWbW0aBAzFV5mod^I5p z#f3`K*Y9xDdYz=9h6`F1dFeEsN$J2HL^JwG++SW3aZ|+OszR9jEoHxR7z&nL8ptTp!m*2>4*~ zu%TH@qR%0B@8f#Q?3d|sR8yGQ%If0r)>tQ-#^;rVPFz%4*$MdfsB;dW{gm{NzoT&Z zkK&X{w)wDH5#^E@qA9<`(oA(t+s!7h6zEqRr5<8a)GzvnfgB9<)AlR}3Eo!DSbC)u zApsN~$blGM7>phh#pT7y$PO?wj#8(~Y4~|2bK#7n7};o)c7{v#BxjZYEAqM?M&=R!F9%i+t~>Wp||U zl*jZ7<^Tsg@-6K3sHCXy)R8&hvyACkEWq9fQ8pre`W+TsCTR+*_tursHAL7U9%;lgDRe#isE zPCQRQOLkH{cLv3Ia#>#-uuyD9{zKzUAYlWVTO zLuwZIuBdpI4lYclT9XKNMZC9?qg=ag(NbEXNd~a7IEOwDpGv)xKySS6AU$wCJh>e5 z&KrNQ++$=?xwuh@dCrpeO_H5udBb6U)WY-?R<5Qq>(r=6TVlpzw{%xRtVMRY!CuAm z3rNhD2hyGftNPRYUcH{PJO{(%(cT>ndX$zygN^fI?CZh&183#aWs4cM>+chp=h^O> z@1If7$)Lqq;x_oa@u}yj*EhvaXmFL5-W_JpY)r7LprFNkLzgG-^N8JughX{GB&AH3 zWHt}ruv^jjVIXWvZ{f6Uj|$MB6SMw&Slfr8daz1mw8=KWn5)zjp*q$;S2A%Yb+g&q z&%?{Gv`xMko4}|kouQ5!GLF1GT}qRg`LxPdP6<1Jo2a}oFL%POJ^qO@zEBZ1??eiS zY1DlI`>S|nT--$KzM91fZrU-GL-Y^|XFiXhC?%LTaBW52@G}dkSa$*Ys*)EsU(EB) zQW?cXpRJ?WX-UE&+F0XyRM62I##uze%f`g;!VJaUNfv5Mv3(;j#LEX0Bga1Sw3ItE zu)59~Q{h~SSI9@+D{)y?!!o3R6-khm>P2XoA@4)vQJz_8y{VPb=PYR^x00IE8Z$4x zh)Q2V^1=S7`gG031qU<0@q-hfjHG~!;H!aqXIW2|YD*l*g;aULLX#TALXPu2-)>b; zD9SAf_zY}5RyJU_jyKOJ?dED!)^?NE9yqF?Q`G14guk+GW$DtYh3O^D3q3-wQ$RLe zZ^)*vNc3Asr`c1}ugrX)x2vRk@wF@Z4$i&G(N&(TFnlZR=o&H5~Sw`y%u$F~9Xhx%-7F8c^7>~yi^A!idd z05nan^w0p!eaalO^=ZR}NFaDfCD@dOZT%}uxxV2R29}ZZIzX$JDqsGJTBO&;!|dPY zQagna?ovt`5x1O6dNne$8n#QtQaxK5Z949n3UVMzZOx_I5il-_(l7UJl`r{pE{uC( zg9Q;yiWeopGwpFacl~AXnG>_?oC=Qykl}s|`;Umg%iqc7w@{!^$JlX!iztL<5AMBM zYL*0*LHjkld_>JaJG4MuCn5al9tEXsr@fBD{s$Lmah@(?W-vET@&6q(!-4=omp&pTe5h?87(PXt?es&Kgf8xDRN>f@BG(!69Dss_lWWtbQj8gk7G;$a4lTO zOlz_DFuSB)Z!}+vCOP~Bsrg}BfX-D5>kX&tUfapkousdG8-QaG{LO+F-Y zjJsl0rK`qWZN{pMq@6|nCrwR~^SqZW~S(nZ<=*b3%B9{oufql24uBMk5?A<0P8}^R=coxBEjeuG+V~r} zH&4c5V?w4*}VC~|%Y)3CZe~x7tKg&iP8ardvK~7SC z>KP=NIUXW^MMQlW^7NwCj0+)&M>2Om46|>V1|7+EI$Ig<3P-Sm-Nao@Gnat4d`IeC z^yC;;&X#(`I@&#DKEL2K^?7eRa`7SGcI}D9KJ&_!S}s$!{O~pMJOA3d>cvGO_iFT6 zu({6w)I7aHK#IG?)Rn)78`8CFmQ)$sHR{8ulKsK5vf|kYzf29%<#jDb7hzG!$_Vp# zwuf0S?8u5BQp%lN{=JhL>3hqNbYwY#o{USU;3T4fh`^IUeL4nB-gW}#PtM<_-y*SO zy&;czE}|vh;_s`+;vq-;NY+fWg@$#L_E!d&hN!AeLn*QfSEpjoI3SKiCQASMlPu3S zqxIc;SB%P8Pnn2GFBh@fI=v0lC z$=9b9oHEOZ@t3Cv$$AXCOqd*;zcLX9D)&E{mgv62+{mbu_mFW)`c!Fu(`+64fk`2- zEzhEe4h5w>3sSHWFlT#W*(Vl%N>YBkEQpnVN5<}0{4JJJX*N%!NbalzqwKX3G(-+` zEXn=tgA^BNv&cK#;eM}!$u|pU<~c+z>N>P#Ye6aq6U}RLoww!{1_4N}f;d-sEO{IJ z5H^%e@!6KlpZ=JNg2VXDyRb4G1yOw%Ni}mwND?fFmuG_mQcx z^<{Fr^kt4Tou{-!5c;;KSA%@{kZGokELAIfV>vj@eN`($n_avSS|sALyb7C^gnb1; zyYnxCHGHfI9r?yDJAxj!aO{*lug`38wLByv4K94w-~O%=%SFn)u6F+u<53mz zY~@C7#jdr5W24(Weuj9Tw1$*MEq?`K>#lZ!rnM#yt<*VV)YPF93Jg)J4(8z{t9Z*i z_b@73;~RVn!Foj8$BJ|j;wr-<`rqpfZ+@BoEjK18YW$J^;lry_)~;3>9TdXT>(?YB>1$4M-Ww~z2 z!3IN|X?xLZw28s=V2JJl*$Sq_7$c92k}J#TS+6**+<>voT>XM=0IVbhqNuh@&(7(Q z=Fueg<4!@{@Td-Z$TAe6Ha;UX!qs4j$=I0a%gkBDrtPsEZyU}>N)v?SC*9BZ=)L>E z{XPz^DJ1;+30hbRZ9H;l35YTXjLc7gam;sT!dykS-nrWHqujo6XIVJzeNHR^?rw+9 z;$*wcFTUE7c5Rnj>>sZ+l~a5!TS@^W_iAxjJZl*&oV6_hISpknae@Mu-A2G<@mTxZ zm@Z2NE7!tk{*yOA&#$mUullP#NmOy>UB~+=vpf4H4bnRi||L$4?a3-1bc|OxbhXsuM%hK&pO#qI1Q=bX*J&8X=vf*_CFTF z>G#LTgi5JzaN$KBvK`aVt(0mTEKH8Av0hE@#+balhS%TQ zEvNjQ(kNgFwl-9ZdCwqrh1Q^exX5_$aY4ajGfkhCNpqAO9Jzes@DOm6a5|>AbB_@l z<`5A(n+-m)1jahGPDfn|H-3?w{RD`KhKKji-kFsBx&$vE6I9PivBhk0+VGa#fifZg zW_N>a1`q*y*u{*qH6>CxqAo;(RVv7LhCr`CkxA!*W!PEbP(&9jBbq~ii$#AJQ+sgP z%Tp56l`^TAQ$)ks>jaz1MUug`?cLn(Xq+2Q(_rO>A?YO#v+Y1M9rZWx9r?X-i-5=hFP;6mRsH9_ z{W<@lajl{JPXqsKs{IN4F{i=B@wXP+&%l2+8vYJ!g@^M0f7{__J3m*S|FU!l@5cXB zi~bq>b3O4duo32O;D1#Yf41;*4ec)rud#kvQTy4z&k4+52AtqEIr#H?{wJOJ8TxbR z_!pFetJ>V07yUu0Ehq> zca3G-T)k~wz0Gy}-EF;0AN#q0X>(BTGG_tqBJcm-@n5_H#YqF|9bEWw=ZfEDHaL_Q zDkU-a_n|$6teWC&oiDpTSQ}?JI^JeHzs8r&CUFw1ARJx@}b zdfRl(Cgu9(5gBe6p)j%Ec58B&6a`*Wb?d z=n*V;*W-LJdN15N-@c-*w?rV+UpS7xK1wv<E|5aZ@-*lSv{|gQ z>OHSgR9d{4Kk`mmSl?aZicDz6T^w3QYitpuR^y|nER6Z~!KgdInXNU*Bq*Sj`33Bo z##Ms=iGw+ZafW0V9dv94py3RwCN1G{9S-i4Ajwr7IQm4pc+Ip*MAf-WY15DqpJ1bY zpWcOiC9fw#79=dl3Jodf?fWRy2HLl#3UbXKMc+V`ySODjBiL?0v`ORlwb3yKNA0il zZi@!)jcgh}J2-x|9m;cq1_0dNq5w4h;+8eKTnyikSW`uM9X8S}%{^_wUYw7Ap8w~L z|HVG|r$;Y_tARSWa3T*Czen|+PcA0lORM-wDK^sSggjMRz^jSNWu#neeME(?L-7nn zA+$N`Ee+}g)16*Y=xRF%JC<8zc#6>bo7;ff5VC2lL_dN! z)YE&l==lgQc!m{vn6geojjH*M2j2R33SxE?^&a&>VKncTkQwUlP7)@bl!8F+NCVPI zC;%9D{J@;Q`-!icr?ZutoAb}?^>3fKgG^yaE&ts|v6h-jCl`J*_DvL*ZyJPzVAh+H zeouE752LS|W&R-@Z^*Z0O4bHbgDDkG6t_tK!@(ZNIX~_i3ejmh+xr+o)Bqyzq3Aug zLBt>mhSh!vHB{jq5#ip>ZqYtA?i(+ndU1F!dj_%4mkVYl@~GT=df9LFW08DLLtH*Q zPF^Dq`jMC!sNQ&b6|TKk0`BzUg2y~6{%2v;C~Vn=}jBet3VUV0{7a?No~|UT=kZ|?q_M8KVcvE zc4)WJ*E6|{wDjL0!&tk~DvpFp8nWEML*9Xe%%8zhtkdT@kK7-!uM+r8Y)15=6&AvZ zR%Ad8VyQO|$;=&`YzV&Oz;(d6Ut4{dbzSC6w5rL5+ym47-69dNXYdWoVj}qzz9&k| zcLvp%zSX|VJY1IBN)j6O?s_+>F~%SiP~mrN*CGh1ff`!XWriKD@HN>J;%Q|B^ib;M9F>6iJQ zk9#ZnbC*SgM}KNC%s0oW!8P|1=uP;q5l$5W1iLd*)>XdhFRz>#~8qke` z&JE{66}bo{tb83+8G>K#+xi};?deC{z;g|^dP$Xec^z`8snf#Z_-cc9fo5vy*^EHT zJNt2|!V7ZmWsD11x911gE7}e(h(#aIq-Ak4)#87hN&BGBMP26cP#B9^HPrph;MjNq z4b^w_tw5Lw3?8C}*f?QN-PEiUc`JK)$qpMrjI%ckrOY~c)*6+TK3u+W^UvMrx{wKH zrnLLfY#S7H+y7uf|FdO19;RgzbLV{@u_dD*^nZd{FMd63J2K*gAfZP5JJh@!EIn;) zw7oqYUG2So2B%n^LuVx3eu&T9x-Uc}whc~%XLOn-yi~W{o!sHs4WGbB z-mc+18b`~XKJf+31BzQMQbi5Fw0yS}Y2a1X>k&iPW&j;54g%%ytO}SP*2+(hRpq|^ zSUQvq@<{atN%x6F_RVE(q|ZD;Nu7No5|$aULsU+3&_HSOP@58;;Z6$^aOn*79hDTr z1-ZU{b4^nrW0IUnG`nj>`+Yld@JVr#=m1;wjS#k{_}v%s?jiX-XF&1=aTi`kbVf+yMtc0mIrG(#FT+g&XPJjRqu#1-0&2a7xC4xw=|pP{}^3-#8Vw#BI~(Wil5EY zZ_&lu!Pdo=^S3kiPmAvv8pG!0i9LNm?TxK9PJf*&LNh~{FRv^dZw|MyX?$6pZ^J{QlO09A zk38q#cASv@`E?|oz)_rX!4^MG(_OhF#wg0KF*Fk$lMd4fEV=r{N3RZn6g3O}VNfRn z{ggece!_~9DJ$=kJQRQ;tsIo`g~8A(-3v4oQ9uQ5*O}ZvXQMuVMpjC zVj?*PW3FAX>Q&QI%w-375S6%kpGoy1sJ*C2DnlX=a&olY*I^E#ej?(3;k8ufX***o z)u&n)c-f&J8hl=l*y3Dn4f^ggb?PWKeasnhwbtd<+S>ml_9I3Y!Y!l8?`q?+&iv+h z-5PeT9wRRjq-gc|t@Fi9eV^6+tOyC}JU2V;SbT1F4b&En(rbF2r_-;Z=iE)fkw*|L zR%;Y9vjzT-)Uw;s_$tHtxif$RUin7$8nl%Udn&La_r%Y8Zws&=_!4C^7%$T)$D*E- zc_rvud6CP{WcfeKk7t$6^j5&WgxFYV*4Wa7 z`zMvv1mQZA6|=4T-sClL4N>u`j7_A`M!k64{Ga^DIZ$I<9!D|f2eh3hM1vXeUTqun zoYp^89p+e=dy@-weN-OPUu%bUH;N9&t&3o{fJj$olNsrPhsK0LR3Hw^GK;K!> zTh6iU9^fKsiR(Q3LGDrtSCZsR4+n581g+r9pasq)|*%^ ze9n?$`a@E4Qe*1HS25|!*c_NYnm&C)e(u2(_sGEsw~VBqjL@4t%(J)li&X_qA_c$4Y8}O(P9c%DY(_B~_i|RR_+h7!)-*ywBd)G_iDO zRU!0V&Iv!ps8&QZUaQSuC{Fa7Po>#Y*Dpz@)!S9pz1Zo9x?2KAg=y5ndbEy5`$d{v|3;dHgV?sX?EG|A%_>#PN@SnnQiI zX4m~Ys_gVJMImRSwg7ZZ@6^x$&3(#D^R-FC`3P>3x7+ zBUQAtgO;b)%**WG>{>N}^~|-9EIe*0i|lG}Y9(x!iluzIFv@Ji?G@DN{;Q@e`fWku zyh!~bpCe%R6I+~Ev6t~;r1)lC&gX9bn8PxocU@i~D`Zq;9s~ZXZ1MI7+x}Ls zywfp8)+@vzG<(SG)mXVGqypWqh0vBInGtJ0<$UeS3o6k?{Iah7;3p=^`^|>&`wLCM{v)G!0=@DTXb11 zgr5UcYamjjWfR(H9Tkztu3smb2VBI;nv>@>$`N>z<`+AXYKEHF#5cDyX9{2*^%+z- zgYUxGF*$}60oNk$nCUDR9%U5N=nds)(Ih=PL1}o@9H4X6$oierZLj%c;zkmr^OWfm zBS?j#S_(G?SPvnUW7>UETDW4|(QK^7MAoxJ6(}>E*$(o2|4NW>*jk9)WKBBVpeqA# z|AZLp;I-yGW`_it7r=0RF3KmWTjzL(Z?k)t)gO{6l5v6R=5)AVTLH28VkE^7$%&)I zrA&3fqeZ8!EL(pgkB0FWtW0G*PeR(aa7>TGtyAM4IE^KVBE8ZL^Q;|Qmn|5%7|(I6 z;-}fDLu00_TggdlPQ8L8(?>$&uZXEHL+mfA%(?Eo3lZy}dwrGzo^jMTMR&klR=R8{_ zzwxiSDPLG1_9(}ghFL)R;1;RHf>Jzfsu`cGN{XKj3dmHV zTwYgkbP(kgEDth|WO$l)BaW;IBcwdY<=esPWbIZVsi<;{ z%fZ&x+l%wJ<+n;KN$+Q`N8E($^)MO~(tJB0Pdin-95ne%hxp{?r5QCzwuQE*eVO~< zeKV0kip>7h_7_WHTIma|Ockg7o&?9?g7mwZP`3vI23GE`I^}1cPW`E%eMt}dxs)YXY#ctUE!ej-Z+?0zE?7Nv47Ui zNdk2}dAVY7_8s%cVf2H}zAC1^tJJPoWztWN=C6AEPgI{9&12+GmM!bFub(Jvj=n*V z$(@BeQrpN$<=9{*kFo{YaOb2X)OIdgG3}5KxbOO;i_=V3vj_diFs7SN2xbpG>L4R^ z@g;=qW~+ZNSw9LMLX7|=K%f~`d17eyZj6<}Be6B?PxNA=n3F_59|)l=Q~ID;81mAk zB<56eG?2j`g?`A2`M}sdhD{i&0MA(WZ5C%N8{E}HaOolB1)XKGN4otc4n#W4%I0a7 z5~NzK+6^jDO$!8%L|T-yZOPF{FuP-H4D?q;B8(ff?u$Qye|{(|VK5V4u3#g(e_(+# zhbaA|PICR!!hmVSx{TLtttH2W`9@L~QTWh#FlyzEn+}4+D8Arzs1#0ZEt!lv#}{89 zKXC6Y2q<2oFXNs&B{;$7?rsK*8#4p8*BFfu7ZtxlyV{zx2oB^&6|4uwE%23d>pByj zb~!FGVw6s*R%o+-)aV`Mu)=WFa(2=sJt4-S0M$$crBCd8X^+NbXet45yxl{9CJq(~ zne|O*rH`Km#0V9+DH4qO#C)kmfTTJW>d~$u$+3Yh5b)}R7E0V=-B=s8+WNBhV|t?p z&+S^7;oXaPxMnhBa|>;+8z(e#-Pug*j?I0K^IuYsp(STmyy)545@kTtH#qa5!$-ql zSf-TAiTxrjA4a)$ZoH+z)DxQ#`QKY?Z3bH4L6mu)VrKI{X03qx2Pk8=-h0K^@62fh zb=7w+u3yx4FuhuRwk4b}@BTQRb7xG)9Abdy zdfV*SI2nUw_He5SG~}<;FjK^H1=zL8OrO5X@ZpvYi80s51DQ@5>_GIXG%~O<+_69F3$)gXJ|Hok;;W23Fzmo# z(-z~Y9$-&J6qTRXpn8md=SGZ^Ep0oU#)m~qkGw1M(J`~D^X($s)Rx}j&gK?oV^-?* zmo8hHyTANpe&t7mNcbh*aV3Tj52*LGAL<6n&9UJL=pSnu>MuGOa3l(;kZA$`&nUEX zcmE#L;_9bFwEns@4I08B0gAUpY541U&Q`B+HAif-YCmTpeVoN^7#hSWP>Fj?d zQ=Ef`-v_W``DEGdlvBP!X7~cgm^UQW^y;B31|Tu`vx|mgx*~N%D;Q6>@VG%rzm6;}-WaapiK1pM}68MN+ZF zUAm0eum_0Q*=z|=C9u}0by};Fc?j|x97mxKX?Xb#9bL%SuM6-4(xEl16r0SJr?nO2 zPLv5b-*?y9rU2p4M;*+#n-iiXgCJoVoI)Y~vpWom6zTM?IEHNnPI>gfGGdv8csLA) z(NzbR-Mj@s9m(TLnUyImnEgH{@kL^g*H1K9F3gDWx|`R$W-ch@%9r;FnWt)EH1d0d zz&Etf*VPja*9o>I3#9$cTf14e=$z~JDTpG&kko=l84ggI)|&4~i~P=B-?__yq!0gj zT>o#o{d@g~;k|~+Ulsgyu=sDmpKA)zIsPuXn{9A(kRs659@Xz9#RDTlxS7g*s Vxreka0Dy&jJw@j55}Kc%{tv9eIrjhn diff --git a/test/model-rule-data/category-feel.json b/test/model-rule-data/category-feel.json deleted file mode 100644 index 5bf7b9f..0000000 --- a/test/model-rule-data/category-feel.json +++ /dev/null @@ -1 +0,0 @@ -{"Category":"decision table(input expression list : ['age'],outputs : \"category\",input values list : [[]],output values : [[]],rule list : [['<2','\"infant\"'],['[2..5]','\"child\"'],['[6..11]','\"student\"'],['[12..18]','\"adoloscent\"'],['[19..55]','\"adult\"'],['>55','\"senior\"']],id : 'Category',hit policy : 'U')"} \ No newline at end of file diff --git a/test/model-rule-data/category-graph.json b/test/model-rule-data/category-graph.json deleted file mode 100644 index 0d21bc2..0000000 --- a/test/model-rule-data/category-graph.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"x":254.9921875,"y":109.390625,"nodeType":"DECISION_TABLE","data":{"inputExpressionList":["age"],"outputs":["category"],"hitPolicy":"U","ruleList":[["<2","\"infant\""],["[2..5]","\"child\""],["[6..11]","\"student\""],["[12..18]","\"adoloscent\""],["[19..55]","\"adult\""],[">55","\"senior\""]],"inputValues":[""],"outputValues":[""]},"id":"gSjbd99uiet","i":0,"name":"Category"}],"connections":[]} \ No newline at end of file diff --git a/test/model-rule-data/corrupt.xlsx b/test/model-rule-data/corrupt.xlsx deleted file mode 100644 index 72ad00a35647578e2346d918f56d0767d324e099..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10828 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 diff --git a/test/model-rule-data/data/functions/index.js b/test/model-rule-data/data/functions/index.js deleted file mode 100644 index 15152a0..0000000 --- a/test/model-rule-data/data/functions/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * - * ©2017-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -module.exports = () => ({ - hasProperty: (obj, propName) => { - - var result = (typeof obj === 'undefined') ? false : (typeof obj[propName] === 'undefined' ? false : true); - console.log('hasProperty', 'obj:', obj, 'property:', propName, 'result:', result); - return result; - } -}); \ No newline at end of file diff --git a/test/model-rule-data/eligibilityUSA.xlsx b/test/model-rule-data/eligibilityUSA.xlsx deleted file mode 100644 index 2bc036a8a06270cdb11c16ae3f8518e20763a49c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10297 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 diff --git a/test/model-rule-data/employee_validation.xlsx b/test/model-rule-data/employee_validation.xlsx deleted file mode 100644 index c662eacae03aace7d13a0c1ec434deda90cfcdd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10220 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 diff --git a/test/model-rule-data/validation.xlsx b/test/model-rule-data/validation.xlsx deleted file mode 100644 index 16b6aa592591362346ed285a8b1fd47da4441170..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11311 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 diff --git a/test/model-rule-test-decision-service-invocation-tests.js b/test/model-rule-test-decision-service-invocation-tests.js deleted file mode 100644 index 078cb65..0000000 --- a/test/model-rule-test-decision-service-invocation-tests.js +++ /dev/null @@ -1,257 +0,0 @@ -/* -©2015-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ - -// This test case will execute a decision service -// and perform model record validation. - -// Here the validation is done for loan amount, type of loan, and, -// number of years of employment experience. The rule goes something -// like if loan amount <=1000 and experience > 6 the record is valid; -// else invalid - -// Author: deostroll - -var fs = require('fs'); -var path = require('path'); - -var async = require('async'); -var chai = require('chai'); -var chaiThings = require('chai-things'); -var chalk = require('chalk'); -var logger = require('oe-logger'); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var loopback = require('loopback'); -var bootstrap = require('./bootstrap'); - -var app = bootstrap.app; -var expect = bootstrap.chai.expect; -var log = logger('model-rule-test'); -var models = bootstrap.models; - -// var decisionTableRules = ['PropertyPopulator', 'PropertyPopulatorOne', 'validation'] // Files in mode-rule-data folder should be of same name. -var testModelName = 'ServiceModelRuleTest'; -var testModelPlural = 'ServiceModelRuleTests'; -// Model with base as PersistedModel -var testModelAsBasePM = 'ModelWithBasePM'; - -var testModel; -var testModelWithBase; - -// Model id and _version required for doing upsert -var modelRuleId, modelRuleVersion; -chai.use(chaiThings); - -describe(chalk.blue('model validations using decision service'), function () { - before('create the temporary model.', function (done) { - // Forming model metadata - var data = [{ - name: testModelName, - base: 'BaseEntity', - plural: testModelPlural, - properties: { - amount: 'number', - type:'string', - experience: 'number' - } - }]; - // Creating Model in Loopback. - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - testModel = loopback.getModel(testModelName, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - done(err); - }); - }); - - before('creating the decision graph & service', function(done){ - var graphData = JSON.parse(fs.readFileSync('test/model-rule-data/approve-graph.json', { encoding: 'utf8' })); - var feelData = JSON.parse(fs.readFileSync('test/model-rule-data/approve-feel.json', { encoding: 'utf8'})); - - var decisionGraphData = { - name: 'ApproveDecision', - decisions: [], - data: feelData, - payload: null, - graph: graphData - }; - - models.DecisionGraph.create(decisionGraphData, bootstrap.defaultContext, function(err, data){ - if (err) { - done(err) - } - else { - expect(data.name).to.equal(decisionGraphData.name); - var decisionServiceData = { - name: 'ApproveValidation', - decisions: ['Approve'], - graphId: decisionGraphData.name - }; - - models.DecisionService.create(decisionServiceData, bootstrap.defaultContext, function(err, data) { - expect(data.name).to.equal(decisionServiceData.name); - done(err); - }); - } - }); - - }); - - it('should insert into model into model rules table to register for validation without errors', function(done){ - var modelRuleData = { - modelName: testModelName, - validationRules: ['ApproveValidation'], //this is a decision service - isService: true - }; - - models.ModelRule.create(modelRuleData, bootstrap.defaultContext, done); - - }); - - it('should deny insertion of record to target model if record data is incorrect', function(done){ - this.timeout(120000); - var incorrectRecordData = { - amount: 1000, - type: 'PERSONAL_LOAN', - experience: 5 - }; - // debugger; - testModel.create(incorrectRecordData, bootstrap.defaultContext, function(err) { - if (err !== null) { - done(); - } - else { - done(new Error('test model should not have inserted the record')); - } - }); - }); - - it('should allow insertion of record to target model if record data is valid', function(done){ - this.timeout(120000); - var data = { - amount: 500, - type: 'PERSONAL_LOAN', - experience: 7 - }; - // debugger; - testModel.create(data, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err) - } - else { - expect(result).to.be.object; - expect(result.amount).to.equal(data.amount); - done(); - } - }); - }); - - after('resetting model', function(done) { - testModelName = 'ServiceModelPropertyPopulationTest'; - testModelPlural = 'ServiceModelPropertyPopulationTests'; - // console.log("After of first Describe block"); - done(); - }); -}); - -describe(chalk.blue('model data populators with decision services'), function(){ - before('create the temporary model.', function (done) { - // Forming model metadata - var data = [{ - name: testModelName, - base: 'BaseEntity', - plural: testModelPlural, - properties: { - name: 'string', - age:'number', - gender: 'string', - category: 'string' - } - }]; - // Creating Model in Loopback. - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - testModel = loopback.getModel(testModelName, bootstrap.defaultContext); - // testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - done(err); - }); - }); - - before('creating the decision graph & service', function(done){ - var graphData = JSON.parse(fs.readFileSync('test/model-rule-data/category-graph.json', { encoding: 'utf8' })); - var feelData = JSON.parse(fs.readFileSync('test/model-rule-data/category-feel.json', { encoding: 'utf8'})); - - var decisionGraphData = { - name: 'CategoryDecision', - decisions: [], - data: feelData, - payload: null, - graph: graphData - }; - - models.DecisionGraph.create(decisionGraphData, bootstrap.defaultContext, function(err, data){ - if (err) { - done(err) - } - else { - expect(data.name).to.equal(decisionGraphData.name); - var decisionServiceData = { - name: 'AssignCategory', - decisions: ['Category'], - graphId: decisionGraphData.name - }; - - models.DecisionService.create(decisionServiceData, bootstrap.defaultContext, function(err, data) { - expect(data.name).to.equal(decisionServiceData.name); - done(err); - }); - } - }); - - }); - - - it('should insert into model into model rules table to register for property population without errors', function(done){ - var modelRuleData = { - modelName: testModelName, - defaultRules: ['AssignCategory'], - isService: true - }; - - models.ModelRule.create(modelRuleData, bootstrap.defaultContext, (err, res) =>{ - if(err) { - done(err) - } - else { - done(); - } - }); - - }); - - it('should assign category to the appropriate model instance', function(done){ - var instanceData = { - name: 'Amit', - age: 32, - gender: 'M' - }; - // debugger; - testModel.create(instanceData, bootstrap.defaultContext, function(err, result) { - if (err) { - done(err) - } - else { - expect('category' in result).to.be.true; - expect(result.category).to.equal('adult'); - done(); - } - }); - }); - - after('second after block', function(done) { - // console.log("After of second Describe block"); - done(); - }); -}); \ No newline at end of file diff --git a/test/model-rule-test.js b/test/model-rule-test.js deleted file mode 100644 index 484fcb7..0000000 --- a/test/model-rule-test.js +++ /dev/null @@ -1,546 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/** - * This file tests ModelRule model and functionality of declaratively adding validation rule to model. - * The test cases are ran against both Node API's and REST api's as well. - * 1) creating ModelRule->modelName with non existent model should throw error. - * 2) create with valid data should be succesfull. - * 3) data without mandatory property value defined in rule throw validation error. - * 4) data without mandatory property value defined in rule and loopback validations throw combined validation errors. - * 5) update model rule and POST data should work. - * @author Pradeep Kumar Tippa - */ -var fs = require('fs'); -var path = require('path'); -var http = require('http'); -var async = require('async'); -var chai = require('chai'); -var chaiThings = require('chai-things'); -var chalk = require('chalk'); -var logger = require('oe-logger'); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var loopback = require('loopback'); -var bootstrap = require('./bootstrap'); - -var app = bootstrap.app; -var expect = bootstrap.chai.expect; -var log = logger('model-rule-test'); -var models = bootstrap.models; - -var decisionTableRules = ['PropertyPopulator', 'PropertyPopulatorOne', 'validation'] // Files in mode-rule-data folder should be of same name. -var testModelName = 'ModelRuleTest'; -var testModelPlural = 'ModelRuleTests'; -// Model with base as PersistedModel -var testModelAsBasePM = 'ModelWithBasePM'; - -var testModel; -var testModelWithBase; - -// Model id and _version required for doing upsert -var modelRuleId, modelRuleVersion; -chai.use(chaiThings); - -describe(chalk.blue('model-rule-test'), function () { - this.timeout(60000); - before('create the temporary model.', function (done) { - // Forming model metadata - var data = [{ - name: testModelName, - base: 'BaseEntity', - plural: testModelPlural, - properties: { - id: { - type: 'string' - }, - status: { - type: 'string', - max: 8 - }, - age: { - type: 'number', - max: 50 - }, - married: 'boolean', - sex: 'string', - husband_name: 'string', - phone: 'number', - email: 'string' - } - }, { - name: testModelAsBasePM, - base: 'PersistedModel', - properties: { - name: 'string' - } - }]; - // Creating Model in Loopback. - models.ModelDefinition.create(data, bootstrap.defaultContext, function (err, models) { - testModel = loopback.getModel(testModelName, bootstrap.defaultContext); - testModelWithBase = loopback.getModel(testModelAsBasePM, bootstrap.defaultContext); - done(err); - }); - }); - - before('create decision tables.', function (done) { - // Population Decision Table rules. - var decisionTablesData = []; - async.each(decisionTableRules, function (rule, callback) { - var obj = { - name: rule, - document: { - documentName: rule + ".xlsx", - documentData: "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," - } - }; - fs.readFile(path.join(__dirname, 'model-rule-data', obj.document.documentName), function (err, data) { - if (err) { - log.error(log.defaultContext(), 'before->create decision tables for rule ', rule, ' error: ', err); - callback(err); - } else { - obj.document.documentData = obj.document.documentData + data.toString('base64'); - decisionTablesData.push(obj); - callback(); - } - }); - }, function (ruleErr) { - if (ruleErr) { - log.error(log.defaultContext(), 'async.each->decisionTableRules final callback. Error: ', ruleErr); - done(err); - } else { - // TODO : POST the array once defect of POST with array is fixed. - // Creating Desicion Table rules. - async.each(decisionTablesData, function (decisionTable, callback) { - models.DecisionTable.create(decisionTable, bootstrap.defaultContext, function (err, res) { - if (err) log.error(log.defaultContext(), 'async.each->decisionTablesData->models.DecisionTable.create ', - 'decisionTable ', decisionTable.name, ' Error: ', err); - callback(err); - }); - }, function (decisionTableErr) { - if (decisionTableErr) log.error(log.defaultContext(), 'async.each->decisionTablesData->final callback Error: ', decisionTableErr); - done(decisionTableErr); - }); - } - }); - }); - - before('create model rules.', function (done) { - var objs = [{ - modelName: testModelName, - defaultRules: [decisionTableRules[0], decisionTableRules[1]], - validationRules: [decisionTableRules[2]] - }, { - modelName: testModelAsBasePM, - defaultRules: [], - validationRules: [] - }] - // debugger; - models.ModelRule.create(objs, bootstrap.defaultContext, function (err, modelRules) { - modelRuleId = modelRules[0].id; - modelRuleVersion = modelRules[0]._version; - done(err); - }); - }); - - describe('From Node API', function () { - it('creating ModelRule->modelName with non existent model should throw error.', function (done) { - models.ModelRule.create({ modelName: 'NonExistentModel' }, bootstrap.defaultContext, function (err, res) { - expect(err).not.to.be.null; - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('create with valid data should be succesfull.', function (done) { - //var model = loopback.findModel(testModelName); - var data = { - status: 'entered', - age: 50, - husband_name: 'Robin' - }; - // The default Rules enrich the data - // debugger; - testModel.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - console.error("model-rule-test Error ", err); - done(err); - } else { - expect(res).not.to.be.null; - expect(res).not.to.be.undefined; - expect(res.sex).to.be.equal('F'); - expect(res.married).to.be.equal(true); - expect(res.phone).to.be.equal(1234); - expect(res.email).to.be.equal('abc'); - done(); - } - }); - }); - - it('data without mandatory property value defined in rule throw validation error.', function (done) { - var data = { - status: 'entered', - age: 45 - }; - // There is a validation rule saying husband_name is mandatory in validation.xlsx - testModel.create(data, bootstrap.defaultContext, function (err, res) { - expect(err.details.codes.DecisionTable[0]).to.be.equal('err-husband-name-presence'); - done(); - }); - }); - - it('data without mandatory property value defined in rule and loopback validations throw combined validation errors.', function (done) { - var data = { - status: 'entered', //morethan8chars - age: 60 - }; - // There is a validation rule saying husband_name is mandatory in validation.xlsx - // and age is out of range ModelDefinition - testModel.create(data, bootstrap.defaultContext, function (err, res) { - var errors = JSON.parse(JSON.stringify(err.details.codes)); - var errCodes = []; - Object.keys(errors).forEach(function (v, k) { - errCodes = errCodes.concat(errors[v]); - }); - expect(errCodes.indexOf('validation-err-002')).not.to.be.equal(-1); - expect(errCodes.indexOf('err-husband-name-presence')).not.to.be.equal(-1); - /* - // When trying the below way getting the error from node. - // (node:15587) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'push' of null - expect(err.details.messages.errs.find(function(err){ - return err.code === 'validation-err-002' && err.path === (testModelName+'->age') - })).to.be.equal(true);*/ - done(); - }); - }); - - }); - - describe('From REST API', function () { - var accessToken; - var api = defaults(supertest(app)); - var baseUrl = bootstrap.basePath; - - before('Getting Access Token', function (done) { - bootstrap.login(function (resAccessToken) { - accessToken = resAccessToken; - done(); - }); - }); - - it('creating ModelRule->modelName with non existent model should throw error.', function (done) { - var url = baseUrl + '/ModelRules?access_token=' + accessToken; - var postData = { modelName: 'NonExistentModel' }; - api - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .post(url) - .send(postData) - .expect(500) - .end(function (err, response) { - if (err) { - log.error(log.defaultContext(), 'create non existent model rest api Error: ', err); - } - expect(response.body.error.status).to.be.equal(500); - done(); - }); - }); - - var modelId, modelVersion; - it('create with valid data should be succesful.', function (done) { - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - var postData = { - id: "5a57260551b1d70c2c4f6666", - status: 'entered', - age: 50, - husband_name: 'Hopper' - }; - - api - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .post(url) - .send(postData) - .expect(200) - .end(function (err, response) { - if (err) { - log.error(log.defaultContext(), 'create with valid data should be succesfull model rest api Error: ', err); - } - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.body).not.to.be.null; - expect(response.body).not.to.be.undefined; - expect(response.body.sex).to.be.equal('F'); - expect(response.body.married).to.be.equal(true); - expect(response.body.phone).to.be.equal(1234); - expect(response.body.email).to.be.equal('abc'); - modelId = response.body.id; - modelVersion = response.body._version; - done(); - }); - }); - - it('PUT with valid data should be succesful.', function (done) { - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - var putData = { - id: modelId, - _version: modelVersion, - status: 'entered', - age: 50, - husband_name: 'Pradeep' - }; - console.log("putData", putData); - api - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .put(url) - .send(putData) - .expect(200) - .end(function (err, response) { - if (err) { - log.error(log.defaultContext(), 'create with valid data should be succesfull model rest api Error: ', err); - } - console.log("response.body", response.body); - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.body).not.to.be.null; - expect(response.body).not.to.be.undefined; - expect(response.body.sex).to.be.equal('F'); - expect(response.body.married).to.be.equal(true); - expect(response.body.phone).to.be.equal(1234); - expect(response.body.email).to.be.equal('abc'); - done(); - }); - }); - - it('data without mandatory property value defined in rule throw validation error.', function (done) { - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - var postData = { - status: 'entered', - age: 45 - }; - // There is a validation rule saying husband_name is mandatory in validation.xlsx - api - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .post(url) - .send(postData) - .expect(422) - .end(function (err, response) { - if (err) { - log.error(log.defaultContext(), 'data without mandatory property value defined in rule throw validation error - rest api Error: ', err); - } - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.body).not.to.be.null; - expect(response.body).not.to.be.undefined; - expect(response.body.error).not.to.be.undefined; - expect(response.body.error).not.to.be.null; - expect(response.body.error.details).not.to.be.undefined; - expect(response.body.error.details).not.to.be.null; - expect(response.body.error.details.codes).not.to.be.undefined; - expect(response.body.error.details.codes).not.to.be.null; - expect(response.body.error.details.codes.DecisionTable[0]).to.be.equal('err-husband-name-presence'); - done(); - }); - }); - - it('data without mandatory property value defined in rule and loopback validations throw combined validation errors.', function (done) { - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - var postData = { - status: 'entered', //morethan8chars - age: 60 - }; - // There is a validation rule saying husband_name is mandatory in validation.xlsx - // and age is out of range ModelDefinition - api - .set('tenant_id', 'test-tenant') - .set('Accept', 'application/json') - .post(url) - .send(postData) - .expect(422) - .end(function (err, response) { - if (err) { - log.error(log.defaultContext(), 'data without mandatory property value defined in rule and ' + - 'loopback validations throw combined validation errors - rest api Error: ', err); - } - - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.body).not.to.be.null; - expect(response.body).not.to.be.undefined; - expect(response.body.error).not.to.be.undefined; - expect(response.body.error).not.to.be.null; - expect(response.body.error.details).not.to.be.undefined; - expect(response.body.error.details).not.to.be.null; - expect(response.body.error.details.codes).not.to.be.undefined; - expect(response.body.error.details.codes).not.to.be.null; - var errors = response.body.error.details.codes; - var errCodes = []; - Object.keys(response.body.error.details.codes).forEach(function (v, k) { - errCodes = errCodes.concat(errors[v]); - }); - expect(errCodes.indexOf('validation-err-002')).not.to.be.equal(-1); - expect(errCodes.indexOf('err-husband-name-presence')).not.to.be.equal(-1); - done(); - }); - }); - - // This test case has to be executed in the before delete modelRule test. - it('update model rule and POST data should work.', function (done) { - var obj = { - modelName: testModelName, - id: modelRuleId, - _version: modelRuleVersion, - defaultRules: [], - validationRules: [] - }; - models.ModelRule.upsert(obj, bootstrap.defaultContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), 'update model rule and POST data should work. Error: ', err); - done(err); - } else { - var postData = { - status: 'reborn', - age: 45 - }; - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - - - - - // An object of options to indicate where to post to - var post_options = { - host: 'localhost', - port: '3000', - path: '/api/' + testModelPlural + '?access_token=' + accessToken, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'tenant_id': 'test-tenant', - 'Accept': 'application/json' - } - }; - - // Set up the request - var post_req = http.request(post_options, function(res) { - res.setEncoding('utf8'); - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function(){ - var response = JSON.parse(data); - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.status).to.be.equal('reborn'); - expect(response.age).to.be.equal(45); - done(); - }); - }); - - // post the data - post_req.write(JSON.stringify(postData)); - post_req.end(); - post_req.on('error', done); - } - }); - }); - - // This test case has to be executed in the end since we are deleting the one of modelRule - it('delete model rule and POST data should work.', function (done) { - models.ModelRule.destroyById(modelRuleId, bootstrap.defaultContext, function (err) { - if (err) { - log.error(log.defaultContext(), 'delete model rule and POST data should work. Error: ', err); - done(err); - } else { - var postData = { - status: 'energized', - age: 45 - }; - var url = baseUrl + '/' + testModelPlural + '?access_token=' + accessToken; - - // An object of options to indicate where to post to - var post_options = { - host: 'localhost', - port: '3000', - path: '/api/' + testModelPlural + '?access_token=' + accessToken, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'tenant_id': 'test-tenant', - 'Accept': 'application/json' - } - }; - - // Set up the request - var post_req = http.request(post_options, function(res) { - res.setEncoding('utf8'); - var data = ''; - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function () { - var response = JSON.parse(data); - expect(response).not.to.be.null; - expect(response).not.to.be.undefined; - expect(response.error).not.to.be.undefined; - expect(response.error).not.to.be.null; - expect(response.error.details).not.to.be.undefined; - expect(response.error.details).not.to.be.null; - expect(response.error.details.codes.status[0]).to.be.equal('validation-err-002'); - done(); - }); - }); - - // post the data - post_req.write(JSON.stringify(postData)); - post_req.end(); - post_req.on('error', done); - } - }); - }); - }); - - after('cleanup data ModelDefinition', function (done) { - var query = { - name: { - inq: [testModelName, testModelAsBasePM] - } - }; - models.ModelDefinition.destroyAll(query, bootstrap.defaultContext, function (err, count) { - done(err); - }); - }); - - after('cleanup data Decision Tables', function (done) { - var query = { - name: { - inq: decisionTableRules - } - }; - models.DecisionTable.destroyAll(query, bootstrap.defaultContext, function (err, count) { - done(err); - }); - }); - - after('cleanup data ModelRule', function (done) { - var query = { - modelName: { - inq: [testModelName, testModelAsBasePM] - } - }; - models.ModelRule.destroyAll(query, bootstrap.defaultContext, function (err, count) { - done(err); - }); - }); - - after('cleanup test model data', function (done) { - testModel.destroyAll({}, bootstrap.defaultContext, function (err, count) { - done(err); - }); - }); -}); diff --git a/test/model-transaction-test.js b/test/model-transaction-test.js deleted file mode 100644 index 31c2282..0000000 --- a/test/model-transaction-test.js +++ /dev/null @@ -1,315 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * UnitTest Cases for transaction handling in mongodb - * - * - * Steps involved : 1. clear all model data - * 2. begin transaction - * 3. create an model - * 4. create another model - * ** do set of create and update operations - * 5. commit and database should have 2 entries - * - * ************************************* - * 6. begin another transaction - * 7. create and entry - * 8. create same entry (it should through error for unique constraint) - * 9. if error, rollback - * 10. database should not have any new entry - * - * - */ - -//var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); -var uTxnId = 1234; -//var debug = require('debug')('model-transaction-test'); - - - -describe(chalk.blue('transaction test'), function() { - - this.timeout(12000); - - var orderTransactionModel; - var shipmentModel; - - before('create models', function(done) { - models.ModelDefinition.create({ - name: 'Shipment', - base: 'BaseEntity', - plural: 'Shipment', - properties: { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - }, - 'products': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }, bootstrap.defaultContext, function(err, shipment) { - if (err) { - done(err); - } else { - expect(err).to.not.be.ok; - models.ModelDefinition.create({ - name: 'OrderTransactionModel', - base: 'BaseEntity', - plural: 'OrderTransactionModels', - properties: { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - }, - 'shipments': { - 'type': 'string', - 'required': true - } - }, - filebased: false - }, bootstrap.defaultContext, function(err, order) { - if (err) { - return done(err); - } - expect(err).to.not.be.ok; - orderTransactionModel = loopback.getModel('OrderTransactionModel', bootstrap.defaultContext); - shipmentModel = loopback.getModel('Shipment', bootstrap.defaultContext); - done(); - }); - } - }); - }); - - after('cleanup', function(done) { - models.ModelDefinition.destroyAll({ - name: "Shipment" - }, bootstrap.defaultContext, function a(err) {}); - models.ModelDefinition.destroyAll({ - name: "OrderTransactionModel" - }, bootstrap.defaultContext, function a(err) {}); - done(); - }); - - it('should start a transaction and commit successfully', function(done) { - - shipmentModel.beginTransaction({ - isolationLevel: shipmentModel.Transaction.READ_COMMITTED - }, function(err, tx) { - expect(err).to.not.be.ok; - var options = JSON.parse(JSON.stringify(bootstrap.defaultContext)); - options.transaction = tx; - shipmentModel.create({ - 'name': 'shipmentOne', - 'products': 'Monitor' - }, options, function(err, res2) { - if (err) { - done(err); - } else { - orderTransactionModel.create({ - 'name': 'orderOne', - 'shipments': 'shipment one' - }, options, function(err, res4) { - if (err) { - tx.rollback(function(err) { - done(err); - }); - } else { - tx.commit(function(err) { - if (err) { - done(err); - } else { - done(); - } - }); - } - }); - } - }); - }); - - }); - - it('should start a transaction and rollback for error, rollback successfully', function(done) { - shipmentModel.beginTransaction({ - isolationLevel: shipmentModel.Transaction.READ_COMMITTED - }, function(err, tx) { - expect(err).to.not.be.ok; - var options = JSON.parse(JSON.stringify(bootstrap.defaultContext)); - options.transaction = tx; - shipmentModel.create({ - 'name': 'shipmentThree', - 'products': 'Monitor' - }, options, function(err, res2) { - expect(err).to.be.null; - shipmentModel.create({ - 'name': 'shipmentFour', - 'products': 'Monitor' - }, options, function(err, res3) { - expect(err).to.be.null; - orderTransactionModel.create({ - 'name': 'orderThree', - 'shipments': 'shipment one and two' - }, options, function(err, res4) { - expect(err).to.be.null; - orderTransactionModel.create({ - 'name': 'orderOne', - 'shipments': 'shipment one and two' - }, options, function(err, res4) { - expect(err).not.to.be.null; // unique constraint - if (err) { - tx.rollback(function(err) { - expect(err).to.be.null; - done(err); - }); - } else { - tx.commit(function(err) { - done(err); - }); - } - }); - }); - }); - }); - }); - }); - - it('should start a transaction, do update, and then commit', function(done) { - shipmentModel.beginTransaction({ - isolationLevel: shipmentModel.Transaction.READ_COMMITTED - }, function(err, tx) { - expect(err).to.not.be.ok; - var options = JSON.parse(JSON.stringify(bootstrap.defaultContext)); - options.transaction = tx; - uTxnId = tx.connection.transactionId; - shipmentModel.create({ - 'name': 'shipment100', - 'products': 'mouse' - }, options, function(err, res2) { - expect(err).to.be.null; - shipmentModel.create({ - 'name': 'shipment101', - 'products': 'keyboard' - }, options, function(err, res3) { - expect(err).to.be.null; - orderTransactionModel.find({}, options, function(err, res) { - orderTransactionModel.upsert({ - 'name': res[0].name, - 'shipments': 'shipment updated 100 and 101', - 'id': res[0].id, - '_version': res[0]._version - }, options, function(err, res4) { - expect(err).to.be.null; - if (err) { - tx.rollback(function(err) { - done(err); - }); - } else { - tx.commit(function(err) { - expect(err).to.be.null; - done(err); - }); - } - }); - }); - - }); - }); - }); - - }); - - it('should start a transaction, do update, and then rollback for error', function(done) { - shipmentModel.beginTransaction({ - isolationLevel: shipmentModel.Transaction.READ_COMMITTED - }, function(err, tx) { - expect(err).to.not.be.ok; - var options = JSON.parse(JSON.stringify(bootstrap.defaultContext)); - options.transaction = tx; - shipmentModel.create({ - 'name': 'shipment1000', - 'products': 'Monitor' - }, options, function(err, res2) { - expect(err).to.be.null; - shipmentModel.create({ - 'name': 'shipment1001', - 'products': 'Monitor' - }, options, function(err, res3) { - expect(err).to.be.null; - orderTransactionModel.find({}, options, function(err, res) { - orderTransactionModel.upsert({ - 'name': res[0].name, - 'shipments': 'shipment updated 1000 and 1001', - 'id': res[0].id, - '_version': res[0]._version - }, options, function(err, res4) { - expect(err).to.be.null; - orderTransactionModel.create({ - 'name': 'orderOne', - 'shipments': 'shipment one and two again' - }, options, function(err, res4) { - expect(err).not.to.be.null; // unique constraint - if (err) { - tx.rollback(function(err) { - expect(err).to.be.null; - done(err); - }); - } else { - tx.commit(function(err) { - done(err); - }); - } - }); - }); - }); - - }); - }); - }); - - }); - - it('should emit reconcile job with valid data', function(done) { - var dbT = models['DbTransaction']; - if (!dbT.dataSource.isRelational()) { - dbT.update({ "transactionId": uTxnId }, { "status": "changing" }, function(err, res) { - if (!err) { - dbT.emit('reconcile', { - 'transactionId': uTxnId, - 'options': bootstrap.defaultContext - }); //at this moment no callback - done(); - } else { - done(err); - } - }); - } else { - console.log('***Below testcases are bypassed as they are running on relational database***'); - done(); - } - }); - - it('should emit reconcile job with no data', function(done) { - var dbtx = models['DbTransaction']; - dbtx.emit('reconcile', { - 'transaction': '345', - 'options': bootstrap.defaultContext - }); - done(); - }); -}); \ No newline at end of file diff --git a/test/model-validation-composite-uniqueness-test.js b/test/model-validation-composite-uniqueness-test.js deleted file mode 100644 index 610c810..0000000 --- a/test/model-validation-composite-uniqueness-test.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - -var modelName = 'Organisation'; - -describe(chalk.blue('Composite Uniqueness Validation test'), function () { - - this.timeout(20000); - var organisationModel; - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - done(); - }); - - models.ModelDefinition.create({ - "name": "Organisation", - "base": "BaseEntity", - "plural": "organisations", - "strict": false, - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": { - "category": { - "type": "string", - "required": true - }, - "name": { - "type": "string", - "required": true, - "unique": { - "scopedTo": ["location", "category"] - } - }, - "location": { - "type": "string", - "required": true - }, - "revenue": { - "type": "number", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - organisationModel = loopback.getModel(modelName, bootstrap.defaultContext); - }); - }); - - - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: modelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - organisationModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - - it('Validation Test - Should fail to insert data', function (done) { - - var data1 = { - "category": "5", - "location": "BLR", - "name": "CROWN", - "revenue": "1000000" - }; - - var data2 = { - "category": "5", - "location": "BLR", - "name": "CROWN", - "revenue": "7000000" - }; - - organisationModel.create(data1, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - organisationModel.create(data2, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - }); - - it('Validation Test - Should insert data successfully', function (done) { - var data1 = { - "category": "7", - "location": "MUM", - "name": "TAJ", - "revenue": "1000000" - }; - - var data2 = { - "category": "7", - "location": "DL", - "name": "TAJ", - "revenue": "1000000" - }; - - organisationModel.create(data1, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - organisationModel.create(data2, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - }); - -}); diff --git a/test/model-validation-custom-function-test.js b/test/model-validation-custom-function-test.js deleted file mode 100644 index ac3705b..0000000 --- a/test/model-validation-custom-function-test.js +++ /dev/null @@ -1,124 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - -var modelName = 'OrganisationModel'; -describe(chalk.blue('Validation custom function test'), function () { - - this.timeout(50000); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + modelName + '-available', function () { - done(); - }); - - models.ModelDefinition.create({ - 'name': modelName, - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'unique': true, - 'required': true - }, - 'location': { - 'type': 'string' - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, modelDefinitionData) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.customValidations = []; - var customArr = []; - - function locationLengthCheck(data, options, cb) { - if (data.location.length < 4) { - cb(null, { - 'fieldName': 'location', - 'errMessage': 'minimum length violated for location', - 'errCode': 'custom-err' - }); - } else { - cb(null, null); - } - } - - function locationValueCheck(data, options, cb) { - if (['INDIA', 'AUSTRALIA', 'CANADA'].indexOf(data.location) < 0) { - cb(null, { - 'fieldName': 'location', - 'errMessage': 'location is out of the allowed list', - 'errCode': 'custom-err' - }); - } else { - cb(null, null); - } - } - - customArr.push(locationLengthCheck); - customArr.push(locationValueCheck); - - model.customValidations = customArr; - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: modelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - - it('Validation Test - Should fail to insert data', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var data = { - 'location': "USA" - }; - model.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.not.null; - done(); - }); - }); - it('Validation Test - Should successfully insert data', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var data = { - 'name': "OrganisationOne", - 'location': "INDIA" - }; - model.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - -}); \ No newline at end of file diff --git a/test/model-validation-embeddedModel-test.js b/test/model-validation-embeddedModel-test.js deleted file mode 100644 index 7e7f840..0000000 --- a/test/model-validation-embeddedModel-test.js +++ /dev/null @@ -1,307 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -//var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -//var supertest = require('supertest'); -//var api = supertest(app); -var parentModelName = 'HotelModel'; -var childModelName = 'RoomModel'; -//var modelNameUrl = bootstrap.basePath + '/' + parentModelName; -//var records = []; - -describe(chalk.blue('Embedded Model Validation test'), function () { - //var loopbackContext; - this.timeout(20000); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + parentModelName + '-available', function () { - done(); - }); - models.ModelDefinition.create([{ - 'name': childModelName, - 'base': 'BaseEntity', - 'plural': childModelName + 's', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'category': { - 'type': 'string', - 'min': 2 - }, - 'price': { - 'type': 'number', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, { - 'name': 'RestaurantModel', - 'base': 'BaseEntity', - 'plural': 'RestaurantModels', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'cuisine': { - 'type': 'string', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, { - 'name': 'LocationModel', - 'base': 'BaseEntity', - 'plural': 'LocationModels', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'country': { - 'type': 'string', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }], bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': parentModelName, - 'base': 'BaseEntity', - 'plural': parentModelName + 's', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - }, - 'rating': { - 'type': 'number', - 'required': true - }, - 'typesOfRoom': { - 'type': ['RoomModel'], - 'required': true, - 'min': 2 - } - }, - 'validations': [], - 'relations': { - '_restaurants': { - 'type': 'embedsMany', - 'model': 'RestaurantModel', - 'property': 'restaurants' - }, - '_locations': { - 'type': 'embedsMany', - 'model': 'LocationModel', - 'property': 'locations', - 'options': { - 'validate': false - } - } - }, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.null; - }); - }); - - - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: parentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for parent Model Hotel'); - return done(); - } - var model = loopback.getModel(parentModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: { inq: ['RoomModel', 'RestaurantModel', 'LocationModel'] } - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for child Model Room'); - return done(); - } - var model = loopback.getModel(childModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - - - afterEach('destroy execution context', function (done) { - done(); - }); - - it('Validation Test - Should insert data successfully', function (done) { - - var parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - - var data = { - 'name': 'TAJ', - 'rating': 7, - 'typesOfRoom': [ - { - 'category': 'Suite', - 'price': 10000 - }, - { - 'category': 'Deluxe', - 'price': 7000 - } - ] - }; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data successfully as price property is required for Room', function (done) { - - var parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - - var data = { - 'name': 'TAJ', - 'rating': 7, - 'typesOfRoom': [ - { - 'category': 'Suite', - 'price': 10000 - }, - { - 'category': 'Deluxe' - }, - { - 'category': '', - 'price': 1000 - }, - { - 'category': null, - 'price': 100 - } - ] - }; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail to insert data successfully as category can only have alphabets', function (done) { - - var childModel = loopback.getModel(childModelName, bootstrap.defaultContext); - - var data = { - 'category': 'Suite123', - 'price': 10000 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should insert data successfully as validate options is false for _locations relation', function (done) { - - var parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - - var data = { - 'name': 'TAJ', - 'rating': 7, - 'typesOfRoom': [ - { - 'category': 'Suite', - 'price': 10000 - }, - { - 'category': 'Deluxe', - 'price': 7000 - } - ], - "restaurants": [{ - "cuisine": "thai" - }], - "locations": [{}] - }; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as validation for _restaurants relation is by default true', function (done) { - - var parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - - var data = { - 'name': 'TAJ', - 'rating': 7, - 'typesOfRoom': [ - { - 'category': 'Suite', - 'price': 10000 - }, - { - 'category': 'Deluxe', - 'price': 7000 - } - ], - "restaurants": [{}], - "locations": [{}] - }; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - expect(err).not.to.be.null; - done(); - }); - }); - -}); diff --git a/test/model-validation-oeValidation-custom-test.js b/test/model-validation-oeValidation-custom-test.js deleted file mode 100644 index d16e3f3..0000000 --- a/test/model-validation-oeValidation-custom-test.js +++ /dev/null @@ -1,280 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var parentModelName = 'Location'; -var orderModel = 'OrderModel'; -// Order Model with Static String for comparison in expression. -var orderModel_SS = 'OrderModel_SS'; - -describe(chalk.blue('oeCloud Validation Custom test'), function () { - - this.timeout(20000); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + orderModel_SS + '-available', function () { - var parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - var data = [{ - "companyCode": "Company1", - "locationCode": "Branch1" - }, { - "companyCode": "SellerCompany", - "locationCode": "BranchSeller1" - }, { - "companyCode": "SellerCompany", - "locationCode": "BranchSeller2" - }, { - "companyCode": "Company1", - "locationCode": "Branch2" - }]; - - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - - }); - - models.ModelDefinition.create({ - "name": "Location", - "base": "BaseEntity", - "idInjection": false, - "strict": "validate", - "options": { - "validateUpsert": true - }, - "properties": { - "companyCode": { - "type": "string", - "required": true - }, - "locationCode": { - "type": "string", - "required": true, - "max": 200 - } - } - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log('oeCloud Validation Custom test : Error in create model ', err); - } else { - models.ModelDefinition.create({ - "name": orderModel, - "plural": orderModel + "s", - "base": "BaseEntity", - "strict": false, - "idInjection": false, - "options": { - "validateUpsert": true - }, - "properties": { - "buyerCompanyCode": { - "type": "string", - "required": true - }, - "requestedBillingLocation": { - "type": "Location", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {}, - "oeValidations": { - "requestedBillingCompanyCheck": { - "validateWhen": {}, - "type": "custom", - "expression": "(@mLocation.companyCode where locationCode = @i.requestedBillingLocation.locationCode and companyCode = @i.buyerCompanyCode) == @i.buyerCompanyCode" - } - } - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log('Error creating Order model definition', err); - } else { - models.ModelDefinition.create({ - "name": orderModel_SS, - "plural": orderModel_SS + "s", - "base": "BaseEntity", - "strict": false, - "idInjection": false, - "options": { - "validateUpsert": true - }, - "properties": { - "buyerCompanyCode": { - "type": "string", - "required": true - }, - "requestedBillingLocation": { - "type": "Location", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [], - "methods": {}, - "oeValidations": { - "requestedBillingCompanyCheck": { - "validateWhen": {}, - "type": "custom", - "expression": "(@mLocation.companyCode where locationCode = @i.requestedBillingLocation.locationCode and companyCode = \"SellerCompany\") == @i.buyerCompanyCode" - } - } - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log('Error creating Order model Static String definition', err); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: parentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for parent Model Hotel'); - return done(); - } - var model = loopback.getModel(parentModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: orderModel - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for orderModel'); - return done(); - } - var model = loopback.getModel(orderModel, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: orderModel_SS - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for orderModel_SS'); - return done(); - } - var model = loopback.getModel(orderModel_SS, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - }); - }); - - - it('Validation Test orderModel - Should insert data successfully', function (done) { - - var childModel = loopback.getModel(orderModel, bootstrap.defaultContext); - - var data = [{ - "buyerCompanyCode": "SellerCompany", - "requestedBillingLocation": { - "companyCode": "SellerCompany", - "locationCode": "BranchSeller1" - } - }, { - "buyerCompanyCode": "Company1", - "requestedBillingLocation": { - "companyCode": "Company1", - "locationCode": "Branch2" - } - }]; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test orderModel - Should fail to insert data', function (done) { - - var childModel = loopback.getModel(orderModel, bootstrap.defaultContext); - - var data = { - "buyerCompanyCode": "Company1", - "requestedBillingLocation": { - "companyCode": "Company1", - "locationCode": "BranchSeller2" - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - // Test cases for allowing Static String comparison. - it('Validation Test Static String - Should fail to insert data when string equality returns false', function (done) { - - var childModel = loopback.getModel(orderModel_SS, bootstrap.defaultContext); - - var data = { - "buyerCompanyCode": "Company1", - "requestedBillingLocation": { - "companyCode": "Company1", - "locationCode": "Branch1" - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test Static String - Should fail to insert data when string equality returns true, then fails at main expression.', function (done) { - - var childModel = loopback.getModel(orderModel_SS, bootstrap.defaultContext); - - var data = { - "buyerCompanyCode": "Company1", - "requestedBillingLocation": { - "companyCode": "SellerCompany", - "locationCode": "BranchSeller1" - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test Static String - Should insert successfully.', function (done) { - - var childModel = loopback.getModel(orderModel_SS, bootstrap.defaultContext); - - var data = { - "buyerCompanyCode": "SellerCompany", - "requestedBillingLocation": { - "companyCode": "SellerCompany", - "locationCode": "BranchSeller2" - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - -}); \ No newline at end of file diff --git a/test/model-validation-oeValidation-test.js b/test/model-validation-oeValidation-test.js deleted file mode 100644 index c9a59e3..0000000 --- a/test/model-validation-oeValidation-test.js +++ /dev/null @@ -1,265 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -//var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -//var supertest = require('supertest'); -//var api = supertest(app); -var parentModelName = 'VehicleModel'; -var childModelName = 'Car'; -var errorModelName = 'Error'; -//var modelNameUrl = bootstrap.basePath + '/' + parentModelName; -//var records = []; - -describe(chalk.blue('EV Validation test'), function () { - //var loopbackContext; - this.timeout(20000); - var errorModel; - var childModel; - var parentModel; - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + childModelName + '-available', function () { - return; - var data = [{ - 'fuel': 'petrol' - }, - { - 'fuel': 'diesel', - 'scope': { - 'location': 'INDIA' - } - }]; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - models.ModelDefinition.create({ - 'name': parentModelName, - 'base': 'BaseEntity', - 'plural': parentModelName + 's', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'fuel': { - 'type': 'string', - 'required': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Car', - 'base': 'BaseEntity', - 'plural': 'cars', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - }, - 'fuelType': { - 'type': 'string', - 'required': true - }, - 'testField1': { - 'type': 'string' - }, - 'testField2': { - 'type': 'string', - 'allowScript': true - } - }, - 'validations': [], - 'oeValidations': { - 'fuelCheck': { - 'validateWhen': {}, - 'type': 'reference', - 'errorCode': 'fuel-err-001', - 'refModel': 'VehicleModel', - 'refWhere': '{\"fuel\":\"{{fuelType}}\"}' - } - }, - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - childModel = loopback.getModel(childModelName, bootstrap.defaultContext); - errorModel = loopback.getModel(errorModelName); - expect(err).to.be.not.ok; - - var data = [{ - 'fuel': 'petrol' - }, - { - 'fuel': 'diesel', - 'scope': { - 'location': 'INDIA' - } - }]; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - return done(); - }); - - }); - } - expect(err).to.be.not.ok; - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: parentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for parent Model Hotel'); - return done(); - } - parentModel.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: childModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for child Model Room'); - return done(); - } - childModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - - it('Validation Test - Should insert data successfully as default scope has a record for fuel petrol', function (done) { - - var data = { - 'name': 'BMW', - 'fuelType': 'petrol' - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Prevent string field to have a script tag', function (done) { - - var data = { - 'name': 'Tata', - 'fuelType': 'petrol', - 'testField1': '' - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Allow string field to have a script tag when allowScript is true', function (done) { - - var data = { - 'name': 'Tata', - 'fuelType': 'petrol', - 'testField2': '' - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as default scope do not have any record for fuel diesel', function (done) { - - var data = { - 'name': 'BMW', - 'fuelType': 'diesel' - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully as provided scope has a record for fuel diesel', function (done) { - - var data = { - 'name': 'BMW', - 'fuelType': 'diesel', - 'scope': { - 'location': 'INDIA' - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data and error message is same as error code', function (done) { - var data = { - 'name': 'KIA', - 'fuelType': 'diesel' - }; - - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err.details.messages.fuelCheck[0]).to.equal(err.details.codes.fuelCheck[0]); - done(); - }); - }); - - it('Validation Test - Should fail to insert data and error message should not be equal to error code', function (done) { - - var errorData = { - "errCode": "fuel-err-001", - "errMessage": "{{name}} in {{path}} is not valid" - }; - var data = { - 'name': 'KIA', - 'fuelType': 'diesel' - }; - - errorModel.create(errorData, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.not.be.null; - expect(err.details.messages.fuelCheck[0]).to.not.equal(err.details.codes.fuelCheck[0]); - done(); - }); - }); - }); - -}); diff --git a/test/model-validation-refcode-test.js b/test/model-validation-refcode-test.js deleted file mode 100644 index 6dc53d5..0000000 --- a/test/model-validation-refcode-test.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - -describe(chalk.blue('RefCode Validation test'), function() { - - this.timeout(20000); - var cType; - var motel; - var serviceApt; - - - before('setup test data', function(done) { - models.ModelDefinition.events.once('model-ServiceApt-available', function() { - return; - }); - - models.ModelDefinition.create({ - 'name': 'CountryType', - 'base': 'RefCodeBase', - 'plural': 'CountryTypes', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': {}, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function(err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Motel', - 'base': 'BaseEntity', - 'plural': 'Motels', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - }, - 'countryIn': { - 'type': 'array', - "refcodetype": "CountryType" - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function(err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'ServiceApt', - 'base': 'BaseEntity', - 'plural': 'ServiceApts', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string' - }, - 'countryIn': { - 'type': 'string', - 'refcodetype': 'CountryType' - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function(err, model) { - if (err) { - console.log(err); - } - cType = loopback.getModel('CountryType', bootstrap.defaultContext); - motel = loopback.getModel('Motel', bootstrap.defaultContext); - serviceApt = loopback.getModel('ServiceApt', bootstrap.defaultContext); - var data = [{ - 'code': 'IN', - 'description': 'India' - }, - { - 'code': 'US', - 'description': 'USA' - }, - { - 'code': 'UK', - 'description': 'United Kingdom' - }, - { - 'code': 'CA', - 'description': 'Canada' - }, - { - 'code': 'DE', - 'description': 'Germany' - }, - { - 'code': 'AU', - 'description': 'Australia' - } - ]; - cType.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).to.be.null; - done(); - }); - }); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - - }); - - after('destroy test models', function(done) { - models.ModelDefinition.destroyAll({ - name: 'CountryType' - }, bootstrap.defaultContext, function(err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for CountryType'); - } - cType.destroyAll({}, bootstrap.defaultContext, function() { - models.ModelDefinition.destroyAll({ - name: 'Motel' - }, bootstrap.defaultContext, function(err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for motel'); - } - motel.destroyAll({}, bootstrap.defaultContext, function() { - models.ModelDefinition.destroyAll({ - name: 'ServiceApt' - }, bootstrap.defaultContext, function(err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for ServiceApt'); - } - serviceApt.destroyAll({}, bootstrap.defaultContext, function() { - done(); - }); - }); - }); - }) - }); - }); - }); - - - afterEach('destroy execution context', function(done) { - done(); - }); - - it('refcode Test - Should insert data with array ref code successfully', function(done) { - var data = { - 'name': 'Holiday Inn', - 'countryIn': ["IN", "US", "UK"] - }; - motel.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Refcode Test - Should insert data with single refcode successfully', function(done) { - var data = { - 'name': 'Palm Homestay', - 'countryIn': "IN" - }; - serviceApt.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('refcode Test - Should fail to insert data as array contains invalid ref code', function(done) { - var data = { - 'name': 'Mariott', - 'countryIn': ['IN', 'US', 'PK'] - }; - motel.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).not.to.be.null; - expect(err.toString().indexOf('PK')).to.be.above(-1); - done(); - }); - }); - - it('refcode Test - Should fail to insert data as invalid ref code is given', function(done) { - var data = { - 'name': 'Mane Homestey', - 'countryIn': 'JP' - }; - serviceApt.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).not.to.be.null; - expect(err.toString().indexOf('JP')).to.be.above(-1); - done(); - }); - }); - - it('refcode Test - Should insert data successfully as no refcode given', function(done) { - var data = { - 'name': 'Hilton' - }; - motel.create(data, bootstrap.defaultContext, function(err, results) { - expect(err).to.be.null; - done(); - }); - }); -}); \ No newline at end of file diff --git a/test/model-validation-relation-test.js b/test/model-validation-relation-test.js deleted file mode 100644 index af8fed1..0000000 --- a/test/model-validation-relation-test.js +++ /dev/null @@ -1,405 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); -var parentModelName = 'Hotel'; -var childModelName = 'Room'; -var restaurantModelName = 'Restaurant'; -var courseModelName = 'Course'; -var universityModelName = 'University'; - -describe(chalk.blue('Relation Validation test'), function () { - - this.timeout(20000); - var parentModel; - var childModel; - var restaurantModel; - var courseModel; - var universityModel; - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + courseModelName + '-available', function () { - return; - }); - - models.ModelDefinition.create({ - 'name': 'Hotel', - 'base': 'BaseEntity', - 'plural': 'hotels', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'name': { - 'type': 'string', - 'required': true - }, - 'rating': { - 'type': 'number', - 'required': true - }, - 'city': { - 'type': 'string', - 'required': true - }, - 'hotelId': { - 'type': 'number', - 'required': true, - 'id': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Room', - 'base': 'BaseEntity', - 'plural': 'rooms', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'category': { - 'type': 'string', - 'required': true - }, - 'price': { - 'type': 'number', - 'required': true - } - }, - 'validations': [], - 'relations': { - 'sample_relation': { - 'type': 'belongsTo', - 'model': 'Hotel', - 'foreignKey': 'roomId' - } - }, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Restaurant', - 'base': 'BaseEntity', - 'plural': 'restaurants', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'name': { - 'type': 'string' - }, - 'cuisine': { - 'type': 'string' - } - }, - 'validations': [], - 'relations': { - 'sample_relation': { - 'type': 'belongsTo', - 'model': 'Hotel', - 'foreignKey': 'resId', - 'foreignKeyRequired': true - } - }, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Course', - 'base': 'BaseEntity', - 'plural': 'courses', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'mixins': { - "IdempotentMixin": false - }, - 'properties': { - 'name': { - 'type': 'string' - }, - 'course_num': { - 'type': 'string' - } - }, - 'validations': [], - 'relations': { - 'sample_relation': { - 'type': 'belongsTo', - 'model': 'University', - 'foreignKey': 'universityId', - 'foreignKeyRequired': true - } - }, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - childModel = loopback.getModel(childModelName, bootstrap.defaultContext); - restaurantModel = loopback.getModel(restaurantModelName, bootstrap.defaultContext); - courseModel = loopback.getModel(courseModelName, bootstrap.defaultContext); - expect(err).to.be.not.ok; - var data = [{ - 'name': 'TAJ', - 'rating': 7, - 'city': 'MUM', - 'hotelId': 29 - }, - { - 'name': 'OBEROI', - 'rating': 8, - 'city': 'DL', - 'hotelId': 98, - 'scope': { - 'dev': 'android' - } - }]; - parentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: parentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for parent Model Hotel'); - return done(); - } - parentModel.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: childModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for child Model Room'); - return done(); - } - childModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - - - afterEach('destroy execution context', function (done) { - done(); - }); - - it('Validation Test - Should insert data successfully', function (done) { - var data = { - 'category': 'Suite', - 'price': 10000, - 'roomId': 29 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully as no foreignKey required constraint is there', function (done) { - var data = { - 'category': 'Deluxe', - 'price': 5000 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as the provided foreignKey doesnot exist', function (done) { - var data = { - 'category': 'Suite', - 'price': 10000, - 'roomId': 28 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as the provided scope donot have the corresponding foreignKey entry', function (done) { - var data = { - 'category': 'Suite', - 'price': 10000, - 'roomId': 98, - 'scope': { - 'dev': 'ios' - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully', function (done) { - var data = { - 'category': 'Suite', - 'price': 10000, - 'roomId': 98, - 'scope': { - 'dev': 'android' - } - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as default scope donot have the corresponding foreignKey entry', function (done) { - var data = { - 'category': 'Standard', - 'price': 3000, - 'roomId': 98 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully', function (done) { - var data = { - 'name': 'Truffles', - 'cuisine': 'Italian', - 'resId': 29 - }; - restaurantModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as foreign key is mandatory but not posted', function (done) { - var data = { - 'name': 'BeijingBites', - 'cuisine': 'Chinese' - }; - restaurantModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data as target model does not exist', function (done) { - var data = { - 'course_num': '6-042', - 'name': 'Mathematics for Computer Science' - }; - courseModel.create(data, bootstrap.defaultContext, function (err) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully as target model is available', function (done) { - - models.ModelDefinition.create({ - 'name': 'University', - 'base': 'BaseEntity', - 'plural': 'universities', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string' - }, - 'university_code': { - 'type': 'string', - 'id': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - expect(err).to.be.not.ok; - if (err) { - console.log(err); - } else { - universityModel = loopback.getModel(universityModelName, bootstrap.defaultContext); - - var universityData = { - 'university_code': 'U_090', - 'name': 'MIT' - }; - - var courseData = { - 'course_num': '6-042', - 'name': 'Mathematics for Computer Science', - 'universityId': 'U_090' - }; - universityModel.create(universityData, bootstrap.defaultContext, function (err) { - expect(err).to.be.null; - courseModel.create(courseData, bootstrap.defaultContext, function (err) { - if (err) { - done(err); - } - expect(err).to.be.null; - done(); - }); - }); - } - }); - }); - -}); diff --git a/test/model-validation-test.js b/test/model-validation-test.js deleted file mode 100644 index b3af5ae..0000000 --- a/test/model-validation-test.js +++ /dev/null @@ -1,452 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); -var api = bootstrap.api; -var url = bootstrap.basePath; - -var starWarsModelName = 'StarWars'; -var starTrekModelName = 'StarTrek'; - -describe(chalk.blue('model-validation PropertyLevel Validation test'), function () { - this.timeout(20000); - var starWarsModel; - var startTrekModel; - - var testUsertoken; - - before('Create testuser Accesstoken', function(done) { - var testUser = { - 'username': 'testuser', - 'password': 'testuser123' - }; - bootstrap.login(testUser, function(returnedAccesstoken) { - testUsertoken = returnedAccesstoken; - done(); - }); - }); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + starWarsModelName + '-available', function () { - done(); - }); - - models.ModelDefinition.create([ - { - 'name': starTrekModelName, - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'name': { - 'type': 'string', - 'unique': 'ignoreCase' - } - } - }, - { - 'name': starWarsModelName, - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true, - "disableManualPersonalization":false - }, - 'properties': { - 'name': { - 'type': 'string', - 'unique': true, - 'min': 4, - 'max': 7 - }, - 'numericField1': { - 'type': 'number', - 'numericality': 'integer' - }, - 'numericField2': { - 'type': 'number', - 'absence': true - }, - 'numericField3': { - 'type': 'number', - 'numericality': 'number', - 'pattern': '^\\d+(\\.\\d{1,2})?$' - }, - 'clan': { - 'type': 'string', - 'required': true, - 'unique': false, - 'absencechar': true - }, - 'country': { - 'type': 'string', - 'notin': ['England'], - 'is': 8 - }, - 'gender': { - 'type': 'string', - 'in': ['Male', 'Female'], - 'required': false - }, - 'shipName': { - 'type': 'string', - 'pattern': '^[A-Za-z0-9-]+$' - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }], bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - starWarsModel = loopback.getModel(starWarsModelName, bootstrap.defaultContext); - starTrekModel = loopback.getModel(starTrekModelName, bootstrap.defaultContext); - }); - }); - - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: starWarsModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - models.ModelDefinition.destroyAll({ - name: starTrekModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - starWarsModel.destroyAll({}, bootstrap.defaultContext, function () { - starTrekModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - - }); - }); - - it('Validation Test - Should insert data successfully', function (done) { - - var data = { - 'name': 'Anakin', - 'numericField1': 10, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi', - 'shipName': 'Delta-7B' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because name is not unique', function (done) { - - var data = { - 'name': 'Vader', - 'clan': 'Sith' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - }); - - it('Validation Test - Should insert data sucessfully as name is unique w.r.t scope', function (done) { - - var data1 = { - 'name': 'Snoke', - 'clan': 'Sith', - 'scope': { - 'ship': 'Nebulon Ranger' - } - }; - var data2 = { - 'name': 'Snoke', - 'clan': 'Sith', - 'scope': { - 'ship': 'Shieldship' - } - }; - starWarsModel.create(data1, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - starWarsModel.create(data2, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - }); - - it('Validation Test - Should fail because pattern for shipName is not proper', function (done) { - - var data = { - 'name': 'Anakin', - 'numericField1': 12, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi', - 'shipName': 'Delta-7B#' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because min length of name is 4', function (done) { - - var data = { - 'name': '3PO', - 'numericField1': 12, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because max length of name is 7', function (done) { - - var data = { - 'name': 'ObiWanKenobi', - 'numericField1': 12, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because presence of clan is mandatory', function (done) { - - var data = { - 'name': 'HanSolo', - 'numericField1': 12, - 'country': 'Tatooine', - 'gender': 'Male' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because clan can have only alphabets as absencechar validation is applicable for it', function (done) { - - var data = { - 'name': 'HanSolo', - 'numericField1': 12, - 'country': 'Tatooine', - 'gender': 'Male', - 'clan': 'Jedi1' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because gender value is out of list', function (done) { - - var data = { - 'name': 'Doku', - 'numericField1': 10, - 'gender': 'Man', - 'country': 'Tatooine', - 'clan': 'Sith' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because country value is from the exclusion list', function (done) { - - var data = { - 'name': 'Leia', - 'numericField1': 10, - 'gender': 'Female', - 'country': 'England', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because length of country is not 8', function (done) { - - var data = { - 'name': 'Luke', - 'numericField1': 10, - 'gender': 'Male', - 'country': 'Australia', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because numericField2 should be absent', function (done) { - - var data = { - 'name': 'Amidala', - 'numericField1': 10, - 'numericField2': 12, - 'gender': 'Female', - 'country': 'Tatooine', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because numericField1 should be integer and input data is decimal number', function (done) { - - var data = { - 'name': 'YODA', - 'numericField1': 10.5, - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should fail because numericField1 should be integer and input data is NaN', function (done) { - - var data = { - 'name': 'YODA', - 'numericField1': "10a", - 'gender': 'Male', - 'country': 'Tatooine', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because numericField3 pattern rule is violated', function (done) { - - var data = { - 'numericField3': 12.345, - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail because numericField3 should be number', function (done) { - - var data = { - 'numericField3': '12a', - 'clan': 'Jedi' - }; - starWarsModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should Fail to insert data by POST', function (done) { - var URL = url + '/StarWars' + '?access_token=' + testUsertoken; - - var postData = { - 'clan': 'Jedi123' - }; - - api.post(URL) - .set('Accept', 'application/json') - .send(postData) - .expect(422).end(function (err, resp) { - if (err) { - throw new Error(err); - } else { - var error = JSON.parse(resp.text).error; - expect(error).not.to.be.null; - done(); - } - }); - - }); - - it('Validation Test Unique Case insensitive- Should insert data successfully', function (done) { - - var data = { - 'name': 'Spock' - }; - starTrekModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test Unique Case insensitive- Should fail with duplicate case insensitive data ', function (done) { - - var data1 = { - 'name': 'spock' - }; - var data2 = { - 'name': 'SPOCK' - }; - var data3 = { - 'name': 'spOck' - }; - starTrekModel.create(data1, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - starTrekModel.create(data2, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - starTrekModel.create(data3, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - }); - }); - -}); diff --git a/test/model-validation-validateWhen.js b/test/model-validation-validateWhen.js deleted file mode 100644 index 30b6784..0000000 --- a/test/model-validation-validateWhen.js +++ /dev/null @@ -1,435 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -//var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var parentModelName = 'CustomerModel'; -var childModelName = 'AccountModel'; -var addressModelName = 'Address'; -describe(chalk.blue('Validation validateWhen test'), function () { - - this.timeout(50000); - var parentModel; - var childModel; - var addressModel; - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + childModelName + '-available', function () { - return; - - }); - - models.ModelDefinition.create({ - 'name': 'Address', - 'base': 'BaseEntity', - 'plural': 'addresses', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'state': { - 'type': 'string', - 'required': true - }, - 'zipcode': { - 'type': 'string', - 'required': true, - 'min': 6, - 'validateWhen': { - 'required': '@i.state == "WASHINGTON"' - } - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, result) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': parentModelName, - 'base': 'BaseEntity', - 'plural': parentModelName + 's', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'id': { - 'type': 'number', - 'required': true, - 'id': true - }, - 'name': { - 'type': 'string', - 'required': true - }, - 'dob': { - 'type': 'date', - 'required': true - }, - 'age': { - 'type': 'number', - 'required': true - }, - 'cityType': { - 'required': 'true', - 'type': 'string', - 'in': ['Urban', 'Semi-Urban', 'Rural'] - }, - 'accNo': { - 'type': 'number', - 'required': true - }, - 'state': { - 'type': 'string' - } - }, - 'validations': [], - 'oeValidations': { - 'adddressCheck': { - 'validateWhen': '@i.state !== undefined && @i.state !== null && @i.state !== ""', - 'type': 'reference', - 'errorCode': 'addr-err-001', - 'refModel': 'Address', - 'refWhere': '{"state":"{{state}}"}' - } - }, - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': childModelName, - 'base': 'BaseEntity', - 'plural': childModelName + 's', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'accountNumber': { - 'type': 'number', - 'required': true - }, - 'uniqueIdentificationNo': { - 'type': 'string', - 'required': true, - 'validateWhen': { - 'required': 'Date(@mCustomerModel.dob where accNo = @i.accountNumber).year > 1988' - } - }, - 'accountType': { - 'type': 'string', - 'required': 'true', - 'in': ['privilage', 'non-privilage'], - 'validateWhen': { - 'in': '(@mCustomerModel.cityType where accNo=@i.accountNumber) inArray ["Urban","Semi-Urban"]' - } - }, - 'balance': { - 'type': 'number', - 'required': true, - 'min': 5000, - 'max': 10000, - 'validateWhen': { - 'min': '(@mCustomerModel.age where accNo = @i.accountNumber) < 65' - } - } - }, - 'validations': [], - 'relations': { - 'cust': { - 'type': 'belongsTo', - 'model': 'CustomerModel', - 'foreignKey': 'custId' - } - }, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - parentModel = loopback.getModel(parentModelName, bootstrap.defaultContext); - childModel = loopback.getModel(childModelName, bootstrap.defaultContext) - addressModel = loopback.getModel(addressModelName, bootstrap.defaultContext) - expect(err).to.be.not.ok; - var cust1 = { - 'id': 101, - 'name': 'Mike', - 'dob': '1936-06-10', - 'cityType': 'Urban', - 'age': 80, - 'accNo': 1001 - }; - var cust2 = { - 'id': 102, - 'name': 'John', - 'dob': '1998-05-05', - 'cityType': 'Urban', - 'age': 18, - 'accNo': 1002 - }; - var cust3 = { - 'id': 103, - 'name': 'Jack', - 'dob': '1951-02-01', - 'cityType': 'Semi-Urban', - 'age': 65, - 'accNo': 1003 - }; - var cust4 = { - 'id': 104, - 'name': 'Jill', - 'dob': '1961-03-03', - 'cityType': 'Rural', - 'age': 55, - 'accNo': 1004 - }; - parentModel.create(cust1, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - parentModel.create(cust2, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - parentModel.create(cust3, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - parentModel.create(cust4, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - }); - }); - }); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: parentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for parent Model Hotel'); - return done(); - } - parentModel.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: childModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for child Model Room'); - return done(); - } - childModel.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: addressModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for Address Model'); - return done(); - } - addressModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - }); - }); - - it('Validation Test - Should insert data successfully despite balance set to 100', function (done) { - - var data = { - 'accountNumber': 1001, - 'balance': 100, - 'accountType': 'privilage', - 'custId': 101 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data successfully', function (done) { - - var data = { - 'accountNumber': 1002, - 'accountType': 'non-privilage', - 'uniqueIdentificationNo': 'UAN5123', - 'balance': 100, - 'custId': 102 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should insert data successfully (t198)', function (done) { - - var data = { - 'accountNumber': 1002, - 'accountType': 'non-privilage', - 'uniqueIdentificationNo': 'UAN5123', - 'balance': 5000, - 'custId': 102 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data successfully - The customer needs to give identification as the dob is later than 1988', function (done) { - - var data = { - 'accountNumber': 1002, - 'accountType': 'non-privilage', - 'balance': 5000, - 'custId': 102 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should insert data successfully for urban customer as the accountType is privilage', function (done) { - - var data = { - 'accountNumber': 1003, - 'accountType': 'privilage', - 'balance': 5000, - 'custId': 103 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data for urban customer as the accountType is not privilage or non-privilage', function (done) { - - var data = { - 'accountNumber': 1003, - 'accountType': 'regular', - 'balance': 5000, - 'custId': 103 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - - it('Validation Test - Should insert data for rural customer as the accountType need not be privilage or non-privilage', function (done) { - - var data = { - 'accountNumber': 1004, - 'accountType': 'regular', - 'balance': 5000, - 'custId': 104 - }; - childModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully as validateWhen condition is false and the reference validation is not applied', function (done) { //sambit - - var cust = { - 'id': 105, - 'name': 'George', - 'dob': '1981-12-25', - 'cityType': 'Urban', - 'age': 35, - 'accNo': 2001 - }; - - parentModel.create(cust, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - - }); - - it('Validation Test - Should fail to insert data as validateWhen condition is true and the reference validation is applied', function (done) { //sambit - - var cust = { - 'id': 106, - 'name': 'Bill', - 'dob': '1973-11-03', - 'cityType': 'Urban', - 'age': 43, - 'accNo': 2002, - 'state': 'CALIFORNIA' - }; - - parentModel.create(cust, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.null; - done(); - }); - }); - - it('Validation Test - Should insert data successfully', function (done) { - - var data = { - 'state': 'WASHINGTON', - 'zipcode': '123456' - }; - addressModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Validation Test - Should fail to insert data successfully', function (done) { - - var data = { - 'state': 'WASHINGTON' - }; - addressModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).not.to.be.undefined; - done(); - }); - }); - it('Validation Test - Should insert data successfully', function (done) { - - var data = { - 'state': 'CALIFORNIA' - }; - addressModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - -}); diff --git a/test/model-validation-xmodelvalidate-test.js b/test/model-validation-xmodelvalidate-test.js deleted file mode 100644 index 3443de0..0000000 --- a/test/model-validation-xmodelvalidate-test.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** -* This test is for the xmodelvalidate cross-model reference check validation. -* The current test creates two models - Invitee and Guest. -* The premise is that without any relations between these models, -* one should be able to declare a reference from Guest to Invitee. -* This is done by declaring the Guest.guestName to be "xmodelvalidate"d -* against Invitee.inviteeName. A few Invitees are added and a couple of -* Guests are created, one is in the Invitee list and one is not. -* The xmodelvalidate is expected to catch the uninvited Guest :) -* -* @author Ajith Vasudevan -* -*/ - - -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - - -describe(chalk.blue('X-Model-Validation test'), function () { - this.timeout(20000); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + 'Guest' + '-available', function () { - var referredModel = loopback.getModel('Invitee', bootstrap.defaultContext); - - // Invitees - var data = [{ 'inviteeName': 'Ajith' }, - { 'inviteeName': 'Rama' }, - { 'inviteeName': 'Anirudh' }]; - - referredModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - // Create the Invitee Model - models.ModelDefinition.create({ - 'name': 'Invitee', - 'base': 'BaseEntity', - 'properties': { - 'inviteeName': 'string' - } - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - - // Create the Guest model if Invitee creation is successful - models.ModelDefinition.create({ - 'name': 'Guest', - 'base': 'BaseEntity', - 'properties': { - 'guestName': { - 'type': 'string', - 'xmodelvalidate': { 'model': 'Invitee', 'field': 'inviteeName' } - } - } - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - }); - } - expect(err).to.be.not.ok; - }); - }); - - it('X-Model-Validation Test - Should succeed', function (done) { - - var mainModel = loopback.getModel('Guest', bootstrap.defaultContext); - var data = [{ 'guestName': 'Ajith' }, { 'guestName': 'Mohan' }]; - mainModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err[0]).to.be.undefined; - expect(err[1]).not.to.be.undefined; - done(); - }); - - }); - - -}); diff --git a/test/model-variant-of-test.js b/test/model-variant-of-test.js deleted file mode 100644 index 62d8ac3..0000000 --- a/test/model-variant-of-test.js +++ /dev/null @@ -1,276 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -function GenerateModelName(model) { - return model + Math.floor(Math.random() * (999)); -} - -describe(chalk.blue('model-variant-of'), function () { - this.timeout(20000); - var accessTokens = {}; - - var tenantId = GenerateModelName('tenant'); - var productModelName = GenerateModelName('Product'); - - var user1 = { - 'username': 'foo', - 'password': 'password++', - 'email': 'foo@gmail.com', - 'tenantId': tenantId - }; - - it('login as admin', function (done) { - var postData = { - 'username': 'admin', - 'password': 'admin' - }; - var postUrl = baseUrl + '/BaseUsers/login'; - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - accessTokens.admin = response.body.id; - done(); - }); - }); - - it('Create Model', function (done) { - var modelDefinitionData = { - 'name': productModelName, - 'plural': productModelName, - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'properties': { - 'name': { - 'type': 'string', - 'unique': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/ModelDefinitions?access_token=' + accessTokens.admin; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(modelDefinitionData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - done(); - } - }); - }); - - it('Create Tenant', function (done) { - - var tenantData = {}; - tenantData.tenantId = tenantId; - tenantData.tenantName = tenantData.tenantId; - - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/Tenants?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(tenantData) - .expect(200) - .end(function (err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('switch tenant', function (done) { - var data = { - tenantId: tenantId - }; - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers/switch-tenant?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(data) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.undefined; - expect(result.body.tenantId).to.be.equal(tenantId); - done(); - } - }); - }); - - it('Create User in tenant1', function (done) { - - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(user1) - .expect(200) - .end(function (err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('login as user1 in tenant1', function (done) { - var postData = { - 'username': user1.username, - 'password': user1.password - }; - var postUrl = baseUrl + '/BaseUsers/login'; - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .set('tenant_id', tenantId) - .send(postData) - .expect(200).end(function (err, response) { - expect(response.body).not.to.be.undefined; - expect(response.body.id).not.to.be.undefined; - accessTokens.user1 = response.body.id; - done(); - }); - }); - - - it('Create Variant Model', function (done) { - var variantModel = productModelName + 'variant'; - var modelDefinitionData = { - 'name': variantModel, - 'base': productModelName, - 'variantOf': productModelName, - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'properties': { - 'namevar': { - 'type': 'string' - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/ModelDefinitions?access_token=' + accessTokens.user1; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(modelDefinitionData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - done(); - } - }); - }); - - it('should return the generated gridmetadata', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/GridMetaData/' + productModelName + '/render' + '?access_token=' + accessTokens.user1; - - api - .get(url) - .expect(200).end(function (err, res) { - var response = res.body; - //console.log('gridmeta', response); - expect(response).to.exist; - expect(response.columnData).to.exist; - expect(response.dialogMetaData).to.exist; - done(); - }); - - }); - - it('should return the generated uimodel ', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/UIMetaData/' + productModelName + '/render' + '?access_token=' + accessTokens.user1; - - api - .get(url) - .expect(200).end(function (err, res) { - //var response = res.body; - //console.log('uimetadata ', response); - done(); - }); - - }); - - it('Post Data', function (done) { - var postData = { - 'name': 'data1' - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/' + productModelName + '?access_token=' + accessTokens.user1; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - var callContext = { ctx: {} }; - callContext.ctx.tenantId = tenantId; - var model = bootstrap.app.loopback.findModel(productModelName, callContext); - //var model = bootstrap.models[productModelName]; - model.find({}, callContext, function (err, list) { - expect(list[0]._autoScope.tenantId).to.be.equal(tenantId); - done(); - }); - } - }); - }); - -}); diff --git a/test/multi-tenancy-test.js b/test/multi-tenancy-test.js deleted file mode 100644 index 8b8a36a..0000000 --- a/test/multi-tenancy-test.js +++ /dev/null @@ -1,449 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -// to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var dbName = process.env.DB_NAME || 'db'; - -// function GenerateModelName(model) { -// return model + Math.floor(Math.random() * (999)); -// } - -describe(chalk.blue('multi-tenancy-test'), function () { - var accessTokens = {}; - this.timeout(60000); - var productModelName = 'MyProducts'; - - var tenants = [{ - tenantId: 'tenant1', - tenantName: 'tenant1' - }, { - tenantId: 'tenant2', - tenantName: 'tenant2' - }]; -var dsname = 'db'; - var datasources = [{ - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/' + dbName + '1', - 'database': dbName + '1', - 'password': 'admin', - 'name': 'db1', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - }, { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/' + dbName + '2', - 'database': dbName + '2', - 'password': 'admin', - 'name': 'db2', - 'connector': 'mongodb', - 'user': 'admin', - 'connectionTimeout': 50000 - }]; - - var user1 = { - 'username': 'user1', - 'password': 'password++', - 'email': 'user1@gmail.com' - }; - - var user2 = { - 'username': 'user2', - 'password': 'password++', - 'email': 'user2@gmail.com' - }; - - var mappings1 = [{ - modelName: productModelName, - dataSourceName: datasources[0].name - }, { - modelName: 'BaseUser', - dataSourceName: datasources[0].name - }]; - - var mappings2 = [{ - modelName: productModelName, - dataSourceName: datasources[1].name - }, { - modelName: 'BaseUser', - dataSourceName: datasources[1].name - }]; - - function cleandb(done) { - bootstrap.models.Tenant.destroyAll({}, bootstrap.defaultContext, function (err, res) { - bootstrap.models.DataSourceDefinition.destroyAll({}, bootstrap.defaultContext, function (err, res) { - bootstrap.models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function (err, res) { - bootstrap.models.DataSourceMapping.destroyAll({}, bootstrap.defaultContext, function (err, res) { - done(); - }); - }); - }); - }); - } - - var dataSource = app.datasources[dsname]; - if (dataSource.name !== 'mongodb') { - return; - } - - before('setup', function (done) { - cleandb(done); - }); - - // after('cleanup', function(done) { - // cleandb(done); - // }); - - it('login as admin', function (done) { - var postData = { - 'username': 'admin', - 'password': 'admin' - }; - var postUrl = baseUrl + '/BaseUsers/login'; - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - accessTokens.admin = response.body.id; - done(); - }); - }); - - it('Create Tenants', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/Tenants?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(tenants) - .expect(200) - .end(function (err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Create Common Product Model for Both Tenants', function (done) { - var modelDefinitionData = { - 'name': productModelName, - 'plural': productModelName, - 'base': 'BaseEntity', - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'properties': { - 'name': { - 'type': 'string', - 'unique': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/ModelDefinitions?access_token=' + accessTokens.admin; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(modelDefinitionData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - done(); - } - }); - }); - - it('Create DataSources', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/DataSourceDefinitions?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(datasources) - .end(function (err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('switch tenant 1', function (done) { - var data = { - tenantId: tenants[0].tenantId - }; - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers/switch-tenant?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(data) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.undefined; - expect(result.body.tenantId).to.be.equal(tenants[0].tenantId); - done(); - } - }); - }); - - it('Create DataSource Mappings for tenant1', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/DataSourceMappings?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(mappings1) - .end(function (err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Create User1 in tenant1', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(user1) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('login as user1 in tenant1', function (done) { - var postData = { - 'username': user1.username, - 'password': user1.password - }; - var postUrl = baseUrl + '/BaseUsers/login'; - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .set('tenant_id', tenants[0].tenantId) - .send(postData) - .expect(200).end(function (err, response) { - expect(response.body).not.to.be.undefined; - expect(response.body.id).not.to.be.undefined; - accessTokens.user1 = response.body.id; - done(); - }); - }); - - xit('Post Data to Product in tenant1 ', function (done) { - var postData = { - 'name': 'data1' - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/' + productModelName + '?access_token=' + accessTokens.user1; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - var callContext = { - ctx: {} - }; - callContext.ctx.tenantId = tenants[0].tenantId; - var model = bootstrap.models[productModelName]; - model.find(function (err, list) { - expect(list[0]._autoScope.tenantId).to.be.equal(tenants[0].tenantId); - done(); - }); - } - }); - }); - - it('switch tenant to tenant2', function (done) { - var data = { - tenantId: tenants[1].tenantId - }; - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers/switch-tenant?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(data) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).not.to.be.undefined; - expect(result.body.tenantId).to.be.equal(tenants[1].tenantId); - done(); - } - }); - }); - - it('Create DataSource Mappings for tenant2', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/DataSourceMappings?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(mappings2) - .end(function (err, response) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Create User2 in tenant2', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = baseUrl + '/BaseUsers?access_token=' + accessTokens.admin; - api.set('Accept', 'application/json') - .post(postUrl) - .send(user2) - .expect(200) - .end(function (err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('login as user2 in tenant2', function (done) { - var postData = { - 'username': user2.username, - 'password': user2.password - }; - var postUrl = baseUrl + '/BaseUsers/login'; - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .set('tenant_id', tenants[1].tenantId) - .send(postData) - .expect(200).end(function (err, response) { - expect(response.body).not.to.be.undefined; - expect(response.body.id).not.to.be.undefined; - accessTokens.user2 = response.body.id; - done(); - }); - }); - - it('Create Variant Model', function (done) { - var variantModel = productModelName + 'variant'; - var modelDefinitionData = { - 'name': variantModel, - 'base': productModelName, - 'variantOf': productModelName, - 'strict': false, - 'idInjection': true, - 'validateUpsert': true, - 'properties': { - 'Tenant2Field': { - 'type': 'string', - 'default': 'default value' - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/ModelDefinitions?access_token=' + accessTokens.user2; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(modelDefinitionData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - done(); - } - }); - }); - - xit('Post Data to Product in tenant2', function (done) { - var postData = { - 'name': 'data2' - }; - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/' + productModelName + '?access_token=' + accessTokens.user2; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - if (response.statusCode !== 200) { - console.log(response.body); - } - expect(response.statusCode).to.be.equal(200); - var callContext = { - ctx: {} - }; - callContext.ctx.tenantId = tenants[1].tenantId; - var model = bootstrap.models[productModelName]; - model.find(function (err, list) { - expect(list[0]._autoScope.tenantId).to.be.equal(tenants[1].tenantId); - expect(list[0].Tenant2Field).to.be.equal('default value'); - done(); - }); - } - }); - }); -}); \ No newline at end of file diff --git a/test/oe-studio-test.js b/test/oe-studio-test.js deleted file mode 100644 index 6039526..0000000 --- a/test/oe-studio-test.js +++ /dev/null @@ -1,380 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -var appconfig = require('../server/config'); - -var options; -var accessToken; -describe(chalk.blue('oe-studio-test'), function () { - this.timeout(10000); - - before('prepare test data', function (done) { - options = {}; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - bootstrap.login(function (token) { - accessToken = token; - done(); - }); - }); - - xit('redirects to login page when not logged in', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath; - api.get(getUrl) - .expect(302) - .end(function (err, result) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - - it('returns designer index page', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath; - api.set('Authorization', accessToken) - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Redirects to designer.mountPath when designer.html is requested', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = '/designer.html'; - api.set('Authorization', accessToken) - .get(getUrl) - .expect(302) - .end(function (err, result) { - - if (err) { - done(err); - } else { - expect(result.header.location).to.exist; - expect(result.header.location).to.equal(appconfig.designer.mountPath); - - done(); - } - }); - }); - - it('returns API endpoints for model', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/routes/Literals'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.status).to.equal(200); - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.path && item.path.indexOf('/Literals')===0); - }); - done(); - } - }); - }); - - - it('returns designer config', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/config'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('object'); - expect(result.body.mountPath).to.equal(appconfig.designer.mountPath); - done(); - } - }); - }); - - it('returns template data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/templates'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.file && item.path && item.content && item.type); - }); - done(); - } - }); - }); - - it('returns style data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/styles'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.file && item.path); - }); - done(); - } - }); - }); - - - it('returns assets data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/assets'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('object'); - expect(result.body.images).to.be.ok; - expect(result.body.videos).to.be.ok; - expect(result.body.audios).to.be.ok; - done(); - } - }); - }); - - it('returns images data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/assets/images'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.file && item.path && item.size); - }); - done(); - } - }); - }); - - it('returns video data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/assets/videos'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.file && item.path && item.size); - }); - done(); - } - }); - }); - - it('returns audio data', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/assets/audios'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.file && item.path && item.size); - }); - done(); - } - }); - }); - - it('returns elements', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/elements'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body).to.be.an('array'); - expect(result.body).all.to.satisfy(function(item){ - return (item.name && item.tag && item.category && item.config && item.config.importUrl); - }); - done(); - } - }); - }); - - it('save-file saves the file', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = appconfig.designer.mountPath + '/save-file'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({file:"client/bower_components/test.html", data: "my-file-dummy-content"}) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.status).to.be.true; - done(); - } - }); - }); - - it('returns properties', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = appconfig.designer.mountPath + '/properties/Literals'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - console.log(result.body); - expect(result.body).to.exist; - expect(result.body.key).not.to.be.undefined; - expect(result.body.key.type).to.be.equal('String'); - done(); - } - }); - }); - - it('error if model not present for default UI', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = appconfig.designer.mountPath + '/createDefaultUI'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({ modelName: "TestUIModel" }) - .expect(500) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.error.message).to.be.equal('Model not found'); - done(); - } - }); - }); - - it('create default UI', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = appconfig.designer.mountPath + '/createDefaultUI'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({ modelName: "ModelDefinition" }) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.message).to.be.equal('Default UI created'); - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({ modelName: "ModelDefinition" }) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.message).to.be.equal('Default UI already exists'); - done(); - } - }); - } - }); - }); - -}); diff --git a/server/oracle-utility.js b/test/oracle-utility.js similarity index 54% rename from server/oracle-utility.js rename to test/oracle-utility.js index 97ed737..7909c9e 100644 --- a/server/oracle-utility.js +++ b/test/oracle-utility.js @@ -30,6 +30,7 @@ var oracleConnectSettings = { }; var userName = process.env.CI_PROJECT_NAMESPACE.toUpperCase() + '-' + (process.env.CI_PROJECT_NAME || 'oecloud').toUpperCase(); +var userName2 = process.env.CI_PROJECT_NAMESPACE.toUpperCase() + '-' + (process.env.CI_PROJECT_NAME || 'oecloud').toUpperCase() + '-NEWDB'; var password = process.env.CI_PROJECT_NAMESPACE.toLowerCase(); var grants = [ @@ -43,6 +44,37 @@ var grants = [ 'CREATE PROCEDURE' ]; +function createUser2(connection, cb) { + var sql = 'alter session set "_ORACLE_SCRIPT"=true'; + connection.execute(sql, function (e, r) { + if (e) { + console.error('Ignoring error of alter session. UserName : ' + userName2 + ' Error :' + e); + } + console.log(sql, ' ......... ok'); + var sql = 'CREATE USER "' + userName2 + '" IDENTIFIED BY ' + password; + + connection.execute(sql, function (err, result) { + if (err) { + throw new Error('Unable to create user ' + userName2 + ' Error :' + err); + } + console.log(sql, ' ......... ok'); + async.each(grants, function (g, callback) { + var sql = 'GRANT ' + g + ' to "' + userName2 + '"'; + + connection.execute(sql, function (err2, result2) { + if (err2) { + throw new Error('Unable to execute grant ' + sql); + } + console.log(sql, ' ......... ok'); + return callback(); + }); + }, function (err) { + console.log('User ' + userName2 + ' Created successfully'); + return cb(); + }); + }); + }); +} function createUser(connection, cb) { var sql = 'alter session set "_ORACLE_SCRIPT"=true'; connection.execute(sql, function (e, r) { @@ -74,7 +106,49 @@ function createUser(connection, cb) { }); }); } +function dropTables2(cb) { + var oracleConnectSettings2 = Object.assign({}, oracleConnectSettings); + oracleConnectSettings2.user = userName2; + oracleConnectSettings2.password = password; + oracledb.getConnection( + oracleConnectSettings2, + function (err, connection) { + if (err) { + console.log(err); + throw new Error(err + ' Unable to connect to Oracle Database ' + JSON.stringify(oracleConnectSettings2)); + } + var sql = "select 'drop table \"' || table_name || '\"' from all_tables where owner = '" + userName2 + "'"; + var totalRows = 1000; + + connection.execute(sql, {}, { maxRows: totalRows }, function (err, result) { + if (err) { + throw new Error('Unable to find tables ' + userName2 + ' Error :' + err); + } + connection.execute(sql, {}, { maxRows: totalRows }, function (err2, result2) { + if (err2) { + throw new Error('Unable to execute droping of table ' + sql); + } + if (!result2 || !result2.rows || result2.rows.length === 0) { + return cb(); + } + async.each(result2.rows, function (row, callback) { + var sql = row[0]; + connection.execute(sql, function (err2, result2) { + if (err2) { + throw new Error('Unable to drop table\nERROR : ' + err2 + '\nSQL : ' + sql); + } + console.log(sql, ' ......... ok'); + return callback(); + }); + }, function (err) { + console.log('Tables of user ' + userName2 + ' dropped successfully'); + return cb(); + }); + }); + }); + }); +} function dropTables(cb) { var oracleConnectSettings2 = Object.assign({}, oracleConnectSettings); @@ -119,6 +193,40 @@ function dropTables(cb) { }); } + +function createAnotherSchema(){ + oracledb.getConnection( + oracleConnectSettings, + function (err, connection) { + if (err) { + throw new Error('Unable to connect to Oracle Database ' + JSON.stringify(oracleConnectSettings)); + } + var sql = "select username, user_id from dba_users where username = '" + userName2 + "'"; + console.log(sql); + connection.execute(sql, + function (err, result) { + if (err) { + console.error(err); return; + } + if (!result.rows || result.rows.length == 0) { + createUser2(connection, function (err) { + if (err) { + return process.exit(1); + } + return process.exit(); + }); + } else { + dropTables2(function (err) { + if (err) { + return process.exit(1); + } + return process.exit(); + }); + } + }); + }); +} + oracledb.getConnection( oracleConnectSettings, function (err, connection) { @@ -137,15 +245,16 @@ oracledb.getConnection( if (err) { return process.exit(1); } - return process.exit(); + return createAnotherSchema(); //process.exit(); }); } else { dropTables(function (err) { if (err) { return process.exit(1); } - return process.exit(); + return createAnotherSchema(); //return process.exit(); }); } }); }); + diff --git a/test/otp-test.js b/test/otp-test.js deleted file mode 100644 index 4066a4f..0000000 --- a/test/otp-test.js +++ /dev/null @@ -1,232 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; -// var uuidv4 = require('uuid/v4'); - -describe(chalk.blue('OTP tests'), function () { - - var modelName = 'BaseOTP'; - var url = baseUrl + '/' + modelName; - - var data = { - email: 'abc@abc.com', - phone: '1234567890' - }; - - var otpId; - var defaultContext = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - } - }; - - function getOTPInstance(id, options, cb) { - var baseOTP = bootstrap.app.models.BaseOTP; - baseOTP.findOne({ 'id': id }, options, cb); - } - - it('Send OTP - email and phone', function (done) { - var postData = { - 'email': data.email, - 'phone': data.phone - }; - var postUrl = url + '/send'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200) - .end(function (err, response) { - if (err) { - done(err); - } else { - otpId = response.body.otpId; - done(); - } - }); - }); - - it('Verify OTP - invalid', function (done) { - var postData = { - 'otpId': otpId, - 'otp': 0 - }; - var postUrl = url + '/verify'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - expect(response.error.message).to.be.equal.toString('Verification failed'); - done(); - } - }); - }); - - it('Resend OTP - check maximum retries 1', function (done) { - var postData = { - 'otpId': otpId - }; - var postUrl = url + '/resend'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - if (err) { - done(err); - } else { - var status = response.body; - done(); - } - }); - }); - - it('Resend OTP - check maximum retries 2', function (done) { - var postData = { - 'otpId': otpId - }; - var postUrl = url + '/resend'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - if (err) { - done(err); - } else { - var status = response.body; - done(); - } - }); - }); - - it('Resend OTP - check maximum retries 3', function (done) { - var postData = { - 'otpId': otpId - }; - var postUrl = url + '/resend'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - if (err) { - done(err); - } else { - var status = response.body; - done(); - } - }); - }); - - it('Resend OTP - exceeded maximum retries', function (done) { - var postData = { - 'otpId': otpId - }; - var postUrl = url + '/resend'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - expect(response.body.error.message).to.be.equal('Exceeded maximum resend'); - done(); - } - }); - }); - - it('Verify OTP - valid', function (done) { - var postData = { - 'otpId': otpId, - 'otp': 1234 - }; - var postUrl = url + '/verify'; - - var api = defaults(supertest(bootstrap.app)); - getOTPInstance(otpId, defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.otp = res.otp; - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - if (err) { - done(err); - } else { - var status = response.body.status; - if (status === 'verified') { - done(); - } else { - done(new Error('Unable to verify')); - } - } - }); - } - }); - }); - - it('Verify OTP - retry verified otp', function (done) { - var postData = { - 'otpId': otpId, - 'otp': 1234 - }; - var postUrl = url + '/verify'; - - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .end(function (err, response) { - if (err) { - done(err); - } else { - expect(response.body.error.message).to.be.equal('No record found'); - done(); - } - }); - }); - - after('after clean up', function (done) { - done(); - }); - -}); diff --git a/test/pg-test/basic-crud-test.js b/test/pg-test/basic-crud-test.js deleted file mode 100644 index ef27fb8..0000000 --- a/test/pg-test/basic-crud-test.js +++ /dev/null @@ -1,120 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('../bootstrap'); -var app = bootstrap.app; -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var chai = require('chai'); -chai.use(require('chai-things')); - -var modelName = 'Customer'; - -describe(chalk.blue('Basic Crud for Postgresql connector'), function () { - - before('setup test data', function (done) { - - var dataSourceConfig = { - 'connector': require('loopback-connector-evpostgresql'), - 'host': 'localhost', - 'port': 5432, - 'url': 'postgres://postgres:postgres@localhost:5432/db2', - 'database': 'db2', - 'password': 'postgres', - 'name': 'db2', - 'user': 'postgres', - 'connectionTimeout': 50000 - }; - - loopback.createModel({ - 'name': modelName, - 'base': 'PersistedModel', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string' - }, - 'age': { - 'type': 'number' - }, - 'phone': { - 'type': 'string' - } - } - }); - var model = loopback.findModel(modelName); - app.dataSource('db2', dataSourceConfig); - model.attachTo(app.dataSources['db2']); - model.app = app; - done(); - }); - - after('destroy test models', function (done) { - var model = loopback.findModel(modelName); - model.destroyAll({}, bootstrap.defaultContext, done); - }); - - - it('Creation of data should be successful', function (done) { - var model = loopback.findModel(modelName); - var data = { - 'name': 'Mike', - 'age': 40, - 'phone': '12345' - }; - model.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Read data from postgres table successfully', function (done) { - var model = loopback.findModel(modelName); - model.find({}, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.undefined; - model.findById(results[0].id, bootstrap.defaultContext, function (err, result) { - expect(err).to.be.undefined; - done(); - }); - }); - }); - - it('Update data successfully', function (done) { - var model = loopback.findModel(modelName); - model.find({}, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.undefined; - var data = results[0]; - data.name = 'Michael'; - model.upsert(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - }); - - it('Delete data successfully', function (done) { - var model = loopback.findModel(modelName); - var data = { - 'name': 'George', - 'age': 45, - 'phone': '54321' - }; - model.create(data, bootstrap.defaultContext, function (err, result) { - expect(err).to.be.null; - model.deleteById(result.id, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - }); - -}); \ No newline at end of file diff --git a/test/property-expressions-test.js b/test/property-expressions-test.js deleted file mode 100644 index 0af2f5c..0000000 --- a/test/property-expressions-test.js +++ /dev/null @@ -1,271 +0,0 @@ -/** -* - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), -* Bangalore, India. All Rights Reserved. -* - */ - -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -//var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var studentModelName = 'Student'; -var departmentModelName = 'Department'; - - -describe(chalk.blue('Property expression test'), function () { - - var studentModel; - var departmentModel; - this.timeout(20000); - - before('setup test data', function (done) { - models.ModelDefinition.events.once('model-' + studentModelName + '-available', function () { - var baseUserModel = loopback.getModel("BaseUser"); - var baseUserData = { - "username": "test-user", - "email": "test-user@mycompany.com", - "emailVerified": true, - "id": "test-user", - "password": "default-password", - "_autoScope": { - "tenantId": "test-tenant" - } - }; - baseUserModel.create(baseUserData, bootstrap.defaultContext, function (err, results) { - expect(err).to.not.be.ok; - }); - }); - - models.ModelDefinition.create( - { - 'name': 'Department', - 'base': 'BaseEntity', - 'plural': 'departments', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'departmentName': { - 'type': 'string', - 'required': true - }, - 'departmentHead': { - 'type': 'string' - }, - 'departmentId': { - 'type': 'number', - 'required': true, - 'id': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } else { - models.ModelDefinition.create({ - 'name': 'Student', - 'base': 'BaseEntity', - 'plural': 'students', - 'strict': false, - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'name': { - 'type': 'string', - 'propExpression': '@mDepartment/{{departmentId:@i.departmentId}}.departmentHead' - }, - 'departmentId': { - 'type': 'number', - 'propExpression': '@mDepartment/{{departmentName:Electronics}}.departmentId' - }, - 'departmentName': { - 'type': 'string', - 'propExpression': '@mDepartment/{{@i.departmentId}}.departmentName' - }, - 'rollNumber': { - 'type': 'string' - }, - 'emailAddress': { - 'type': 'string', - 'propExpression': '@mBaseUser/{{username:callContext.ctx.remoteUser}}.email' - }, - 'studentId': { - 'type': 'number', - 'required': true, - 'id': true - } - }, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {} - }, bootstrap.defaultContext, function (err, model) { - if (err) { - console.log(err); - } - expect(err).to.be.not.ok; - studentModel = loopback.getModel(studentModelName, bootstrap.defaultContext); - var studentData = [{ - 'name': 'David', - 'departmentId': 1, - 'departmentName': 'Instru', - 'rollNumber': '1', - 'studentId': 1 - }, - { - 'name': 'Jane', - 'departmentId': 2, - 'departmentName': 'Computer Science', - 'rollNumber': '2', - 'studentId': 2 - }]; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(err).to.not.be.ok; - }); - departmentModel = loopback.getModel(departmentModelName, bootstrap.defaultContext); - var deptData = [{ - 'departmentName': 'Computer Science', - 'departmentHead': 'David', - 'departmentId': 1 - }, - { - 'departmentName': 'Electronics', - 'departmentHead': 'Rahul', - 'departmentId': 2 - }]; - departmentModel.create(deptData, bootstrap.defaultContext, function (err, results) { - console.info("deptData done", JSON.stringify(err)); - expect(err).to.be.null; - done(); - }); - }); - } - expect(err).to.be.not.ok; - }); - - }); - - after('destroy test models', function (done) { - models.ModelDefinition.destroyAll({ - name: studentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for studentModelName'); - return done(); - } - studentModel.destroyAll({}, bootstrap.defaultContext, function () { - models.ModelDefinition.destroyAll({ - name: departmentModelName - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for departmentModelName'); - return done(); - } - departmentModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - }); - }); - - - it('Property Expression Test - Should insert data successfully', function (done) { - var data = { - 'departmentName': 'Mechanical Engineering', - 'departmentHead': 'Austin', - 'departmentId': 3 - }; - departmentModel.create(data, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - }); - var studentData = { - 'name': 'David', - 'departmentId': 2, - 'departmentName': 'Test Dept', - 'rollNumber': '3', - 'studentId': 3 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(err).to.be.null; - done(); - }); - }); - - it('Property Expression Test - Should insert data in database if instance property expression is provided and field is blank or not provided', function (done) { - var studentData = { - 'name': 'Rahul', - 'departmentId': 2, - 'rollNumber': '4', - 'studentId': 4 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(results.departmentName).to.have.length.above(0); - done(); - }); - }); - - it('Property Expression Test - Should not update data if property already has value if property expression is provided', function (done) { - var studentData = { - 'name': 'Rahul', - 'departmentId': 2, - 'departmentName': 'Science', - 'rollNumber': '5', - 'studentId': 5 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(results.departmentName).to.equal('Science'); - done(); - }); - }); - - it('Property Expression Test - Should get data from where instance query if property expression is provided and field is blank or not provided', function (done) { - var studentData = { - 'departmentId': 2, - 'rollNumber': '6', - 'studentId': 6 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(results.name).to.equal('Rahul'); - done(); - }); - }); - - it('Property Expression Test - Should get data from where identifier query if property expression is provided and field is blank or not provided', function (done) { - var studentData = { - 'rollNumber': '7', - 'studentId': 7 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(results.departmentId).to.equal(2); - done(); - }); - }); - - it('Property Expression Test - Should get data from where callcontext query if property expression is provided and field is blank or not provided', function (done) { - var studentData = { - 'rollNumber': '8', - 'studentId': 8 - }; - studentModel.create(studentData, bootstrap.defaultContext, function (err, results) { - expect(results.emailAddress).to.equal('test-user@mycompany.com'); - done(); - }); - }); - -}); \ No newline at end of file diff --git a/test/providers.json b/test/providers.json deleted file mode 100644 index e616d7f..0000000 --- a/test/providers.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "local": { - "provider": "local", - "module": "passport-local", - "usernameField": "username", - "passwordField": "password", - "authPath": "/auth/local", - "successRedirect": "/", - "successReturnToOrRedirect": "/", - "failureRedirect": "/local", - "failureFlash": true, - "setAccessToken": true, - "session": false, - "forceDefaultCallback": true, - "cookie": true, - "json": true, - "failWithError": true - }, - "google-login": { - "provider": "google", - "module": "passport-google-oauth", - "strategy": "OAuth2Strategy", - "clientID": "{google-client-id}.apps.googleusercontent.com", - "clientSecret": "{google-client-secret}", - "callbackURL": "/auth/google/callback", - "authPath": "/auth/google", - "callbackPath": "/auth/google/callback", - "successRedirect": "/auth/account", - "failureRedirect": "/login", - "scope": [ - "email", - "profile" - ], - "failureFlash": true - }, - "google-link": { - "provider": "google", - "module": "passport-google-oauth", - "strategy": "OAuth2Strategy", - "clientID": "{google-client-id}.apps.googleusercontent.com", - "clientSecret": "{google-client-secret}", - "callbackURL": "/link/google/callback", - "authPath": "/link/google", - "callbackPath": "/link/google/callback", - "successRedirect": "/auth/account", - "failureRedirect": "/login", - "scope": [ - "email", - "profile" - ], - "link": true, - "failureFlash": true - }, - "saml": { - "provider": "saml", - "module": "passport-saml", - "entryPoint": "http://localhost:8080/OpenAM-12.0.0/SSORedirect/metaAlias/idp", - "issuer": "http://localhost:3000", - "passReqToCallback": true, - "cert": "{certificate}", - "authScheme": "saml", - "callbackHTTPMethod": "post", - "callbackURL": "/saml/consume/callback", - "callbackPath": "/saml/consume/callback", - "authPath": "/saml/consume", - "failureRedirect": "/logout", - "successRedirect": "/" - } -} diff --git a/test/redirect-for-uiroute-test.js b/test/redirect-for-uiroute-test.js deleted file mode 100644 index 5a82ed8..0000000 --- a/test/redirect-for-uiroute-test.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -var options; -describe(chalk.blue('uiroutes-test'), function () { - this.timeout(10000); - - before('prepare test data', function (done) { - options = {}; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - bootstrap.models.UIRoute.destroyAll({}, options, function (err, r) { - if (err) return done(err); - - bootstrap.models.UIRoute.create({ - name: 'test-path', - path: '/test-path', - type: 'elem', - import: '/dummy/test-path.html' - }, options, function (err, r) { - done(err); - }); - }); - }); - - it('returns 404 for non-existing routes', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = '/non-existing-path'; - api.get(getUrl) - .expect(404) - .end(function (err, result) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - - it('returns redirect for existing routes', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = '/test-path'; - api.get(getUrl) - .expect(302) - .end(function (err, result) { - if (err) { - done(err); - } else { - done(); - } - }); - }); -}); diff --git a/test/relation-has-one-test.js b/test/relation-has-one-test.js deleted file mode 100644 index ed17524..0000000 --- a/test/relation-has-one-test.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('switch-data-source-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); - -describe(chalk.blue('relation-has-one'), function () { - this.timeout(20000); - var parentModelName = 'HasOneParent'; - var childModelName = 'HasOneChild'; - - var testChildModel = { - name: childModelName, - base: 'BaseEntity', - properties: { - 'childName': { - 'type': 'string', - } - }, - relations: { - 'parent': { - 'type': 'belongsTo', - 'model': parentModelName - } - } - }; - - var testParentModel = { - name: parentModelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db', - relations: { - 'child': { - 'type': 'hasOne', - 'model': childModelName, - 'foreignKey': 'parentId' - } - } - }; - - var data = { - 'name': 'Name1', - 'description': 'OK' - }; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - var iciciUser = { - 'username': 'iciciUser', - 'password': 'password++', - 'email': 'iciciuser@gmail.com', - 'tenantId': 'icici' - }; - - before('create model', function (done) { - async.series([ - function createChildModel(cb) { - ModelDefinition.create(testChildModel, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', err); - cb(err); - } else { - cb(); - } - }); - }, - function createParentModel(cb) { - ModelDefinition.create(testParentModel, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', err); - cb(err); - } else { - cb(); - } - }); - }, - function (cb) { - // this line is just to test context lost problem - bootstrap.createTestUser(iciciUser, 'admin', cb); - }, - function alldone(err) { - done(); - } - ]); - }); - - it('create and find data ', function (done) { - - var model = loopback.getModel(parentModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) { - model.create(data, bootstrap.defaultContext, function (err, res) { - model.find({ - 'where': { - 'name': 'Name1' - } - }, bootstrap.defaultContext, function (err, res) { - log.debug(bootstrap.defaultContext, 'verify data ', err, res); - - res[0].reload(bootstrap.defaultContext, function (err, parent) { - parent.child.create({ - childName: 'Child1' - }, bootstrap.defaultContext, function (err, response) { - expect(response.parentId).to.be.equal(parent.id); - done(); - }); - }); - }); - }); - }); - }); - - - after('after clean up', function (done) { - var model = loopback.getModel(parentModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - done(err); - } else { - log.debug(bootstrap.defaultContext, 'number of record deleted -> ', info.count); - ModelDefinition.destroyAll({ - 'name': parentModelName - }, bootstrap.defaultContext, function () { - done(); - }); - } - }); - }); - -}); diff --git a/test/relation-references-many-test.js b/test/relation-references-many-test.js deleted file mode 100644 index 02bc054..0000000 --- a/test/relation-references-many-test.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('switch-data-source-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); - -describe(chalk.blue('relation-references-many'), function () { - - var sourceModelName = 'RefManySource'; - var dependentModelName = 'RefManyDependent'; - var ids; - - var testdependentModel = { - name: dependentModelName, - base: 'BaseEntity', - properties: { - 'dependentName': { - 'type': 'string', - } - }, - relations: { - 'source': { - 'type': 'referencesMany', - 'model': sourceModelName, - 'foreignKey': 'sourceIds' - } - } - }; - - var testsourceModel = { - name: sourceModelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db', - relations: { - 'dependent': { - 'type': 'hasOne', - 'model': dependentModelName, - 'foreignKey': 'sourceIds' - } - } - }; - - var data = [{ - 'name': 'Name1', - 'description': 'OK' - },{ - 'name': 'Name2', - 'description': 'OK' - }]; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - var iciciUser = { - 'username': 'iciciUser', - 'password': 'password++', - 'email': 'iciciuser@gmail.com', - 'tenantId': 'icici' - }; - - before('create model', function (done) { - async.series([ - function createdependentModel(cb) { - ModelDefinition.create(testdependentModel, bootstrap.defaultContext, function (err, res) { - if (err) { - if (err.statusCode === 422) { - cb(); - } else { - cb(err); - } - } else { - cb(); - } - }); - }, - function createsourceModel(cb) { - ModelDefinition.create(testsourceModel, bootstrap.defaultContext, function (err, res) { - if (err) { - if (err.statusCode === 422) { - cb(); - } else { - cb(err); - } - } else { - cb(); - } - }); - }, - function (cb) { - // this line is just to test context lost problem - bootstrap.createTestUser(iciciUser, 'admin', cb); - }, - function alldone(err) { - done(); - } - ]); - }); - - it('create and find data ', function (done) { - var model = loopback.getModel(sourceModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) { - model.create(data, bootstrap.defaultContext, function (err, res) { - ids = res.map(x=>JSON.parse(JSON.stringify(x.id))); - done(); - }); - }); - }); - - it('embedsMany - empty foreign key ', function (done) { - var model = loopback.getModel(dependentModelName, bootstrap.defaultContext); - var data = {dependentName: 'empty'}; - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err).to.be.null; - expect(res).not.to.be.null; - done(); - }); - }); - - it('embedsMany - foreign key all wrong ', function (done) { - var model = loopback.getModel(dependentModelName, bootstrap.defaultContext); - var data = {dependentName: 'wrong1', sourceIds: ['something', 'something2']}; - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err.statusCode).to.be.eq(422); - done(); - }); - }); - - it('embedsMany - foreign key few wrong ', function (done) { - var model = loopback.getModel(dependentModelName, bootstrap.defaultContext); - var data = {dependentName: 'wrong2'}; - data.sourceIds = JSON.parse(JSON.stringify(ids)); - data.sourceIds.push('notok'); - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err.statusCode).to.be.eq(422); - done(); - }); - }); - - var instance; - it('embedsMany - foreign key correct array ', function (done) { - var model = loopback.getModel(dependentModelName, bootstrap.defaultContext); - var data = {dependentName: 'ok', sourceIds: ids}; - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err).to.be.null; - expect(res).not.to.be.null; - instance = res; - done(); - }); - }); - - it('embedsMany - javascript api test ', function (done) { - instance.source(null, bootstrap.defaultContext, function(err, res) { - expect(err).to.be.null; - expect(res.length).to.be.eq(2); - done(); - }) - }); - - after('after clean up', function (done) { - var model = loopback.getModel(sourceModelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - done(err); - } else { - log.debug(bootstrap.defaultContext, 'number of record deleted -> ', info.count); - ModelDefinition.destroyAll({ - 'name': sourceModelName - }, bootstrap.defaultContext, function () { - done(); - }); - } - }); - }); - -}); diff --git a/test/relation-references-many2-test.js b/test/relation-references-many2-test.js deleted file mode 100644 index e14054b..0000000 --- a/test/relation-references-many2-test.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async'); -var log = require('oe-logger')('switch-data-source-test'); -var chai = require('chai'); -var expect = chai.expect; -var loopback = require('loopback'); -chai.use(require('chai-things')); - -describe(chalk.blue('relation-references-many-recursive-relation'), function () { - - var modelName = 'RefMany2'; - var ids; - - var testModel = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - } - }, - relations: { - 'parent': { - 'type': 'referencesMany', - 'model': modelName, - 'foreignKey': 'parentIds' - }, - 'children': { - 'type': 'hasMany', - 'model': modelName, - 'foreignKey': 'parentIds' - } - } - }; - - var data = [{ - 'name': 'Name1', - 'description': 'OK' - },{ - 'name': 'Name2', - 'description': 'OK' - }]; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - var iciciUser = { - 'username': 'iciciUser', - 'password': 'password++', - 'email': 'iciciuser@gmail.com', - 'tenantId': 'icici' - }; - - before('create model', function (done) { - async.series([ - function createModel(cb) { - ModelDefinition.create(testModel, bootstrap.defaultContext, function (err, res) { - if (err) { - if (err.statusCode === 422) { - cb(); - } else { - cb(err); - } - } else { - cb(); - } - }); - }, - function (cb) { - // this line is just to test context lost problem - bootstrap.createTestUser(iciciUser, 'admin', cb); - }, - function alldone(err) { - done(); - } - ]); - }); - - it('create and find data ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, res) { - model.create(data, bootstrap.defaultContext, function (err, res) { - ids = res.map(x=>JSON.parse(JSON.stringify(x.id))); - done(); - }); - }); - }); - - it('embedsMany - foreign key incorrect values ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var data = {dependentName: 'wrong', parentIds: ['blah1', 'blah2']}; - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err.statusCode).to.be.eq(422); - done(); - }); - }); - - var instance; - it('embedsMany - foreign key correct array ', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - var data = {dependentName: 'ok', parentIds: ids}; - model.create(data, bootstrap.defaultContext, function (err, res) { - expect(err).to.be.null; - expect(res).not.to.be.null; - instance = res; - done(); - }); - }); - - var parentInstance; - it('embedsMany - javascript api test ', function (done) { - instance.parent(null, bootstrap.defaultContext, function(err, res) { - expect(err).to.be.null; - expect(res.length).to.be.eq(2); - parentInstance = res[0]; - done(); - }) - }); - - it('embedsMany - javascript reverse api test ', function (done) { - parentInstance.children(null, bootstrap.defaultContext, function(err, res) { - expect(err).to.be.null; - expect(res.length).to.be.eq(1); - done(); - }) - }); - - after('after clean up', function (done) { - var model = loopback.getModel(modelName, bootstrap.defaultContext); - model.destroyAll({}, bootstrap.defaultContext, function (err, info) { - if (err) { - done(err); - } else { - log.debug(bootstrap.defaultContext, 'number of record deleted -> ', info.count); - done(); - } - }); - }); - -}); diff --git a/test/retry-support-mixin-test.js b/test/retry-support-mixin-test.js deleted file mode 100644 index 6f41cd2..0000000 --- a/test/retry-support-mixin-test.js +++ /dev/null @@ -1,156 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* - * @author David Zharnest - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('retry-support-test'); - -var api = bootstrap.api; - -var accessToken; - -var modelId; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function(err, res) { - if (err || res.body.error) { - log.error(err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -describe(chalk.blue('retry-support-tests'), function() { - this.timeout(30000); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function(err, res) { - if (err) { - log.error(err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestRetryAccount', - 'base': 'BaseEntity', - 'properties': { - 'yodels': { - 'type':'string', - 'id':true - } - }, - 'mixins': { - 'RetrySupportMixin': true - }, - 'options': { - stateThreshold: 1 - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, function (err, res) { - if (err) { - return done(err); - } - modelId = res.id.toString(); - done(); - }); - }); - - - it('check that it is retryable', function(done) { - - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestRetryAccounts/isRetryable?access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body).to.be.equal('true'); - return done(); - } - }); - - }); - - it('check that the id is yodels', function(done) { - - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestRetryAccounts/primaryKeyField?access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body.name).to.be.equal('yodels'); - return done(); - } - }); - - }); - - var deleteContext = {fetchAllScopes: true, ctx: {tenantId: 'test-tenant'}}; - - after('delete all the test accounts', function(done) { - var testAccount = loopback.getModel('TestRetryAccount', bootstrap.defaultContext); - testAccount.destroyAll({}, deleteContext, function(err) { - if (err) { - log.error(err); - return done(err); - } else { - return done(); - } - }); - }); - - after('delete all modelDefinition models', function(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - modelDefinition.destroyById(modelId, bootstrap.defaultContext, function(err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); -}); diff --git a/test/rule-engine-cluster-tests/cluster-tests.js b/test/rule-engine-cluster-tests/cluster-tests.js deleted file mode 100644 index d962917..0000000 --- a/test/rule-engine-cluster-tests/cluster-tests.js +++ /dev/null @@ -1,383 +0,0 @@ -/** - * - * ©2017-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -const chalk = require('chalk'); -const https = require('https'); -const assert = require('assert'); -const url = require('url'); -const querystring = require('querystring'); -const util = require('util'); -const fs = require('fs'); -const verbose = process.env.DEBUG_TEST || 0; - -const node1 = "https://test.node1.oecloud.local"; -const node2 = "https://test.node2.oecloud.local"; - -// Using urlToOptions from the nodejs source itself -// https://github.com/nodejs/node/blob/1329844a0808705091891175a6bee58358380af6/lib/internal/url.js#L1305-L1322 -function urlToOptions(url) { - var options = { - protocol: url.protocol, - hostname: url.hostname, - hash: url.hash, - search: url.search, - pathname: url.pathname, - path: `${url.pathname}${url.search}`, - href: url.href - }; - if (url.port !== '') { - options.port = Number(url.port); - } - if (url.username || url.password) { - options.auth = `${url.username}:${url.password}`; - } - return options; -}; - -var prefix = 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,'; - -var makeUrl = function(server, apiPath) { - // return util.format('%s%s', server, apiPath); - var urlInfo = new url.URL(server); - urlInfo.pathname = apiPath; - return urlInfo; -}; - -function assertStatusCode200(result) { - var status_code = result.res.statusCode; - var response = result.responseText; - console.assert(status_code === 200, - "Expected status code 200.", - util.format("Got status code: %s.", status_code), - "Response text:\n", response); -} - -function postData(urlInfo, data) { - // console.log('POST:', options.href, 'DATA:', data); - return new Promise((resolve, reject) => { - var payload = JSON.stringify(data); - var options = urlToOptions(urlInfo); - options.method = 'POST'; - options.headers = { - 'Content-Type' : 'application/json', - 'Content-Length' : payload.length - }; - // console.log('POST:', options, 'Payload:', payload); - if (verbose) { - console.log('POST:', options, 'Payload:', payload); - } - var req = https.request(options, res => { - var outputString = ""; - res.on('data', chunk => outputString += chunk); - res.on('end', () => { - resolve({ res, responseText: outputString }); - }); - }); - - req.on('error', reject); - req.write(payload); - req.end(); - }); -} - -function get(urlInfo) { - // console.log('GET:', options.href); - return new Promise((resolve, reject) => { - var options = urlToOptions(urlInfo); - if (verbose) { - console.log('GET:', options); - } - var req = https.get(options, res => { - var data = ""; - res.on('data', chunk => data += chunk); - res.on('end', () => { - resolve({ - res, - responseText: data - }); - }); - }); - - req.on('error', reject); - }); -} - -function delay(timeout) { - return new Promise(resolve => setTimeout(resolve, timeout)); -} - -describe(chalk.blue('rule cluster tests'), function(){ - - var credo = { - 'username' : "admin", - 'password' : 'admin', - 'tenantId' : 'test-tenant' - }; - - var access_token_node1; - - it('should successfuly log-in to node1', done => { - // var reqObj = url.parse("https://test.node1.oecloud.local/auth/local"); - // var payload = JSON.stringify(credo); - - var urlInfo = makeUrl(node1, "/api/BaseUsers/login"); - - postData(urlInfo, credo).then(result => { - assertStatusCode200(result); - var data = result.responseText; - access_token_node1 = JSON.parse(data).id; - assert(access_token_node1, "got no access token post login", data); - done(); - }) - .catch(done); - - }); - - it('should create Employee model (via node2)', done => { - - var employeeModel = { - name: 'Employee', - properties: { - name: 'string', - qualification: 'object' - } - }; - - // var options = new url.URL('https://test.node1.oecloud.local/api/ModelDefinitions'); - var options = makeUrl(node2, "/api/ModelDefinitions"); - options.searchParams.append('access_token', access_token_node1); - - postData(options, employeeModel).then(result => { - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - assert(!Array.isArray(data), "expected the json response to be an object"); - assert(data.name, "record retrieved should have \"name\" as a property"); - assert(data.name === employeeModel.name, "does not match what we input for creation"); - done(); - }) - .catch(done) - - }); - // - it('should assert that the Employee model exists (in node1)', done => { - // var endpoint = new url.URL('https://test.node2.oecloud.local/api/ModelDefinitions'); - // debugger; - var endpoint = makeUrl(node1, "/api/Employees"); - var filter = { where: { name: 'Employee'}}; - endpoint.searchParams.append('access_token', access_token_node1); - // endpoint.searchParams.append('filter', JSON.stringify(filter)); - // var options = url.parse(util.format(endpoint, access_token_node1, querystring.escape(JSON.stringify(filter)))); - delay(5000).then(() => { - get(endpoint).then(result => { - // console.log(result.responseText) - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - assert(Array.isArray(data), 'expected response to be an array'); - // var record = data[0]; - assert(data.length === 0, "no records should exist"); - // console.log(record); - // assert(record.name === 'Employee', "model name should be \"Employee\""); - done(); - }) - .catch(done); - }); - }); - - it('should insert a decision called "TestDecision" into DecisionTable (via node1)', done => { - // var options = new url.URL('https://test.node1.oecloud.local/api/DecisionTables'); - var options = makeUrl(node1, "/api/DecisionTables"); - options.searchParams.append('access_token', access_token_node1); - - var data = { - name: 'TestDecision', - document: { - documentName: 'foo.xlsx', - documentData: prefix + fs.readFileSync('./test/model-rule-data/employee_validation.xlsx').toString('base64') - } - }; - - postData(options, data).then(result => { - assertStatusCode200(result); - done(); - }) - .catch(done); - - }); - - it('should assert that "TestDecision" is available (in node2)', done => { - // var options = new url.URL('https://test.node2.oecloud.local/api/DecisionTables'); - var options = makeUrl(node2, "/api/DecisionTables"); - options.searchParams.append('access_token', access_token_node1); - options.searchParams.append('filter', JSON.stringify({ where: {name: 'TestDecision'}})); - // console.log('Url:', options.href); - get(options).then(result => { - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - assert(Array.isArray(data), "expected response to be an array"); - assert(data.length, "expected response to have one record"); - var record = data[0]; - assert(record.name === 'TestDecision', 'not matching what was inserted'); - done(); - }) - .catch(done); - }); - - it('should successfully attach a model validation rule to the Employee model (via node2)', done => { - // var options = new url.URL('https://test.node2.oecloud.local/api/ModelRules'); - var options = makeUrl(node2, "/api/ModelRules"); - options.searchParams.append('access_token', access_token_node1); - - var record = { - modelName: 'Employee', - validationRules: ['TestDecision'] - }; - // debugger; - postData(options, record).then(result => { - // console.log(result.responseText); - var response = JSON.parse(result.responseText); - // console.log(response); - assertStatusCode200(result); - done(); - }) - .catch(done); - - }); - - it('should successfully insert a valid employee record (via node1)', done => { - // var options = new url.URL('https://test.node1.oecloud.local/api/Employees'); - var options = makeUrl(node1, "api/Employees"); - options.searchParams.append('access_token', access_token_node1); - - var data = { - name: 'Emp1', - qualification: { - marks_10: 65, - marks_12: 65 - } - }; - - postData(options, data).then(result => { - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - // console.log(data); - done(); - }) - .catch(done); - }); - - it('should assert the presence of the above inserted record (via node2)', done => { - // var options = new url.URL('https://test.node2.oecloud.local/api/Employees'); - var options = makeUrl(node2, "api/Employees"); - options.searchParams.append('access_token', access_token_node1); - options.searchParams.append('filter', JSON.stringify({ where: { name: 'Emp1' }})); - - get(options).then(result => { - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - assert(Array.isArray(data), "response received is not an array"); - assert(data.length === 1, "Expected length of data: 1. Actual length received: " + data.length); - assert(data[0].name === 'Emp1', 'Expected "Emp1", Actual: ' + data[0].name); - done(); - }) - .catch(done); - }); - - it('should deny insert of invalid record (via node2)', done => { - // var options = new url.URL('https://test.node2.oecloud.local/api/Employees'); - var options = makeUrl(node2, "api/Employees"); - options.searchParams.append('access_token', access_token_node1); - - var data = { - name: 'Emp2', - qualification: { - marks_10: 45, - marks_12: 65 - } - }; - - postData(options, data).then(result => { - // assertStatusCode200(result.res); - assert(result.res.statusCode !== 200, "expected an error status code, but got 200"); - done(); - }) - .catch(done); - }); - - it('should assert the absence of the record attempted to save in the previous step (via node1)', done => { - // var options = new url.URL('https://test.node1.oecloud.local/api/Employees'); - var options = makeUrl(node1, "api/Employees"); - options.searchParams.append('access_token', access_token_node1); - options.searchParams.append('filter', JSON.stringify({ where: { name: 'Emp2' }})); - - get(options).then(result => { - assertStatusCode200(result); - var data = JSON.parse(result.responseText); - assert(Array.isArray(data), "response received is not an array"); - assert(data.length === 0, "Expected length of data: 0. Actual length received: " + data.length); - // assert(data[0].name === 'Emp1', 'Expected "Emp1", Actual: ' + data[0].name); - done(); - }) - .catch(done); - }); - - it('should not take down nodes when executing a rule that came from a corrupted file', done => { - - // debugger; - var fileContents = prefix + fs.readFileSync('./test/model-rule-data/corrupt.xlsx').toString('base64'); - - var urlInfo = makeUrl(node1, 'api/DecisionTables'); - urlInfo.searchParams.append('access_token', access_token_node1); - - var data = { - name: 'TestDecision2', - document: { - documentName: 'foo2.xlsx', - documentData: fileContents - } - }; - - postData(urlInfo, data).then(httpResult => { - //! We have inserted the file... - assertStatusCode200(httpResult); - }) - .then(() => { - // now we execute this rule against payload - var input_data = { - qualification: { - marks_12: 66, - marks_10: 65 - } - }; - - return Promise.all([node1, node2].map(n => { - var u = makeUrl(n, `api/DecisionTables/exec/${data.name}`); - u.searchParams.append('access_token', access_token_node1); - return postData(u, input_data); - })); - }) - .then(results => { - // console.assert(httpResult.res.statusCode !== 200, "Expected non 200 status code", "Actual Status code received:", httpResult.res.statusCode, 'Response:\n', httpResult.responseText); - results.forEach(r => { - var status_code = r.res.statusCode; - console.assert(status_code !== 200, "expected a non 200 response status code"); - }) - }) - .then(() => { - //check if nodes haven't gone down... - return Promise.all([node1, node2].map(n => { - var u = makeUrl(n, ''); - return get(u); - })) - }) - .then(results => { - results.forEach(r => { - assertStatusCode200(r); - }); - done(); - }) - .catch(done); - }).timeout(60000); -}); diff --git a/test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml b/test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml deleted file mode 100644 index 71e09d4..0000000 --- a/test/rule-engine-cluster-tests/rule-engine-cluster-setup.yml +++ /dev/null @@ -1,66 +0,0 @@ -version: "3" -services: - mongo: - image: ${REGISTRY}/alpine-mongo:latest - networks: - - ${NETWORK_NAME} - # - router_network - - broadcaster: - image: ${REGISTRY}/oecloudio-broadcaster-server:latest - environment: - SERVICE_PORTS: "2345" - networks: - - ${NETWORK_NAME} - - node1: - entrypoint: ["node", "."] - image: ${REGISTRY}/${APP_IMAGE_NAME}:latest - depends_on: - - mongo - - broadcaster - deploy: - mode: replicated - replicas: 1 - update_config: - delay: 60s - environment: - VIRTUAL_HOST: "https://test.node1.${DOMAIN_NAME},test.node1.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - SERVICE_NAME: "${APP_IMAGE_NAME}" - BROADCASTER_HOST: "broadcaster" - ORCHESTRATOR: "dockerSwarm" - ROUTER_HOST: ${ROUTER} - networks: - - ${NETWORK_NAME} - - router_network - - node2: - entrypoint: ["node", "."] - image: ${REGISTRY}/${APP_IMAGE_NAME}:latest - depends_on: - - mongo - - broadcaster - deploy: - mode: replicated - replicas: 1 - update_config: - delay: 60s - - environment: - VIRTUAL_HOST: "https://test.node2.${DOMAIN_NAME},test.node2.${DOMAIN_NAME}" - SERVICE_PORTS: "3000" - FORCE_SSL: "yes" - SERVICE_NAME: "${APP_IMAGE_NAME}" - BROADCASTER_HOST: "broadcaster" - ORCHESTRATOR: "dockerSwarm" - ROUTER_HOST: ${ROUTER} - networks: - - ${NETWORK_NAME} - - router_network - -networks: - $NETWORK_NAME: - router_network: - external: true diff --git a/test/rule-engine-cluster-tests/wait-all.sh b/test/rule-engine-cluster-tests/wait-all.sh deleted file mode 100644 index 8c709f8..0000000 --- a/test/rule-engine-cluster-tests/wait-all.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -waitcmd="wait " -i=0 -for url in $@; do - /bin/sh ./test/rule-engine-cluster-tests/wait-for-up.sh $url & - # ((i=$i+1)) - let i=i+1 - waitcmd="$waitcmd%$i " -done -echo "Waiting for services..." -eval $waitcmd - -ls pid.*.txt > /dev/null 2>&1 - -if [[ "$?" -eq "0" ]]; then - #files exist!! - echo "Services not up!!" - exit 1 -else - echo "Services up!!" -fi diff --git a/test/rule-engine-cluster-tests/wait-for-up.sh b/test/rule-engine-cluster-tests/wait-for-up.sh deleted file mode 100644 index c94055e..0000000 --- a/test/rule-engine-cluster-tests/wait-for-up.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# rm pid.*.txt -pid=$$ -fname="pid.$pid.txt" -echo $pid > $fname - -url=$1 -counter=0 -status_code=404 -max_retry=50 -# status_code=$(curl -s -o /dev/null -w "%{http_code}" $url) -status_code="0" -is_fail=0 -sleep_time=30 - -echo "Checking status of : $url" -until [[ "$status_code" -eq "200" ]]; do - # ((counter=$counter+1)) - # counter=$((counter+1)) - let counter=counter+1 - echo "Attempting to reach $url ... ($counter/$max_retry)" - status_code=$(curl -k -s -o /dev/null -w "%{http_code}" --connect-timeout 5 $url) - echo "$status_code ($url)" - if [[ "$counter" -lt "$max_retry" ]]; then - sleep $sleep_time - else - is_fail=1 - break - fi -done - -if [[ "$is_fail" -eq "1" ]]; then - echo "Attempting to reach $url ... ($counter/$max_retry) ... failed!!!" - exit 1 -else - # echo "rm $fname" - rm $fname -fi diff --git a/test/secrets-manager-test.js b/test/secrets-manager-test.js deleted file mode 100644 index 2e2c57e..0000000 --- a/test/secrets-manager-test.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var secretsManager = require('../lib/secrets-manager.js'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); - -var orchestrator; -var vcapServices; - -describe(chalk.blue('secrets-manager'), function () { - - this.timeout(60000); - - before("setting required data", function(done){ - orchestrator = process.env.ORCHESTRATOR; - vcapServices = process.env.VCAP_SERVICES; - done(); - }); - - after("cleaning up secrets", function(done){ - process.env.ORCHESTRATOR = orchestrator; - process.env.VCAP_SERVICES = vcapServices; - done(); - }); - - it("Docker secrets are populated in env variable", function(done){ - process.env.ORCHESTRATOR = "DockerSwarm"; - process.env.DOCKER_SECRETS_FOLDER = "./test/secrets-test-data"; - secretsManager.populateSecrets(); - expect(process.env.TESTMONGOHOST).to.be.equal('testmongohost'); - expect(process.env.TESTMONGOUSER).to.be.equal('testmongouser'); - done(); - }); - - it("PCF secrets are populated in env variable", function(done){ - process.env.ORCHESTRATOR = "PCF"; - process.env.PCF_SERVICES = "[\"TEST_MONGO_SECRETS\"]"; - process.env.VCAP_SERVICES = '{"user-provided":[{"credentials":{"TEST_MONGO_HOST":"10.0.0.1","TEST_MONGO_PASSWORD":"password","TEST_MONGO_USER":"oecloud"},"syslog_drain_url":"","volume_mounts":[],"label":"user-provided","name":"TEST_MONGO_SECRETS","tags":[]}]}'; - secretsManager.populateSecrets(); - expect(process.env.TEST_MONGO_HOST).to.be.equal('10.0.0.1'); - expect(process.env.TEST_MONGO_PASSWORD).to.be.equal('password'); - expect(process.env.TEST_MONGO_USER).to.be.equal('oecloud'); - done(); - }); -}); \ No newline at end of file diff --git a/test/secrets-test-data/TESTMONGOHOST b/test/secrets-test-data/TESTMONGOHOST deleted file mode 100644 index 0226de1..0000000 --- a/test/secrets-test-data/TESTMONGOHOST +++ /dev/null @@ -1 +0,0 @@ -testmongohost \ No newline at end of file diff --git a/test/secrets-test-data/TESTMONGOUSER b/test/secrets-test-data/TESTMONGOUSER deleted file mode 100644 index 7725922..0000000 --- a/test/secrets-test-data/TESTMONGOUSER +++ /dev/null @@ -1 +0,0 @@ -testmongouser \ No newline at end of file diff --git a/test/select-for-update-test.js b/test/select-for-update-test.js deleted file mode 100644 index 165a769..0000000 --- a/test/select-for-update-test.js +++ /dev/null @@ -1,228 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var async = require('async'); -var baseUrl = bootstrap.basePath; -var uuidv4 = require('uuid/v4'); -var loopback = require('loopback'); -var _ = require('lodash'); -if (!process.env.PERF_TEST || - (process.env.NODE_ENV && process.env.NODE_ENV !== 'postgres')) { - return -} - -describe(chalk.blue('select for update performance test'), function() { - - var transactionModel = 'SelForUpdTransaction'; - var activityModel = 'ActorActivity'; - var accountBalanceModel = 'SelForUpdAccountBalance'; - var transactionModelPlural = transactionModel + 's'; - var accountBalanceModelPlural = accountBalanceModel + 's'; - var transactionUrl = baseUrl + '/' + transactionModelPlural; - - this.timeout(300000); - var accessToken; - var newContext = function() { - return { - ctx: { - tenantId: 'default', - remoteUser: 'admin' - } - }; - }; - - it('login', function(done) { - var postUrl = baseUrl + '/BaseUsers/login'; - var credentials = {username: 'admin', password: 'admin'}; - var api = defaults(supertest(bootstrap.app)); - - // without jwt token - api.set('Accept', 'application/json') - .set('tenant_id', 'default') - .post(postUrl) - .send(credentials) - .expect(200).end(function(err, response) { - accessToken = response.body.id; - done(); - }); - }); - - it('Create Models', function(done) { - var transactionModelData = { - name: transactionModel, - plural: transactionModelPlural, - base: 'BaseEntity', - properties: { - activities: [activityModel] - } - }; - - var accountBalanceModelData = { - name: accountBalanceModel, - base: 'BaseEntity', - overridingMixins: { - ModelValidations: false, - DataPersonalizationMixin: false, - HistoryMixin: false, - IdempotentMixin: false, - EvVersionMixin: false, - FailsafeObserverMixin: false, - BusinessRuleMixin: false, - SoftDeleteMixin: true, - AuditFieldsMixin: true, - ExpressionAstPopulatorMixin: false, - CryptoMixin: false, - PropertyExpressionMixin: false - }, - properties: { - amount: 'number' - } - }; - - var postUrl = baseUrl + '/ModelDefinitions?access_token=' + accessToken; - var createModel = function(x, cb) { - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .send(x) - .end(function(err, resp) { - if (err) { - cb(); - } else { - if (resp.status === 400) { - cb(); - } else if (resp.status === 422) { - cb(); - } else { - expect(200); - cb(); - } - } - }); - } - async.eachSeries([transactionModelData, accountBalanceModelData], (x,cb) => createModel(x,cb), done); - }); - - it('cleanup existing data', function(done) { - var AccountBalance = loopback.findModel(accountBalanceModel, ctx); - var Transaction = loopback.findModel(transactionModel, ctx); - var ctx = newContext(); - AccountBalance.destroyAll({}, ctx, function(err) { - Transaction.destroyAll({}, ctx, function(err) { - done(); - }); - }); - }); - - it('Attach business logic to transaction model', function(done) { - var ctx1 = newContext(); - var Transaction = loopback.findModel(transactionModel, ctx1); - var AccountBalance = loopback.findModel(accountBalanceModel, ctx1); - var TxnLogic = function(model) { - model.observe('persist', function(ctx, next) { - model.beginTransaction({ - isolationLevel: model.Transaction.READ_COMMITTED, - timeout: 30000 - }, function(err, tx) { - if (err) - console.log('Unable to start transaction ', err); - ctx.options.transaction = tx; - var instance = ctx.instance || ctx.currentInstance || ctx.data; - var accountList = JSON.parse(JSON.stringify(instance.activities.map(x=>x.entityId))); - var filter = { where: { id: {inq: accountList}}, selectForUpdate: true}; - AccountBalance.find(filter, ctx.options, function(err, data) { - data.forEach(function(accountBalance) { - var activity = instance.activities.find(x=> x.entityId == accountBalance.id); - var delta = activity.instructionType == 'C' ? activity.payload.amount : -activity.payload.amount; - accountBalance.amount += delta; - }); - async.forEach(data, (acct, cb) => AccountBalance.upsert(data, ctx.options, (err, data) => cb(err)) , next) - }); - }); - }); - model.observe('after save', function(ctx, next) { - if (ctx.options.transaction) { - ctx.options.transaction.commit(ctx.options, function(err) { - next(); - }); - } else { - console.log('transaction object is missing'); - } - }); - } - TxnLogic(Transaction); - done(); - }); - - var accountNumbersArray = Array.from(new Array(1000), (x,i) => (i+1).toString()) - - it('Create account balance data', function(done) { - var postUrl = baseUrl + '/' + accountBalanceModelPlural + '?access_token=' + accessToken; - var createData = function(x, cb) { - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .send(x) - .end(function(err, resp) { - if (err) { - cb(); - } else { - if (resp.status === 400) { - cb(); - } else if (resp.status === 422) { - cb(); - } else { - expect(200); - cb(); - } - } - }); - } - var accountBalanceData = accountNumbersArray.map(x=>{return {id:x,balance:0}}); - - // do POST async with concurrency of 10 - async.eachSeries(_.chunk(accountBalanceData, 10), (x1,cb1) => async.forEach(x1, (x2,cb2)=>createData(x2,cb2), cb1), done); - }); - - it('Do transaction post', function(done) { - var postUrl = baseUrl + '/' + transactionModelPlural + '?access_token=' + accessToken; - var createData = function(x, cb) { - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .post(postUrl) - .send(x) - .end(function(err, resp) { - if (err) { - cb(); - } else { - if (resp.status === 400) { - cb(); - } else if (resp.status === 422) { - cb(); - } else { - expect(200); - cb(); - } - } - }); - } - var txnData = accountNumbersArray.map(x=>{return {activities:[{entityId: x, payload: {amount: 1}, instructionType: "C"}]}}); - - async.eachSeries(_.chunk(txnData, 10), (x1,cb1) => async.forEach(x1, (x2,cb2)=>createData(x2,cb2), cb1), done); - }); - - after('after clean up', function(done) { - done(); - }); -}); diff --git a/test/server.js b/test/server.js new file mode 100644 index 0000000..221417b --- /dev/null +++ b/test/server.js @@ -0,0 +1,29 @@ +/** + * + * 2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + + +const _require = require; + +require = function (a) { + if (a === 'oe-cloud') { + return _require('../index.js'); + } + return _require(a); +} + +var oecloud = require('oe-cloud'); +var loopback=require('loopback'); + +oecloud.observe('loaded', function (ctx, next) { + return next(); +}) + +oecloud.boot(__dirname, function (err) { + oecloud.start(); + oecloud.emit('test-start'); +}); + diff --git a/test/service-personalization-relation-test.js b/test/service-personalization-relation-test.js deleted file mode 100644 index 605485c..0000000 --- a/test/service-personalization-relation-test.js +++ /dev/null @@ -1,331 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -// var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -var CustomerUrl = bootstrap.basePath; - -describe(chalk.blue('service - personalization - relation test'), function () { - var tenantId = 'test-tenant'; - var Customer = 'CustomerServPersn'; - var Address = 'AddressServPersn'; - this.timeout(20000); - - var addressMOdel; - var customerModel; - - before('setup test data', function (done) { - //upload data - var customerData = [ - { - 'name': 'jenny', - 'age': 23, - 'billingAddress': { - 'city': 'bangalore', - 'state': 'Karnataka', - 'street': 'HSR' - } - }, - - { - 'name': 'John', - 'age': 50, - 'billingAddress': { - 'city': 'blore', - 'state': 'KTK', - 'street': 'BTM' - } - }, - { - 'name': 'Jack', - 'age': 50, - 'billingAddress': { - 'city': 'blore', - 'state': 'KTK', - 'street': 'Ecity' - } - } - ]; - - models.ModelDefinition.create({ - name: Address, - base: 'BaseEntity', - plural: Address + 's', - 'properties': { - 'street': { - 'type': 'string' - }, - 'city': { - 'type': 'string' - }, - 'state': { - 'type': 'string' - } - } - - }, bootstrap.defaultContext, function (err, data) { - if (err) { - done(err); - } - models.ModelDefinition.create({ - name: Customer, - base: 'BaseEntity', - plural: Customer + 's', - properties: { - 'name': { - 'type': 'string' - }, - 'age': { - 'type': 'number' - } - }, - 'relations': { - 'address': { - 'type': 'embedsOne', - 'model': 'AddressServPersn', - 'property': 'billingAddress', - 'options': { - 'validate': true, - 'forceId': false - } - } - } - }, bootstrap.defaultContext, function (err, data) { - if (err) { - done(err); - } - addressMOdel = loopback.getModel(Address, bootstrap.defaultContext); - customerModel = loopback.getModel(Customer, bootstrap.defaultContext); - - customerModel.create(customerData, bootstrap.defaultContext, function (err, res) { - if (err) { - console.log('data creation error'); - done(err); - } else { - done(); - } - - }); - - }); - - }); - - - CustomerUrl = CustomerUrl + '/' + Customer + 's'; - - }); - - // TODO: WARNING- this is important for clean consistent runs - after('clean up', function (done) { - models.ModelDefinition.destroyAll({ - name: 'CustomerServPersn' - }, bootstrap.defaultContext, function (err, d) { - if (err) { - console.log('Error - not able to delete modelDefinition entry for mysettings'); - return done(); - } - customerModel.destroyAll({}, bootstrap.defaultContext, function () { - done(); - }); - }); - }); - - - afterEach('destroy context', function (done) { - var callContext = { - ctx: { - 'device': ['android'], - 'tenantId': tenantId - } - }; - models.PersonalizationRule.destroyAll({}, callContext, function (err, result) { - // console.log("Model Removed : ", result.count); - done(); - }); - }); - //field Name Replace - it('Embeds One:should replace field names in response data when fieldReplace personalization rule is configured', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': Customer, - 'personalizationRule': { - 'fieldReplace': { - 'billingAddress\uFF0Estreet': 'lane' - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - api.get(CustomerUrl) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = resp.body; - expect(results.length).to.be.equal(3); - expect(results[0].billingAddress).keys('city', 'state', 'lane', '_isDeleted'); - expect(results[0]).to.include.keys('name', 'age', 'billingAddress', 'id', '_isDeleted'); - done(); - - }); - - }); - }); - - - //field value Replace - xit('Embeds One:should replace field values in response data when field' + - ' value Replace personalization rule is configured', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': Customer, - 'personalizationRule': { - 'fieldValueReplace': { - 'billingAddress\uFF0Ecity': { - 'bangalore': 'Mangalore' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - api.get(CustomerUrl) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - - var results = resp.body; - expect(results.length).to.be.equal(3); - expect(results[0].billingAddress.city).to.be.equal('Mangalore'); - done(); - - }); - - }); - }); - - //sort - it('Embeds One:should sort the results based on sort expression', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': Customer, - 'personalizationRule': { - 'sort': { - 'billingAddress|street': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(CustomerUrl) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android').expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = resp.body; - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(3); - expect(results[0].billingAddress.street).to.be.equal('BTM'); - expect(results[1].billingAddress.street).to.be.equal('Ecity'); - expect(results[2].billingAddress.street).to.be.equal('HSR'); - done(); - }); - }); - }); - - // TODO: Move this test case to enable - //mask - xit('Embeds One:should mask the given fields and not send them to the response', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': Customer, - 'personalizationRule': { - 'mask': { - 'billingAddress|street': true - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(CustomerUrl) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android').expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = resp.body; - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(3); - expect(results[0].billingAddress.street).to.be.equal(undefined); - done(); - - }); - }); - }); -}); diff --git a/test/service-personalization-test.js b/test/service-personalization-test.js deleted file mode 100644 index 1c57817..0000000 --- a/test/service-personalization-test.js +++ /dev/null @@ -1,1789 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var expect = bootstrap.chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var app = bootstrap.app; - -var chai = require('chai'); -chai.use(require('chai-things')); - -var api = bootstrap.api; - -var productCatalogUrl = bootstrap.basePath; -// Clean up is required for these test cases. -describe(chalk.blue('service-personalization'), function () { - - var tenantId = 'test-tenant'; - - var dataSource; - - var ProductCatalog; - var productOwnerUrl; - this.timeout(30000); - - var accessToken; - function createModelsAndPopulateData(done) { - //var price = { - // value: Number, - // currency: String - //}; - var ProductCatalogSchema = { - 'name': { - 'type': 'string', - 'required': true - }, - 'category': { - 'type': 'string', - 'required': true - }, - 'desc': { - 'type': 'string', - 'required': true - }, - 'price': { - 'type': 'object', - 'required': true - }, - 'isAvailable': { - 'type': 'boolean', - 'required': true - }, - 'keywords': { - 'type': 'array' - }, - 'modelNo': { - 'type': 'string', - 'length': 10 - } - }; - - var opts = { - strict: false, - base: 'BaseEntity', - plural: 'ProductCatalogs' - }; - productCatalogUrl = productCatalogUrl + '/' + opts.plural; - dataSource = app.dataSources['db']; - ProductCatalog = dataSource.createModel('ProductCatalog', ProductCatalogSchema, opts); - ProductCatalog.attachTo(dataSource); - ProductCatalog.clientModelName = 'ProductCatalog'; - app.model(ProductCatalog); - - // Populate some data. - var item1 = { - 'name': 'king size bed', - 'category': 'furniture', - 'desc': 'king size bed', - 'price': { - 'value': 10000, - 'currency': 'inr' - }, - 'isAvailable': true, - "productOwnerId": 1 - }; - var item2 = { - 'name': 'office chair', - 'category': 'furniture', - 'desc': 'office chair', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - var item3 = { - 'name': 'dinning table', - 'category': 'furniture', - 'desc': 'dinning table', - 'price': { - 'value': 8000, - 'currency': 'inr' - }, - 'isAvailable': false - }; - var item11 = { - 'name': 'refrigerator', - 'category': 'electronics', - 'desc': 'refrigerator', - 'price': { - 'value': 10000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - var item22 = { - 'name': 'water heater', - 'category': 'electronics', - 'desc': 'water heater', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - var item33 = { - 'name': 'oven', - 'category': 'electronics', - 'desc': 'oven', - 'price': { - 'value': 8000, - 'currency': 'inr' - }, - 'isAvailable': false - }; - - ProductCatalog.create(item1, bootstrap.defaultContext, function (err, item) { - if (err) { - return done(err); - } - ProductCatalog.create(item2, bootstrap.defaultContext, function (err, item) { - if (err) { - return done(err); - } - ProductCatalog.create(item3, bootstrap.defaultContext, function (err, item) { - if (err) { - return done(err); - } - ProductCatalog.create(item11, bootstrap.defaultContext, function (err, item) { - if (err) { - return done(err); - } - ProductCatalog.create(item22, bootstrap.defaultContext, function (err, item) { - if (err) { - return done(err); - } - ProductCatalog.create(item33, bootstrap.defaultContext, function (err, item) { - done(err); - }); - }); - }); - }); - }); - }); - - - var ProductOwnerSchema = { - 'name': { - 'type': 'string', - 'required': true - }, - 'city': { - 'type': 'string', - 'required': true - } - }; - - var productOwnerOpts = { - strict: false, - base: 'BaseEntity', - plural: 'ProductOwners', - relations: { - "ProductCatalog": { - "type": "hasMany", - "model": "ProductCatalog" - } - } - }; - productOwnerUrl = bootstrap.basePath + '/' + productOwnerOpts.plural; - var ProductOwner = dataSource.createModel('ProductOwner', ProductOwnerSchema, productOwnerOpts); - ProductOwner.attachTo(dataSource); - ProductOwner.clientModelName = 'ProductOwner'; - app.model(ProductOwner); - - var owner1 = { - "name": "John", - "city": "Miami", - "id": 1 - }; - - var owner2 = { - "name": "Wick", - "city": "Texas", - "id": 2 - }; - - ProductOwner.create(owner1, bootstrap.defaultContext, function (err) { - if (err) { - return done(err); - } - ProductOwner.create(owner2, bootstrap.defaultContext, function (err) { - if (err) { - return done(err); - } - }); - }); - - } - - function createAccessToken(cb) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(err); - return cb(err); - } else { - accessToken = res.body.id; - return cb(); - } - }); - } - - before('setup test data', function (done) { - createAccessToken(function (err) { - if (err) return done(err); - createModelsAndPopulateData(done); - }); - }); - - // TODO: WARNING- this is important for clean consistent runs - after('clean up', function (done) { - loopback.Application.tenantds = {}; - done(); - }); - - - afterEach('destroy context', function (done) { - var callContext = { - ctx: { - 'device': ['android', 'ios'], - 'tenantId': tenantId - } - }; - models.PersonalizationRule.destroyAll({}, callContext, function (err, result) { - //console.log("Personalization Rule Model Removed : ", err, result); - done(); - }); - }); - - - it('t1 should replace field names in response when fieldReplace personalization is configured', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'name': 'product name', - 'desc': 'product description', - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('DEVICE', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - - // console.log('resp ---------->' + JSON.stringify(resp.body, null, 2)); - - var results = JSON.parse(resp.text); - - expect(results.length).to.be.equal(6); - expect(results[0]) - .to.include.keys('product name', 'product description'); - expect(results[0]) - .to.not.include.keys('name', 'desc'); - done(); - - }); - - }); - }); - - it('t2 should replace field values in response when fieldValueReplace personalization is configured', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldValueReplace': { - 'isAvailable': { - true: 1, - false: 0 - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('DEVICE', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - - // console.log('resp -------->' + JSON.stringify(resp.body, null, 2)); - - var results = JSON.parse(resp.text); - // console.log('resp --' + JSON.stringify(results, null, 2)); - expect(results.length).to.be.equal(6); - expect(results[0]).to.include.keys('isAvailable'); - expect(results[0].isAvailable).to.be.oneOf([0, 1]); - done(); - - }); - - }); - }); - - it('t3 should post results on the given URL when httpPostFunction is configured', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'httpPostFunction': { - 'url': 'http://localhost:1880/dumpResults', - 'async': true - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('DEVICE', 'android') - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - - // console.log('resp --' + JSON.stringify(resp, null, 2)); - - var results = JSON.parse(resp.text); - // console.log('resp --' + JSON.stringify(results, null, 2)); - expect(results.length).to.be.equal(6); - expect(results[0]).to.include.keys('name', 'desc', 'category', 'price', 'isAvailable', 'id'); - // expect(results[0].isAvailable).to.be.equal(1); - done(); - - }); - - }); - }); - - //sort test cases - it('t4 single sort condition: should return the sorted result when sort personalization rule is configured.', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'name': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('DEVICE', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = JSON.parse(resp.text); - - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('dinning table'); - done(); - }); - - }); - }); - - it('t5 single sort condition: should sort in ascending order when the sort order is not specified', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'name': '' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('dinning table'); - done(); - }); - - }); - }); - - it('t6 single sort condition: should accept the keywords like asc,ascending,desc or descending as sort order', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'name': 'descending' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('water heater'); - done(); - }); - - }); - }); - - it('t7 multiple sort condition: should return sorted result' + - ' when personalization rule with multiple sort is configured', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': [{ - 'category': 'asc' - }, { - 'name': 'desc' - }] - }, - 'scope': { - 'device': 'android' - } - }; - - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].category).to.be.equal('electronics'); - expect(results[0].name).to.be.equal('water heater'); - - done(); - }); - }); - }); - - it('t8 multiple sort condition: should omit the sort expression' + - ' whose order value(ASC|DSC) doesnt match the different cases', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': [{ - 'category': 'asc' - }, { - 'name': 'abcd' - }] - }, - 'scope': { - 'device': 'android' - } - }; - - /*sort order name is 'abcd' which doesnt ,match any of the cases hence - the sort expression will not be passed on to the query.*/ - - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].category).to.be.equal('electronics'); - expect(results[1].category).to.be.equal('electronics'); - expect(results[2].category).to.be.equal('electronics'); - expect(results[3].category).to.be.equal('furniture'); - expect(results[4].category).to.be.equal('furniture'); - expect(results[5].category).to.be.equal('furniture'); - done(); - }); - }); - }); - - it('t9 sort and filter combined: should return filterd and sorted result' + - ' when filter and sort personalization is configured', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'filter': { - 'category': 'furniture' - }, - 'sort': { - 'name': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(3); - expect(results[0].category).to.be.equal('furniture'); - expect(results[0].name).to.be.equal('dinning table'); - done(); - - }); - }); - }); - - it('t10 multiple sort: should handle duplicate sort expressions', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'name': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken + '&filter[order]=name ASC') - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android').expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('dinning table'); - done(); - - }); - }); - }); - - it('t11 multiple sort: should handle clashing sort expressions.(Eg:name ASC in personalization rule and' + - 'name DESC from API, in this case consider name DESC from API)', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'name': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken + '&filter[order]=name DESC') - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android').expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('water heater'); - done(); - - }); - }); - }); - - xit('t12 sort: should handle nested sorting', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'sort': { - 'price|value': 'asc' - } - }, - 'scope': { - 'device': 'android' - } - }; - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android').expect(200).end(function (err, resp) { - if (err) { - done(err); - } - console.log("==============", resp.body); - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].name).to.be.equal('office chair'); - expect(results[0].price.value).to.be.equal(5000); - done(); - - }); - }); - }); - - //Mask Test Cases - it('t13 Mask:should mask the given fields and not send them to the response', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'mask': { - 'category': true - } - }, - 'scope': { - 'device': 'android' - } - }; - - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - - api.get(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].category).to.be.equal(undefined); - done(); - }); - - }); - }); - - it('t14 Mask:should mask the given fields and not send them to the response', - function (done) { - - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'mask': { - 'category': true - } - }, - 'scope': { - 'device': 'android' - } - }; - - - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - - - api.get(productCatalogUrl + '?access_token=' + accessToken + '&filter[fields][name]=true') - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - var results = JSON.parse(resp.text); - expect(results).to.be.instanceof(Array); - expect(results.length).to.equal(6); - expect(results[0].desc).to.be.equal(undefined); - expect(results[0].category).to.be.equal(undefined); - // expect(results[0].name).to.be.equal('king size bed'); - done(); - }); - - }); - }); - //reverse service personalization tests - - it('t15 should replace field names while posting when fieldReplace personalization is configured', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'name': 'product name', - 'desc': 'product description' - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - var postData = { - 'product name': 'o1ven', - 'product description': 'o1ven', - 'category': 'electronics', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var results = JSON.parse(resp.text); - expect(results) - .to.include.keys('product name', 'product description'); - done(); - } - }); - - }); - }); - - it('t16 should replace field value names while posting when fieldValueReplace personalization is configured', - function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldValueReplace': { - 'name': { - 'oven': 'new oven' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - models.PersonalizationRule.create(ruleForAndroid, bootstrap.defaultContext, function (err, rule) { - - if (err) { - throw new Error(err); - } - //var ruleId = rule.id; - - var postData = { - 'name': 'new oven', - 'desc': 'oven', - 'category': 'electronics', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var results = JSON.parse(resp.text); - expect(results.name).to.be.equal('new oven'); - done(); - } - }); - - }); - }); - - - // Test case when personalization rules are there for multiple scopes. - - it('t17 should replace field names and field value names when scope of personalization rule matches', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'name': 'product_name_android', - 'desc': 'product_description_android' - }, - 'fieldValueReplace': { - 'name': { - 'oven': 'new_oven_android' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - var ruleForIos = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'name': 'product_name_ios', - 'desc': 'product_description_ios' - }, - 'fieldValueReplace': { - 'name': { - 'oven': 'new_oven_ios' - } - } - }, - 'scope': { - 'device': 'ios' - } - }; - - var personalizationRuleArray = [ruleForAndroid, ruleForIos]; - - models.PersonalizationRule.create(personalizationRuleArray, bootstrap.defaultContext, function (err, rules) { - - if (err) { - throw new Error(err); - } - - var postData = { - 'name': 'oven', - 'desc': 'oven', - 'category': 'electronics', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'ios') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - - var results = JSON.parse(resp.text); - expect(results) - .to.include.keys('product_name_ios', 'product_description_ios'); - expect(results.product_name_ios).to.be.equal('new_oven_ios'); - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - - var results = JSON.parse(resp.text); - expect(results) - .to.include.keys('product_name_android', 'product_description_android'); - expect(results.product_name_android).to.be.equal('new_oven_android'); - done(); - }); - }); - - - - }); - }); - - //Nested input values - it('t18 (Nested input) should replace field names and field value names when scope of personalization rule matches while posting', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'price\uFF0Ecurrency': 'price_currency', - 'name': 'product_name_android', - 'desc': 'product_description_android' - }, - 'fieldValueReplace': { - 'name': { - 'oven': 'new_oven_android' - }, - 'price\uFF0Ecurrency': { - 'inr': 'IndianRupee' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - var personalizationRule = ruleForAndroid; - - models.PersonalizationRule.create(personalizationRule, bootstrap.defaultContext, function (err, rules) { - - var postData = { - 'name': 'oven', - 'desc': 'oven', - 'category': 'electronics', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - 'isAvailable': true, - 'id': '9898' - }; - - if (err) { - throw new Error(err); - } - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - throw new Error(err); - } - - var results = JSON.parse(resp.text); - - expect(results.price).keys('price_currency', 'value'); - expect(results).to.include.keys('product_name_android', 'product_description_android'); - expect(results.product_name_android).to.be.equal('new_oven_android'); - expect(results.price.price_currency).to.be.equal('IndianRupee'); - done(); - }); - }); - }); - - - it('t19 (Nested input) should replace field names and field value names when scope of personalization rule matches while getting', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'price\uFF0Ecurrency': 'price_currency', - 'name': 'product_name_android', - 'desc': 'product_description_android' - }, - 'fieldValueReplace': { - 'name': { - 'oven': 'new_oven_android' - }, - 'price\uFF0Ecurrency': { - 'inr': 'IndianRupee' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - var personalizationRule = ruleForAndroid; - - models.PersonalizationRule.create(personalizationRule, bootstrap.defaultContext, function (err, rules) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken).set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - var results = JSON.parse(resp.text); - var result = results.filter(function (obj) { - if (obj.id === '9898') { - return true; - } else { - return false; - } - }); - expect(result[0].price).keys('price_currency', 'value'); - expect(result[0]).to.include.keys('product_name_android', 'product_description_android'); - expect(result[0].product_name_android).to.be.equal('new_oven_android'); - expect(result[0].price.price_currency).to.be.equal('IndianRupee'); - done(); - - }); - }); - }); - - - it('t20 (Nested input) should not replace field names and field value names when scope of personalization rule not matches while getting the data', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldReplace': { - 'price\uFF0Ecurrency': 'price_currency', - 'name': 'product_name_android', - 'desc': 'product_description_android' - }, - 'fieldValueReplace': { - 'name': { - 'oven': 'new_oven_android' - }, - 'price\uFF0Ecurrency': { - 'inr': 'IndianRupee' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - var personalizationRule = ruleForAndroid; - - models.PersonalizationRule.create(personalizationRule, bootstrap.defaultContext, function (err, rules) { - - if (err) { - throw new Error(err); - } - - api.get(productCatalogUrl + '?access_token=' + accessToken).set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'ios') - .expect(200).end(function (err, resp) { - var results = JSON.parse(resp.text); - var result = results.filter(function (obj) { - if (obj.id === '9898') { - return true; - } else { - return false; - } - }); - expect(result[0].price).keys('currency', 'value'); - expect(result[0]).to.include.keys('category', 'price', 'isAvailable', 'id', 'name', 'desc'); - expect(result[0].name).to.be.equal('oven'); - expect(result[0].price.currency).to.be.equal('inr'); - done(); - - }); - }); - }); - - it('t21 should give filterd result when lbFilter is applied', function (done) { - // Setup personalization rule - var ruleForAndroid = { - 'modelName': 'ProductOwner', - 'personalizationRule': { - 'lbFilter': { - 'include': 'ProductCatalog', - 'where': { - 'name': 'John' - } - } - }, - 'scope': { - 'device': 'android' - } - }; - - var personalizationRule = ruleForAndroid; - - models.PersonalizationRule.create(personalizationRule, bootstrap.defaultContext, function (err, rules) { - if (err) { - throw new Error(err); - } - api.get(productOwnerUrl + '?access_token=' + accessToken).set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'android') - .expect(200).end(function (err, resp) { - var results = JSON.parse(resp.text); - expect(results).to.have.length(1); - expect(results[0]).to.include.keys('ProductCatalog'); - expect(results[0].ProductCatalog).to.have.length(1); - done(); - - }); - }); - - }); - - it('t22 should replace field value names array datatype while posting when fieldValueReplace personalization is configured', function (done) { - // Setup personalization rule - var ruleForMobile = { - 'modelName': 'ProductCatalog', - 'personalizationRule': { - 'fieldValueReplace': { - 'keywords': { - 'Alpha': 'A', - "Bravo": 'B' - } - } - }, - 'scope': { - 'device': 'mobile' - } - }; - - models.PersonalizationRule.create(ruleForMobile, bootstrap.defaultContext, function (err, rule) { - if (err) { - done(err); - } else { - var postData = { - 'name': 'Smart Watch', - 'desc': 'Smart watch with activity tracker', - 'category': 'electronics', - 'price': { - 'value': 5000, - 'currency': 'inr' - }, - "keywords": ["Alpha", "Bravo", "Charlie", "Delta"], - 'isAvailable': true, - 'id': 'watch1' - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('device', 'mobile') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var result = resp.body; - expect(result.name).to.be.equal('Smart Watch'); - expect(result.keywords).to.be.an('array'); - expect(result.keywords).to.have.length(4); - expect(result.keywords).to.include.members(['A', 'B', 'Charlie', 'Delta']); - done(); - } - }); - } - }); - }); - - it('t23 should replace field values on array datatype in response when fieldValueReplace personalization is configured ', function (done) { - // Setup personalization rule - var productWithId = productCatalogUrl + '/watch1'; - api.get(productWithId + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('DEVICE', 'mobile') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var result = resp.body; - expect(result.name).to.be.equal('Smart Watch'); - expect(result.keywords).to.be.an('array'); - expect(result.keywords).to.have.length(4); - expect(result.keywords).to.include.members(['A', 'B', 'Charlie', 'Delta']); - done(); - }); - }); - - it('t24 should be able to create a fieldMask personalization rule, post data and get response in specific format', function (done) { - // Setup personalization rule - var ruleForMobile = { - "modelName": "ProductCatalog", - "personalizationRule": { - "fieldMask": { - "modelNo": { - "pattern": "([0-9]{3})([0-9]{3})([0-9]{4})", - "maskCharacter": "X", - "format": "($1) $2-$3", - "mask": ['$3'] - } - } - }, - "scope": { - "region": "us" - } - }; - - models.PersonalizationRule.create(ruleForMobile, bootstrap.defaultContext, function (err, rule) { - if (err) { - done(err); - } else { - var postData = { - 'name': 'Omnitrix', - 'desc': 'Alien tech smart watch allows to transform into .... nah nothing, just a smart watch', - 'category': 'electronics', - 'price': { - 'value': 89000, - 'currency': 'inr' - }, - "keywords": ["Alpha", "Bravo"], - 'isAvailable': true, - 'id': 'watch2', - "modelNo": "1233567891" - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'us') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('(123) 356-XXXX'); - done(); - } - }); - } - }); - }); - - it('t25 should get result in specific format on get when fieldMask personalization rule is applied', function (done) { - // Setup personalization rule - var productWithId = productCatalogUrl + '/watch2'; - api.get(productWithId + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'us') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('(123) 356-XXXX'); - done(); - }); - }); - - it('t26 should be able to create a fieldMask personalization rule, post data and get response in specific format', function (done) { - // Setup personalization rule - var ruleForMobile = { - "modelName": "ProductCatalog", - "personalizationRule": { - "fieldMask": { - "modelNo": { - "pattern": "([0-9]{5})([0-9]{1})([0-9]{4})", - "maskCharacter": "-", - "format": "+91 $1 $2$3", - "mask": ['$3'] - } - } - }, - "scope": { - "region": "in" - } - }; - - models.PersonalizationRule.create(ruleForMobile, bootstrap.defaultContext, function (err, rule) { - if (err) { - done(err); - } else { - var postData = { - 'name': 'MultiTrix', - 'desc': 'There is no such smart watch', - 'category': 'electronics', - 'price': { - 'value': 23400, - 'currency': 'inr' - }, - "keywords": ["Charlie", "India"], - 'isAvailable': true, - 'id': 'watch3', - "modelNo": "9080706050" - }; - - api.post(productCatalogUrl + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'in') - .send(postData) - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('+91 90807 0----'); - done(); - } - }); - } - }); - }); - - it('t27 should get result in specific format on get when fieldMask personalization rule is applied', function (done) { - // Setup personalization rule - var productWithId = productCatalogUrl + '/watch3'; - api.get(productWithId + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'in') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('+91 90807 0----'); - done(); - }); - }); - - it('t28 should get result in specific format on get when fieldMask personalization rule is applied no masking', function (done) { - // Setup personalization rule - var ruleForMobile = { - "modelName": "ProductCatalog", - "personalizationRule": { - "fieldMask": { - "modelNo": { - "pattern": "([0-9]{5})([0-9]{1})([0-9]{4})", - "maskCharacter": "X", - "format": "+91 $1 $2$3" - } - } - }, - "scope": { - "region": "ka" - } - }; - - models.PersonalizationRule.create(ruleForMobile, bootstrap.defaultContext, function (err, rule) { - if (err) { - done(err); - } else { - var productWithId = productCatalogUrl + '/watch3'; - api.get(productWithId + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'ka') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('+91 90807 06050'); - done(); - } - }); - } - }); - - }); - - it('t29 should get result on get when fieldMask personalization rule is applied and no format is given', function (done) { - // Setup personalization rule - var ruleForMobile = { - "modelName": "ProductCatalog", - "personalizationRule": { - "fieldMask": { - "modelNo": { - "pattern": "([0-9]{5})([0-9]{1})([0-9]{4})", - "maskCharacter": "X", - "mask": ['$3'] - } - } - }, - "scope": { - "region": "kl" - } - }; - - models.PersonalizationRule.create(ruleForMobile, bootstrap.defaultContext, function (err, rule) { - if (err) { - done(err); - } else { - var productWithId = productCatalogUrl + '/watch3'; - api.get(productWithId + '?access_token=' + accessToken) - .set('Accept', 'application/json') - .set('TENANT_ID', tenantId) - .set('REMOTE_USER', 'testUser') - .set('region', 'kl') - .expect(200).end(function (err, resp) { - if (err) { - done(err); - } else { - var result = resp.body; - expect(result).not.to.be.null; - expect(result).not.to.be.empty; - expect(result).not.to.be.undefined; - expect(result.modelNo).to.be.equal('908070XXXX'); - done(); - } - }); - } - }); - - }); - -}); - -//1. sort -//2. union -//3. mask -//4. filter -//5. fieldValueReplace -//6. fieldDataReplace - - -//ModelDefinition.create({ -//'name': 'productCatalog', -//'properties': { -//'name': 'string', -//'category': 'string', -//'desc': 'string', -//'price': 'price', -//'isAvailable': 'boolean' -//} -//}); - -//ModelDefinition.create({ -//'name': 'productCatalogNonPreferred', -//'properties': { -//'name2': 'string', -//'category2': 'string', -//'desc2': 'string', -//'price2': 'price', -//'isAvailable': 'boolean' -//} -//}); - -//ModelDefinition.create({ -//'name': 'personalizationRules', -//'properties': { -//'modelName': 'string', -//'personalizationRule': 'personaliztionRule', -//'scope': 'scope' -//} -//}); - -//var ruleForAndroid: { -//'modelId': 'productCatalog', -//'personalizationRule': { -//'sort': { -//'name': 'desc' -//} -//'filter': { -//'category': 'furniture' -//} -//'mask': { -//'desc': true -//} -//} -//'scope': { -//'device': 'android' -//} -//} -//var ruleForTenantDealer: { -//'modelId': 'productCatalog', -//'personalizationRule': { -//'union': { -//'model': 'productCatalogNonPreferred', -//'fieldMap': { -//'name': 'name2', -//'category': 'category2', -//'price': 'price2', -//'desc': 'desc2' -//} -//} -//'fieldValueReplace': { -//'isAvailable': { -//'true': '1', -//'false': '0' -//} -//}, -//'fieldNameReplace': { -//'desc': 'Description', -//'name': 'Name of product' -//} -//}, -//'scope': { -//'tenantId': 'dealer' -//} -//} - -//personalizationRules.create(ruleForAndroid); personalizationRules.create(ruleForTenantDealer); - - - -//var item11 = { -//'name': 'refrigerator', -//'category': 'electronics', -//'desc': 'refrigerator', -//'price': { -//'value': 10000, -//'currency': 'inr' -//}, -//'isAvailable': true -//}; -//var item22 = { -//'name': 'water heater', -//'category': 'electronics', -//'desc': 'water heater', -//'price': { -//'value': 5000, -//'currency': 'inr' -//}, -//'isAvailable': true -//}; -//var item33 = { -//'name': 'oven', -//'category': 'electronics', -//'desc': 'oven', -//'price': { -//'value': 8000, -//'currency': 'inr' -//}, -//'isAvailable': false -//}; - -//productCatalog.create(item1); productCatalog.create(item2); productCatalog.create(item3); -//productCatalog.create(item11); productCatalog.create(item22); productCatalog.create(item33); - - -//it('should apply personalization rule for android') { -//expect('sorted by desc in descending order'); -//expect('filtered for category furniture'); -//expect('desc field does not appear in the response'); -//} - -//if ('should apply personalization rule for tenant=dealer') { -//expect('number of records due to union = 6'); -//expect('isAvailable is returned as 1 or 0'); -//expect('desc field name is returned as description'); -//expect('name field name is retuned as \'product name\''); -//} -//} diff --git a/test/soft-delete-mixin-test.js b/test/soft-delete-mixin-test.js deleted file mode 100644 index 3436f57..0000000 --- a/test/soft-delete-mixin-test.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * 1) In before function a. Create in memory dataSource b. Create model c. - * Attach model to datasource d. Pass model to audit-fileds-mixins to add the - * mixins properties to the model e. Attach model to the app - * - * 2) Test case 1 -- see if the model is successfully created a. Check if all - * the soft-delete-mixins are present - * - * 3) Test case 2 --- add data to the model a. Add new data b. Check if - * soft-delete-mixins are not null and undefined c. Check if _isDeleted is set - * to false - * - * 4) Test case 3 --- add data to the model, delete the same using destroyById - * a. Add new data b. Check if soft-delete-mixins are not null and undefined c. - * Check if _isDeleted is set to false d. Delete the record using destroyById. - * e. find the same record _isDeleted should be True - * - * 5) Test case 4 --- add data to the model, delete the same using destroyAll a. - * Add new data b. Check if soft-delete-mixins are not null and undefined c. - * Check if _isDeleted is set to false d. Delete the record using destroyAll. e. - * find the all record _isDeleted should be True, for all the records. - * - * 6) In after function a. Delete Data source b. Delete Model - * - * @author sivankar jain - */ - -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var app = bootstrap.app; -var models = bootstrap.models; -//var loopback = require ('loopback'); -//var debug = require('debug')('soft-delete-mixin-test'); - -var uuidv4 = require('uuid/v4'); -var chalk = require('chalk'); -var softDeleteMixin = require('../common/mixins/soft-delete-mixin.js'); - - -describe(chalk.blue('soft-delete-mixin tests Programmatically'), function () { - this.timeout(20000); - var testDatasourceName = uuidv4(); - var modelName = 'TestModel'; - - var TestModelSchema = { - 'name': { - 'type': 'string', - 'required': true, - 'unique': true - } - }; - var opts = { - base: 'BaseEntity', - mixins: { - SoftDeleteMixin: true, - ModelMixin: false, - VersionMixin: true - } - }; - - before('create test model', function (done) { - - var dataSource = app.dataSources['db']; - - var TestModel = dataSource.createModel(modelName, TestModelSchema, opts); - TestModel.attachTo(dataSource); - softDeleteMixin(TestModel); - app.model(TestModel, { - dataSource: 'db' - }); - done(); - }); - - after('delete model clear in memory', function (done) { - - // clearing data from TestModel - delete models[modelName]; - delete app.dataSource[testDatasourceName]; - done(); - }); - - it('Should create TestModel with SoftDeleteMixins SET ', function (done) { - expect(models[modelName]).not.to.be.null; - expect(models[modelName].definition.properties).not.to.be.undefined; - expect(Object.keys(models[modelName].definition.properties)).to.include.members(Object.keys(TestModelSchema)); - expect(Object.keys(models[modelName].definition.properties)).to.include.members(['_isDeleted']); - expect(Object.keys(models[modelName].settings)).to.include.members(Object.keys(opts)); - expect(Object.keys(models[modelName].settings.mixins)).to.include.members(Object.keys(opts.mixins)); - done(); - }); - - it('Should insert data to the TestModel and see if _isDelete is present and set to false ', function (done) { - var postData = { - 'name': 'TestCaseOne', - '_version': uuidv4() - }; - models[modelName].create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res.name).to.be.equal(postData.name); - expect(res['_isDeleted']).to.be.false; - done(); - } - }); - }); - - it('Should delete record from TestModel using destroyById and find the same record ,should not return the record ', function (done) { - var postData = { - 'name': 'TestCaseTwo', - '_version': uuidv4() - }; - models[modelName].create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - models[modelName].destroyById(res.id, bootstrap.defaultContext, function (err) { - if (err) { - done(err); - } else { - models[modelName].findById(res.id, bootstrap.defaultContext, function (err, record) { - if (err) { - done(err); - } else { - expect(record).to.be.null; - done(); - } - }); - } - }); - } - }); - }); - - xit('Should find with IncludeMixin.softDelete = true, all records should be return ', function (done) { - - var postData = { - 'name': 'TestCaseThree', - '_version': uuidv4() - }; - models[modelName].create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - models[modelName].destroyById(res.id, bootstrap.defaultContext, function (err) { - if (err) { - done(err); - } else { - models[modelName].find({ - includeMixin: { - softDelete: true - } - }, bootstrap.defaultContext, function (err, record) { - if (err) { - done(err); - } else { - expect(record).to.have.length(4); - expect(record[0]._isDeleted).to.be.false; - expect(record[1]._isDeleted).to.be.true; - expect(record[2]._isDeleted).to.be.true; - done(); - } - }); - } - }); - } - }); - }); - it('Should delete record from TestModel using destroyAll, on find nothing should be return ', function (done) { - models[modelName].destroyAll({}, bootstrap.defaultContext, function (err) { - if (err) { - done(err); - } else { - models[modelName].find({}, bootstrap.defaultContext, function (err, record) { - if (err) { - done(err); - } else { - expect(record).to.be.empty; - done(); - } - }); - } - }); - }); -}); diff --git a/test/switch-data-source-test.js b/test/switch-data-source-test.js deleted file mode 100644 index 5cbb980..0000000 --- a/test/switch-data-source-test.js +++ /dev/null @@ -1,439 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var async = require('async') -var loopback = require('loopback'); -var log = require('oe-logger')('switch-data-source-test'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var events = require('events'); -var eventEmitter = new events.EventEmitter(); -var mongoHost = process.env.MONGO_HOST || 'localhost'; - -describe(chalk.blue('switch-data-source-test'), function () { - - this.timeout(60000); - - var models = [ - { - name: 'model1', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }, - { - name: 'model2', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }, - { - name: 'model3', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }, - { - name: 'model4', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }, - { - name: 'model5', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'db' - }, - { - name: 'model9', - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - } - }, - dataSourceName: 'emailDs' - } - - ]; - - var datasources = [ - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/tenant1a', - 'database': 'tenant1a', - 'password': 'admin', - 'name': 'tenant1a', - 'connector': 'mongodb', - 'user': 'admin', - 'id': 'ds-tenant1a', - 'description': 'tenant1a', - 'connectionTimeout': 50000 - }, - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/tenant2a', - 'database': 'tenant2a', - 'password': 'admin', - 'name': 'tenant2a', - 'connector': 'mongodb', - 'user': 'admin', - 'id': 'ds-tenant2a', - 'description': 'tenant2a', - 'connectionTimeout': 50000 - }, - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/commondb', - 'database': 'commondb', - 'password': 'commondb', - 'name': 'commondb', - 'connector': 'mongodb', - 'user': 'admin', - 'description': 'accountsModule', - 'id': 'ds-commondb', - 'connectionTimeout': 50000 - }, - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/fxdb', - 'database': 'fxdb', - 'name': 'fxdb', - 'connector': 'mongodb', - 'user': 'admin', - 'description': 'fxdb', - 'id': 'ds-fxdb', - 'connectionTimeout': 50000 - }, - { - 'host': mongoHost, - 'port': 27017, - 'url': 'mongodb://' + mongoHost + ':27017/superdb', - 'database': 'superdb', - 'name': 'superdb', - 'connector': 'mongodb', - 'user': 'admin', - 'description': 'fxdb', - 'id': 'ds-superdb', - 'connectionTimeout': 50000 - } - - ]; - - var tenant1Scope = { - ignoreAutoScope: false, - ctx: { - tenantId: 'tenant1' - } - }; - - var tenant2Scope = { - ignoreAutoScope: false, - ctx: { - tenantId: 'tenant2' - } - }; - - var mappingsForTenant1 = [ - { - modelName: 'model1', - dataSourceName: 'tenant1a', - }, - { - modelName: 'model2', - dataSourceName: 'commondb', - }, - { - modelName: 'model3', - dataSourceName: 'fxdb', - 'scope': { - 'department': 'fx' - } - }, - { - modelName: 'model1', - dataSourceName: 'superdb', - priority: 10, - 'scope': { - 'superpower': true - } - }, - ]; - - var mappingsForTenant2 = [ - { - modelName: 'model1', - dataSourceName: 'tenant2a', - }, - { - modelName: 'model2', - dataSourceName: 'commondb', - }, - { - modelName: 'model3', - dataSourceName: 'fxdb', - 'scope': { - 'department': 'fx' - } - }, - { - modelName: 'model1', - dataSourceName: 'superdb', - priority: 10, - 'scope': { - 'superpower': true - } - }, - ]; - - var ModelDefinition = bootstrap.models.ModelDefinition; - var DataSourceDefinition = bootstrap.models.DataSourceDefinition; - var DataSourceMapping = bootstrap.models.DataSourceMapping; - - var cleanup = function (done) { - async.series([function (cb) { - var model = bootstrap.models['DataSourceDefinition']; - if (model) { - var options = { ctx: {} }; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - model.remove({}, options, function () { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - var model = bootstrap.models['DataSourceMapping']; - if (model) { - model.destroyAll({}, tenant1Scope, function () { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - var model = bootstrap.models['DataSourceMapping']; - if (model) { - model.destroyAll({}, tenant2Scope, function () { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - - var options = { ctx: {} }; - options.fetchAllScopes = true; - ModelDefinition.remove({ - 'where': { - 'name': { - inq: ['model1', 'model2', 'model3', 'model4', 'model5'] - } - } - }, options, function () { - cb(); - }); - }, function () { - done(); - }]); - }; - - before('setup datasources', function (done) { - - eventEmitter.setMaxListeners(100); - - var callContext = bootstrap.defaultContext; - callContext.ignoreAutoScope = true; - - async.series([function (cb) { - cleanup(cb); - }, - function (cb) { - async.each(datasources, function (ds, callback) { - DataSourceDefinition.findById(ds.id, callContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), 'error in datasource find', err); - return callback(err); - } - if (!res) { - DataSourceDefinition.create(ds, callContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), 'error in datasource find', err); - return callback(err); - } - callback(); - }); - } else { - log.debug(log.defaultContext(), 'data source exists ', ds.name, ds.database); - callback(); - } - }); - }, function (err) { - cb(); - }); - }, - function (cb) { - Object.keys(bootstrap.app.datasources).forEach(function (dsname) { - log.debug(log.defaultContext(), dsname); - }); - ModelDefinition.create(models, bootstrap.defaultContext, function (err, res) { - if (err) { - log.debug(log.defaultContext(), 'unable to create model'); - cb(); - } else { - cb(); - } - }); - }, - function (cb) { - DataSourceMapping.create(mappingsForTenant1, tenant1Scope, function (err, res) { - if (err) { - cb(err); - } else { - cb(); - } - }); - }, - function (cb) { - DataSourceMapping.create(mappingsForTenant2, tenant2Scope, function (err, res) { - if (err) { - cb(err); - } else { - cb(); - } - }); - }, - function (cb) { - Object.keys(bootstrap.app.datasources).forEach(function iter(id) { - log.debug(log.defaultContext(), id, bootstrap.app.datasources[id].settings); - }); - cb(); - }, - function () { - done(); - }]); - }); - - it('tenant1 and model 1 ', function (done) { - var model = loopback.getModel('model1', bootstrap.defaultContext); - var ds = model.getDataSource(tenant1Scope); - expect(ds).not.to.be.null; - expect(ds.settings.database).to.equal('tenant1a'); - done(); - }); - - it('tenant2 and model 1 ', function (done) { - var model = loopback.getModel('model1', bootstrap.defaultContext); - var ds = model.getDataSource(tenant2Scope); - expect(ds).not.to.be.null; - expect(ds.settings.database).to.equal('tenant2a'); - - done(); - }); - - it('model2 tenant1 commondb ', function (done) { - var model = loopback.getModel('model2', bootstrap.defaultContext); - var ds = model.getDataSource(tenant1Scope); - expect(ds.settings.database).to.equal('commondb'); - - done(); - }); - - it('model2 tenant2 commondb ', function (done) { - var model = loopback.getModel('model2', bootstrap.defaultContext); - var ds = model.getDataSource(tenant2Scope); - expect(ds.settings.database).to.equal('commondb'); - - done(); - }); - - it('department for tenant 1 ', function (done) { - var callContext = {}; - callContext.ctx = { - tenantId: 'tenant1', - department: 'fx' - }; - var model = loopback.getModel('model3', bootstrap.defaultContext); - var ds = model.getDataSource(callContext); - expect(ds.settings.database).to.equal('fxdb'); - - done(); - }); - - it('superdb higher priority ', function (done) { - var callContext = {}; - - callContext.ctx = { - superpower: true, - tenantId: 'tenant1', - department: 'fx' - }; - - var model = loopback.getModel('model1', bootstrap.defaultContext); - var ds = model.getDataSource(callContext); - expect(ds.settings.database).to.equal('superdb'); - - done(); - }); - - after('after clean up', function (done) { - cleanup(function () { - done(); - }); - }); - -}); diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..f6e463e --- /dev/null +++ b/test/test.js @@ -0,0 +1,648 @@ +/** + * + * �2018-2019 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), + * Bangalore, India. All Rights Reserved. + * + */ + +const _require = require; +require = function (a) { + if (a === 'oe-cloud') { + return _require('../index.js'); + } + return _require(a); +}; + +var oecloud = require('oe-cloud'); +var loopback = require('loopback'); + +const path = require('path'); + +oecloud.observe('loaded', function (ctx, next) { + if (!oecloud.options.baseEntitySources) { + oecloud.options.baseEntitySources = [process.cwd() + '/test/common/models/base-entity-test.js']; + } + oecloud.attachMixinsToModelDefinition('NewMixin'); + oecloud.attachMixinsToBaseEntity('TestMixin'); + oecloud.addSettingsToBaseEntity({ mysettings: true }); + oecloud.addSettingsToModelDefinition({ xsettings: true }); + return next(); +}); + +oecloud.boot(__dirname, function (err) { + if (err) { + console.log(err); + process.exit(1); + } + oecloud.start(); + oecloud.emit('test-start'); +}); + + +var chalk = require('chalk'); +var chai = require('chai'); +var async = require('async'); +chai.use(require('chai-things')); + +var expect = chai.expect; + +var app = oecloud; +var defaults = require('superagent-defaults'); +var supertest = require('supertest'); +var api = defaults(supertest(app)); +var basePath = app.get('restApiRoot'); +var url = basePath + '/Employees'; + +var models = oecloud.models; + + +function deleteAllUsers(done) { + var userModel = loopback.findModel('User'); + userModel.destroyAll({}, {}, function (err) { + if (err) { + return done(err); + } + userModel.find({}, {}, function (err2, r2) { + if (err2) { + return done(err2); + } + if (r2 && r2.length > 0) { + return done(new Error('Error : users were not deleted')); + } + }); + return done(err); + }); +} + +var globalCtx = { + ignoreAutoScope: true, + ctx: { tenantId: '/default'} +}; +var defaultContext = { + ctx: { tenantId: '/default' } +}; + + +describe(chalk.blue('oeCloud Test Started'), function (done) { + this.timeout(10000); + + before('wait for boot scripts to complete', function (done) { + app.on('test-start', function () { + deleteAllUsers(function () { + return done(); + }); + }); + }); + + afterEach('destroy context', function (done) { + done(); + }); + + it('t1-1 create user admin/admin with /default tenant', function (done) { + //app.removeForceId('User'); + //app.removeForceId('Role'); + //app.removeForceId('RoleMapping'); + var userModel = loopback.findModel('User'); + //userModel.settings.forceId = false; + //var validations = userModel.validations; + //if (validations.id && validations.id[0].validation === 'absence' && validations.id[0].if === 'isNewRecord' ) { + // validations.id.shift(); + //} + var url = basePath + '/users'; + api.set('Accept', 'application/json') + .post(url) + .send([{ id: 'admin', username: 'admin', password: 'admin', email: 'admin@admin.com' }, + {id: 'evuser', username: 'evuser', password: 'evuser', email: 'evuser@evuser.com' }, + {id: 'infyuser', username: 'infyuser', password: 'infyuser', email: 'infyuser@infyuser.com' }, + { id: 'bpouser', username: 'bpouser', password: 'bpouser', email: 'bpouser@bpouser.com' }, + { id: 'iciciuser', username: 'iciciuser', password: 'iciciuser', email: 'iciciuser@iciciuser.com' }, + { id: 'citiuser', username: 'citiuser', password: 'citiuser', email: 'citiuser@citiuser.com' } + ]) + .end(function (err, response) { + var result = response.body; + expect(result[0].id).to.be.defined; + expect(result[1].id).to.be.defined; + expect(result[2].id).to.be.defined; + expect(result[3].id).to.be.defined; + expect(result[4].id).to.be.defined; + expect(result[5].id).to.be.defined; + done(err); + }); + }); + + it('t1-2 create roles', function (done) { + var roleModel = loopback.findModel('Role'); + //roleModel.settings.forceId = false; + //var validations = roleModel.validations; + //if (validations.id && validations.id[0].validation === 'absence' && validations.id[0].if === 'isNewRecord' ) { + // validations.id.shift(); + //} + roleModel.create([ + {id: 'admin', name: 'admin'}, + {id: 'businessUser', name: 'businessUser'}, + {id: 'guest', name: 'guest'}, + {id: 'poweruser', name: 'poweruser'} + ], function (err, result) { + return done(err); + }); + }); + + it('t1-3 create user role mapping', function (done) { + var roleMapping = loopback.findModel('RoleMapping'); + //var validations = roleMapping.validations; + //if (validations.id && validations.id[0].validation === 'absence' && validations.id[0].if === 'isNewRecord' ) { + // validations.id.shift(); + //} + //roleMapping.settings.forceId = false; + roleMapping.create([ + {id: 'adminuser', principalType: roleMapping.USER, principalId: 'admin', roleId: 'admin'}, + {id: 'businessUser', principalType: roleMapping.USER, principalId: 'infyuser', roleId: 'businessUser'}, + {id: 'poweruser', principalType: roleMapping.USER, principalId: 'infyuser', roleId: 'poweruser'}, + {id: 'guestuser', principalType: roleMapping.USER, principalId: 'evuser', roleId: 'guest' } + ], function (err, result) { + return done(err); + }); + }); + + var adminToken; + it('t2 Login with admin credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'admin', password: 'admin' }) + .end(function (err, response) { + var result = response.body; + adminToken = result.id; + expect(adminToken).to.be.defined; + done(); + }); + }); + + + var infyToken; + it('t3 Login with infy credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'infyuser', password: 'infyuser' }) + .end(function (err, response) { + var result = response.body; + infyToken = result.id; + expect(infyToken).to.be.defined; + done(); + }); + }); + + var evToken; + it('t4 Login with ev credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'evuser', password: 'evuser' }) + .end(function (err, response) { + var result = response.body; + evToken = result.id; + expect(evToken).to.be.defined; + done(); + }); + }); + + + var bpoToken; + it('t5 Login with bpo credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'bpouser', password: 'bpouser' }) + .end(function (err, response) { + var result = response.body; + bpoToken = result.id; + expect(bpoToken).to.be.defined; + done(); + }); + }); + + + var icicitoken; + it('t5 Login with bpo credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'iciciuser', password: 'iciciuser' }) + .end(function (err, response) { + var result = response.body; + icicitoken = result.id; + expect(bpoToken).to.be.defined; + done(); + }); + }); + + + var cititoken; + it('t5 Login with bpo credentials', function (done) { + var url = basePath + '/users/login'; + api.set('Accept', 'application/json') + .post(url) + .send({ username: 'citiuser', password: 'citiuser' }) + .end(function (err, response) { + var result = response.body; + cititoken = result.id; + expect(bpoToken).to.be.defined; + done(); + }); + }); + + it('t6 Create Model dynamically', function (done) { + var m = { + name: 'NewCustomer', + base: 'BaseEntity', + properties: { + name: { + type: 'string' + }, + age: { + type: 'number' + }, + officeEmail: { + type: 'email' + } + } + }; + + var url = basePath + '/ModelDefinitions?access_token=' + adminToken; + api.set('Accept', 'application/json') + .post(url) + .send(m) + .end(function (err, response) { + console.log(err); + var result = response.body; + + expect(result.name).to.be.equal('NewCustomer'); + done(); + }); + }); + + function newCustomerListener(ctx, next) { + console.log('Before save called'); + ctx.instance.name = ctx.instance.name + 'XXXXX'; + return next(); + } + + it('t7 hooking observer', function (done) { + var modelDefinition = loopback.findModel('ModelDefinition'); + modelDefinition.find({ + where: {name: 'NewCustomer'} + }, {}, function (err, results) { + var item = results[0]; + expect(item.name).to.be.equal('NewCustomer'); + var newCustomerModel = loopback.findModel('NewCustomer'); + newCustomerModel.evObserve('before save', newCustomerListener); + + newCustomerModel.create({ name: 'x' }, {}, function (err, results) { + if (err) { + return done(err); + } + expect(results.name).to.be.equal('xXXXXX'); + return done(); + }); + }); + }); + + it('t8 removing observer', function (done) { + var newCustomerModel = loopback.findModel('NewCustomer'); + newCustomerModel.evRemoveObserver('before save', newCustomerListener); + + newCustomerModel.create({ name: 'y' }, {}, function (err, results) { + if (err) { + return done(err); + } + expect(results.name).to.be.equal('y'); + return done(); + }); + }); + + const utils = require('../lib/common/util.js'); + it('t9 utility function testing', function (done) { + var newCustomerModel = loopback.findModel('NewCustomer'); + var x = utils.isInstanceQuery(newCustomerModel, { where: { name: 'x' } }); + if (x) { + return done(new Error('Expcted instnace query flag to be false')); + } + x = utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { id: 1 }] } }); + if (!x) { + return done(new Error('Expcted instnace query flag to be true')); + } + x = utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { age: 1 }, { and: [{ id: 1 }, {age: 10}]}] } }); + if (!x) { + return done(new Error('Expcted instnace query flag to be true')); + } + x = utils.isInstanceQuery('NewCustomer', { where: { and: [{ name: 'x' }, { age: 1 }, { and: [{ id: 1 }, { age: 10 }] }] } }); + if (!x) { + return done(new Error('Expcted instnace query flag to be true')); + } + x = utils.isInstanceQuery(newCustomerModel, { where: { and: [{ name: 'x' }, { age: 1 }, { or: [{ id: 1 }, { age: 10 }] }] } }); + if (x) { + return done(new Error('Expcted instnace query flag to be flase')); + } + + var id = utils.getIdValue(newCustomerModel, { id: 10, name: 'A' }); + + var f = utils.checkDependency(oecloud, ['./']); + f = utils.checkDependency(oecloud, './'); + + var o1 = { x: 'x', a1: [12, 3, 4] }; + var o2 = { y: 'y', a2: [1122, 33, 44], a1: [1122, 33, 44] }; + + var o3 = utils.mergeObjects(o1, o2); + expect(o3.x).to.be.equal('x'); + expect(o3.y).to.be.equal('y'); + var y; + utils.mergeObjects(y, o1); + expect(utils.isBaseEntity(newCustomerModel)).to.be.equal(true); + + newCustomerModel.find({ where: { and: [{ name: 'x' }, { and: [{ id: 1 }, { age: 10 }] }, { age: { inq: [10, 20] } }] } }, {}, function (err, r) { + if (err) { + return done(err); + } + newCustomerModel.find(function (err, d) { + return done(err); + }); + }); + }); + + it('t10-1 testing wrapper functions', function (done) { + var newCustomerModel = loopback.findModel('NewCustomer'); + newCustomerModel.findById(1, { + where: { name: 'X' } + }, function (err, r) { + return done(err); + }); + }); + + + it('t10-2 testing wrapper functions', function (done) { + var newCustomerModel = loopback.findModel('NewCustomer'); + newCustomerModel.findById(1, function (err, r) { + return done(err); + }); + }); + + it('t10-3 testing wrapper functions - findOne', function (done) { + var newCustomerModel = loopback.findModel('NewCustomer'); + newCustomerModel.findOne(function (err, r) { + return done(err); + }); + }); + + + var parentModelName = 'MyTestModel'; + var enumName = 'MyTestEnum'; + + + it('t11-1 enum test', function (done) { + var enumName = 'MyTestEnum'; + var enumConfig = { + 'name': enumName, + 'base': 'EnumBase', + 'strict': true, + 'properties': {}, + 'validations': [], + 'relations': {}, + 'acls': [], + 'methods': {}, + 'enumList': [ + { + code: 'M', + description: 'Monthly' + }, + { + code: 'S', + description: 'Semi' + }, + { + code: 'A', + description: 'Annual' + }, + { + code: 'Qu', + description: 'Quarterly' + } + ] + }; + + var parentModelConfig = { + 'name': parentModelName, + 'base': 'BaseEntity', + 'strict': true, + 'properties': { + 'code': { + 'type': 'string', + 'enumtype': 'MyTestEnum', + 'required': true + } + }, + 'validations': [], + 'relations': {}, + 'acls': [], + 'methods': {} + }; + var modelDefinition = loopback.findModel('ModelDefinition'); + loopback.createModel(enumConfig); + modelDefinition.create(parentModelConfig, globalCtx, function (err, model) { + done(err); + }); + }); + + it('t11-2 enum test - should be valid if code is exact match', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.isValidEnum('S')).to.be.equal(true); + done(); + }); + it('t11-3 enum test - should be valid if code is different case', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.isValidEnum('s')).to.be.equal(true); + done(); + }); + it('t11-4 enum test - should be invalid if code is not valid', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.isValidEnum('Y')).to.be.equal(false); + done(); + }); + it('t11-5 enum test - should be invalid if code is partial match', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.isValidEnum('Q')).to.be.equal(false); + done(); + }); + it('t11-6 enum test - should return correct description for given code', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.toDescription('S')).to.be.equal('Semi'); + done(); + }); + it('t11-7 enum test - should return correct description for code in different case', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.toDescription('qu')).to.be.equal('Quarterly'); + done(); + }); + it('t11-8 enum test - should return undefined for incorrect code', function (done) { + var myenum = loopback.findModel(enumName); + expect(myenum.toDescription('y')).to.be.undefined; + done(); + }); + // it('t11-9 enum test - should should return invalid model for invalid enum', function (done) { + // var mymodel = loopback.findModel(parentModelName, globalCtx); + // var mymodeldata = { + // code: 'KKK' + // }; + // var context = { + // options: defaultContext + // }; + // var mymodelobj = new mymodel(mymodeldata); + // mymodelobj.isValid(function (ret) { + // expect(ret).to.be.equal(false); + // done(); + // }, context); + // }); + it('t11-9 enum test - should should return valid model for valid enum', function (done) { + var mymodel = loopback.findModel(parentModelName, globalCtx); + var mymodeldata = { + code: 'Qu' + }; + var context = { + options: globalCtx + }; + var mymodelobj = new mymodel(mymodeldata); + mymodelobj.isValid(function (ret) { + expect(ret).to.be.equal(true); + done(); + }, context); + }); + + + var currentDB = process.env.NODE_ENV || ''; + var datasourceFile; + + if (!currentDB) { + datasourceFile = './datasources.json'; + } else { + datasourceFile = './datasources.' + currentDB + '.js'; + } + + + var db2 = require(datasourceFile); + var newds = Object.assign({}, db2.db); + + newds.name = 'oe-cloud-test-newdb'; + newds.id = 'oe-cloud-test-newdb'; + + if (currentDB && (currentDB.toLowerCase().indexOf('mongo') >= 0 || currentDB.toLowerCase().indexOf('postgre') >= 0)) { + var dbname = process.env.DB_NAME || 'oe-cloud-test'; + newds.database = dbname + '-newdb'; + if (db2.db.url) { + var y = db2.db.url.split('/'); + var len = y.length; + var last = y[len - 1]; + last = last + '-newdb'; + y[len - 1] = last; + newds.url = y.join('/'); + // newds.url = db2.db.url.replace('oe-cloud-test', 'oe-cloud-test-newdb'); + } + } else if (currentDB && currentDB.toLowerCase().indexOf('oracle') >= 0) { + newds.user = newds.user + '-newdb'; + } else { + newds.url = db2.db.url.replace('oe-cloud-test', 'oe-cloud-test-newdb'); + } + + console.log(JSON.stringify(newds)); + + var datasources = [ newds ]; + + it('t12 - creating datasource', function (done) { + var ds = datasources[0]; + var DataSourceDefinition = loopback.findModel('DataSourceDefinition'); + DataSourceDefinition.findById(ds.id, defaultContext, function (err, res) { + if (err) { + log.error(defaultContext, 'error in datasource find', err); + return done(err); + } + if (!res) { + DataSourceDefinition.create(ds, defaultContext, function (err, res) { + return done(err); + }); + } else { + return done(); + } + }); + }); + + it('t13 - changing datasource of model', function (done) { + var DataSourceDefinition = loopback.findModel('DataSourceDefinition'); + var ds = oecloud.datasources['oe-cloud-test-newdb']; + var newCustomerModel = loopback.findModel('NewCustomer'); + ds.attach(newCustomerModel); + newCustomerModel.create({name: 'CustomerInNewDB'}, defaultContext, function (err, res) { + return done(err); + }); + }); + + it('t14 - testing group by', function (done) { + var customerModel = loopback.findModel('Customer'); + customerModel.create([{name: 'A', age: 30}, { name: 'B', age: 30 }], {}, function (err, r) { + if (err) return done(err); + customerModel.find({group: {groupBy: ['age']}}, {}, function (err2, r2) { + if (err2) return done(err2); + console.log(r2); + return done(); + }); + }); + }); + it('t15 - Calling aboutMe api', function (done) { + var url = basePath + '/users/aboutMe?access_token=' + infyToken; + api.set('Accept', 'application/json') + .get(url) + .end(function (err, response) { + var result = response.body; + expect(result.username).to.be.equal('infyuser'); + expect(result.email).to.be.equal('infyuser@infyuser.com'); + done(err); + }); + }); + var tmpCustomerId; + it('t16-1 - Testing HasOne relationship overriden functions', function (done) { + var customerModel = loopback.findModel("Customer"); + customerModel.find({}, function (err, result) { + var record = result[0]; + tmpCustomerId = record.id; + var url = basePath + '/customers/' + tmpCustomerId + '/spouseRel?access_token=' + infyToken; + api.set('Accept', 'application/json') + .post(url) + .send({ name: "Spouse1", id: "1", customerId: tmpCustomerId }) + .end(function (err, response) { + var result = response.body; + done(err); + }); + }) + + }); + it('t16-2 - Testing HasOne relationship overriden functions(update)', function (done) { + var url = basePath + '/customers/' + tmpCustomerId + '/spouseRel?access_token=' + infyToken; + api.set('Accept', 'application/json') + .put(url) + .send({ name: "Spouse1", id: "1", customerId: tmpCustomerId }) + .end(function (err, response) { + var result = response.body; + done(err); + }); + }); + + it('t16-3 - Testing HasOne relationship overriden functions(destroy)', function (done) { + var url = basePath + '/customers/' + tmpCustomerId + '/spouseRel?access_token=' + infyToken; + api.set('Accept', 'application/json') + .delete(url) + .end(function (err, response) { + console.log(response.error); + done(err); + }); + }); +}); + + diff --git a/test/ui-manager-test.js b/test/ui-manager-test.js deleted file mode 100644 index 217327a..0000000 --- a/test/ui-manager-test.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -var options; -var accessToken; -describe(chalk.blue('ui-manager-test'), function () { - this.timeout(60000); - - before('prepare test data', function (done) { - options = {}; - options.ignoreAutoScope = true; - options.fetchAllScopes = true; - bootstrap.login(function (token) { - accessToken = token; - done(); - }); - }); - - it('Creates the UIRoute, NavigationLink and UIComponent entries', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = '/api/UIManagers/generate/Literal'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({}) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.status).to.be.true; - expect(result.body.messages).to.exist.and.be.an('array'); - expect(result.body.messages.length).to.equal(3); - expect(result.body.messages).all.to.satisfy(function(item){ - return item.endsWith('-created'); - }); - done(); - } - }); - }); - - it('Returns error if model is not found', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = '/api/UIManagers/generate/InvalidModel'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({}) - .expect(500) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.error).to.exist; - expect(result.body.error.status).to.be.false; - expect(result.body.error.messages).to.exist.and.be.an('array'); - expect(result.body.error.messages.length).to.equal(1); - expect(result.body.error.messages[0]).to.equal('invalid-model-name'); - done(); - } - }); - }); - - - it('Calling /generate second time returns appropriate message', function (done) { - var api = defaults(supertest(bootstrap.app)); - var postUrl = '/api/UIManagers/generate/Literal'; - api.set('Authorization', accessToken) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .post(postUrl) - .send({}) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.body).to.exist; - expect(result.body.status).to.be.true; - expect(result.body.messages).to.exist.and.be.an('array'); - expect(result.body.messages.length).to.equal(3); - expect(result.body.messages).all.to.satisfy(function(item){ - return item.endsWith('-already-defined'); - }); - done(); - } - }); - }); -}); diff --git a/test/uicomponent-test.js b/test/uicomponent-test.js deleted file mode 100644 index b341b66..0000000 --- a/test/uicomponent-test.js +++ /dev/null @@ -1,420 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var loopback = require('loopback'); -var chalk = require('chalk'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var async = require('async'); - -describe(chalk.blue('UIElement'), function () { - this.timeout(60000); - - var designationModelName = 'Designation'; - var salutationModelName = 'Salutation'; - var testModelName = 'ModelPerson'; - - var designationModelDetails = { - 'name': designationModelName, - 'base': 'EnumBase', - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': {}, - 'validations': [], - 'relations': {}, - 'acls': [], - 'methods': {}, - 'enumList': [ - { - 'code': 'DV', - 'description': 'Developer' - }, - { - 'code': 'MGR', - 'description': 'Manager' - } - ] - }; - - var salutationModelDetails = { - 'name': 'Salutation', - 'base': 'RefCodeBase', - 'idInjection': true, - 'options': { - 'validateUpsert': true - } - }; - - - var testModelDetails = { - 'name': testModelName, - 'base': 'BaseEntity', - 'idInjection': true, - 'options': { - 'validateUpsert': true - }, - 'properties': { - 'salutation': { - 'type': 'string', - 'refcodetype': 'Salutation' - }, - 'firstName': { - 'type': 'string', - 'required': true - }, - 'middleName': { - 'type': 'string', - 'required': false - }, - 'lastName': { - 'type': 'string', - 'required': false - }, - 'gender': { - 'type': 'string', - 'required': true, - 'in': ['male', 'female', 'other'] - }, - 'language': { - 'type': 'string', - 'required': false - }, - 'birthDate': { - 'type': 'date', - 'required': true - }, - 'captureTime': { - 'type': 'timestamp', - 'required': false - }, - 'annualIncome': { - 'type': 'number', - 'required': false - }, - 'placeOfBirth': { - 'type': 'String', - 'max': 35, - 'required': false - }, - 'profession': { - 'type': 'string', - 'max': 35, - 'required': false - }, - 'nationality': { - 'type': 'string', - 'max': 35, - 'required': false - }, - 'minorIndicator': { - 'type': 'boolean', - 'required': false, - 'default': false - }, - 'qualifications': { - 'type': ['string'] - }, - 'languages': { - 'type': ['Literal'] - }, - 'designation': { - 'type': 'string', - 'enumtype': 'Designation' - }, - 'email': { - 'type': 'email' - } - }, - 'validations': [], - 'relations': { - department: { - type: 'belongsTo', - model: 'XDepartment' - }, - addresses: { - type: 'embedsMany', - model: 'XAddress' - } - }, - 'acls': [], - 'methods': {} - }; - - var addressModel = { - name: 'XAddress', - base: 'BaseEntity', - properties: { - line1: { type: 'string' }, - line2: { type: 'string' } - } - } - var departmentModel = { - name: 'XDepartment', - base: 'BaseEntity', - properties: { - name: { type: 'string' } - } - } - - var metadataCache = {}; - - function createModels(allModels, callback) { } - - function fetchComponent(componentName, callback) { - bootstrap.models.UIComponent.component(componentName, bootstrap.defaultContext, function (err, data) { - if (err) return callback(err); - - var start = data.indexOf(''); - var metaString = data.substr(start + 8, end - start - 8); - metaString = metaString.replace('window.OEUtils ||', ''); - eval(metaString); - - metadataCache = Object.assign(metadataCache, OEUtils.metadataCache); - - callback(err, data); - }); - } - - function simulateComponent(component, callback) { - bootstrap.models.UIComponent.simulate(component, bootstrap.defaultContext, function (err, data) { - if (err) return callback(err); - - var start = data.indexOf(''); - var metaString = data.substr(start + 8, end - start - 8); - var htmlPart = data.substr(end + 9).trim(); - metaString = metaString.replace('window.OEUtils ||', ''); - eval(metaString); - callback(err, OEUtils.metadataCache[component.name], htmlPart); - }); - } - - before('setup data', function (done) { - async.series([ - // function step0a(next) { - // loopback.createModel(salutationModelName, salutationModelDetails.properties, salutationModelDetails); - // next(); - // }, - function step0b(next) { - loopback.createModel(designationModelName, designationModelDetails.properties, designationModelDetails); - next(); - }, - function step1(next) { - bootstrap.models.ModelDefinition.create([salutationModelDetails, departmentModel, addressModel, testModelDetails], bootstrap.defaultContext, next); - }, - function step1a(next) { - bootstrap.models.UIElement.create({ - component: 'modelperson-form', - field: 'firstName', - attributes: [{ - name: 'minlength', - value: 2 - }] - }, bootstrap.defaultContext, function (err, data) { - next(err); - }); - }, - function step2(next) { - fetchComponent('modelperson-form.html', next); - }, - function step3(next) { - fetchComponent('modelperson-list.html', next); - }, - function step4(next) { - fetchComponent('modelperson-tpl.html', next); - } - ], function (err, data) { - done(err); - }); - }); - - it('fetch using modelmeta method', function (done) { - var UIComponent = bootstrap.models.UIComponent; - UIComponent.modelmeta(testModelName, bootstrap.defaultContext, function (err, data) { - expect(data).to.exist; - expect(data.componentName).to.equal(testModelName); - expect(data.modelName).to.equal(testModelName); - done(); - }); - }); - - it('returns error if form-template is not found', function (done) { - this.timeout(60000); - fetchComponent('modelperson-missing', function (err, data) { - expect(err).to.exist; - expect(data).to.not.exist; - expect(err.code).to.equal('TEMPLATE_TYPE_MISSING'); - done(); - }); - }); - it('returns error if form-template is not provided', function (done) { - fetchComponent('modelperson-', function (err, data) { - expect(err).to.exist; - expect(data).to.not.exist; - expect(err.code).to.equal('TEMPLATE_TYPE_UNDEFINED'); - done(); - }); - }); - - - it('returns error if model is not found', function (done) { - fetchComponent('missingmodel-form', function (err, data) { - expect(err).to.exist; - expect(data).to.not.exist; - expect(err.code).to.equal('MODEL_NOT_FOUND'); - done(); - }); - }); - - it('loads default form template', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata).to.exist; - expect(metadata.componentName).to.equal('modelperson-form'); - expect(metadata.modelName).to.equal(testModelName); - expect(metadata.metadata).to.exist; - done(); - }); - - it('default form cache has model definition', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata.metadata.models).to.be.an('object'); - expect(metadata.metadata.models[testModelName]).to.exist; - done(); - }); - - it('default form cache has properties', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata.metadata.properties).to.be.an('object'); - expect(Object.keys(metadata.metadata.properties)).to.include.members(Object.keys(testModelDetails.properties)); - done(); - }); - - xit('default form cache loads element definitions', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata.elements).to.be.an('object'); - expect(metadata.elements.firstName).to.be.an('object').and.have.keys('minlength'); - done(); - }); - - it('belongsTo relashionship reflects in properties as typeahead', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata.metadata.properties).to.be.an('object'); - expect(metadata.metadata.properties.departmentId).to.exist; - expect(metadata.metadata.properties.departmentId.type).to.equal('typeahead'); - expect(metadata.metadata.properties.departmentId.valueproperty).to.exist; - expect(metadata.metadata.properties.departmentId.displayproperty).to.exist; - expect(metadata.metadata.properties.departmentId.searchurl).to.exist; - done(); - }); - - it('embedsMany relashionship reflects in properties as grid', function (done) { - var metadata = metadataCache['modelperson-form']; - expect(metadata.metadata.properties).to.be.an('object'); - expect(metadata.metadata.properties['xAddress-test-tenants']).to.exist; - expect(metadata.metadata.properties['xAddress-test-tenants'].type).to.equal('grid'); - done(); - }); - - it('default list cache has gridConfig with all required fields as columns', function (done) { - var metadata = metadataCache['modelperson-list']; - expect(metadata.gridConfig).to.be.an('object'); - expect(metadata.gridConfig.modelGrid).to.be.an('array').and.include.members(['firstName', 'gender']); - done(); - }); - - it('simulate method returns the component definition', function (done) { - var component = { - name: 'salutation-form', - modelName: 'Salutation' - }; - simulateComponent(component, function (err, data) { - expect(data.componentName).to.equal(component.name); - expect(data.modelName).to.equal(component.modelName); - expect(data.metadata.properties).to.be.an('object').and.have.keys('code', 'description'); - done(); - }); - }); - - it('When template is not defined, content is returned as html', function (done) { - var component = { - name: 'salutation-form', - modelName: 'Salutation', - content: '

    Dummy
    ' - }; - simulateComponent(component, function (err, data, htmlPart) { - expect(htmlPart).to.equal(component.content); - done(); - }); - }); - - it('When template is defined, content is returned as response.content', function (done) { - var component = { - name: 'salutation-form', - templateName: 'default-form.html', - modelName: 'Salutation', - content: '
    Dummy
    ' - }; - simulateComponent(component, function (err, data, htmlPart) { - expect(data.content).to.equal(component.content); - done(); - }); - }); - - it('When filePath is defined, its content are returned as html', function (done) { - var component = { - name: 'sample-element', - filePath: '../client/bower_components/sample-element/sample-element.html' - }; - simulateComponent(component, function (err, data, htmlPart) { - expect(htmlPart.indexOf('')).to.equal(0); - done(); - }); - }); - - it('When templateName is defined, its content are returned as html', function (done) { - var component = { - name: 'sample-element', - templateName: '../client/bower_components/sample-element/sample-element.html' - }; - simulateComponent(component, function (err, data, htmlPart) { - expect(htmlPart.indexOf('')).to.equal(0); - done(); - }); - }); - - it('importUrls are added as link tags', function (done) { - var component = { - name: 'salutation-form', - templateName: 'default-form.html', - importUrls: ['link1.html', 'link2.html'] - }; - simulateComponent(component, function (err, data, htmlPart) { - expect(htmlPart.indexOf('' - }; - cssData = { - name: 'main.css', - type: 'text/css', - content: '.root { background: red}' - }; - jsonData = { - name: 'mydata', - type: 'application/json', - content: '{"score":100,"subject":"js"}' - }; - - bootstrap.models.UIResource.create([htmlData, cssData, jsonData - ], options, function (err, r) { - done(err); - }); - }); - }); - - it('returns 404 for non-existing pages', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = baseUrl + '/UIResources/content/nonexistent.html'; - api.get(getUrl) - .expect(404) - .end(function (err, result) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - - it('returns html-data with appropriate contentType', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = baseUrl + '/UIResources/content/' + htmlData.name; - api.get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.headers['content-type']).to.equal(htmlData.type); - expect(result.text).to.equal(htmlData.content); - done(); - } - }); - }); - - it('returns css-data with appropriate contentType', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = baseUrl + '/UIResources/content/' + cssData.name; - api.get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.headers['content-type']).to.equal(cssData.type); - expect(result.text).to.equal(cssData.content); - done(); - } - }); - }); - - it('returns json-data with appropriate contentType', function (done) { - var api = defaults(supertest(bootstrap.app)); - var getUrl = baseUrl + '/UIResources/content/' + jsonData.name; - api.get(getUrl) - .expect(200) - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.headers['content-type']).to.equal(jsonData.type); - expect(result.text).to.equal(jsonData.content); - expect(result.body).to.exist; - expect(result.body.score).to.equal(100); - expect(result.body.subject).to.equal('js'); - done(); - } - }); - }); - -}); diff --git a/test/unauthorised-write.js b/test/unauthorised-write.js deleted file mode 100644 index ca137e6..0000000 --- a/test/unauthorised-write.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * UnitTest Case - * Unathorised Update should not be allowed - * - * @author Praveen Kumar Gulati - */ -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; - -describe(chalk.blue('unauthorised-post'), function () { - - it('unauthorised-post', function (done) { - var data = { - 'key': 'BASE_ENTITY1', - 'value': 'Base Entity' - }; - - var api = defaults(supertest(bootstrap.app)); - - var postUrl = baseUrl + '/Literals'; - - api.set('Accept', 'application/json') - .post(postUrl) - .send(data) - .expect(401) - .end(function (err, resp) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - -}); diff --git a/test/update-data-acl-test.js b/test/update-data-acl-test.js deleted file mode 100644 index 0259844..0000000 --- a/test/update-data-acl-test.js +++ /dev/null @@ -1,405 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/* jshint -W024 */ -/* jshint expr:true */ -//to avoid jshint errors for expect - -var bootstrap = require('./bootstrap'); -var chalk = require('chalk'); -var chai = require('chai'); -chai.use(require('chai-things')); -var loopback = require('loopback'); -var async = require('async'); -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -var baseUrl = bootstrap.basePath; -var expect = chai.expect; -var logger = require('oe-logger'); - -var models = bootstrap.models; - -describe(chalk.blue('data-acl-update-test'), function () { - - var modelName1 = 'ItemModelPQR'; - var modelName2 = 'ConfirmationPQR'; - - var defaultContext = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'system' - } - }; - - //-------------------------------------------------------------- - // user with role A should be able to - //-------------------------------------------------------------- - // Category | Find | Create | Update | Create - // | | | | Confirmation - // ------------------------------------------------------------ - // Book | Allowed | Allowed | Allowed | NotAllowed - //-------------------------------------------------------------- - // Music | Allowed | NotAllowed|NotAllowed | Allowed - //-------------------------------------------------------------- - // Update category of existing item from book to music should - // not be allowed - //-------------------------------------------------------------- - var dataacls = [ - { - model: modelName1, - principalType: 'ROLE', - principalId: 'ROLE232', - accessType: 'READ', - group: 'category', - property: '*', - filter: { 'or': [{ 'category': 'book' }, { 'category': 'music' }] }, - errorCode: 'data-acl-err-003' - }, - { - model: modelName1, - principalType: 'ROLE', - principalId: 'ROLE232', - accessType: 'WRITE', - property: 'update', - group: 'category', - filter: { 'category': 'book' }, - errorCode: 'data-acl-err-003' - }, - { - model: modelName1, - principalType: 'ROLE', - principalId: 'ROLE232', - accessType: 'WRITE', - property: 'updateAttributes', - group: 'category', - filter: { 'category': 'book' }, - errorCode: 'data-acl-err-003' - }, - { - model: modelName1, - principalType: 'ROLE', - principalId: 'ROLE232', - accessType: 'WRITE', - property: '__create__confirmations', - group: 'category', - filter: { 'category': 'music' }, - errorCode: 'data-acl-err-003' - } - - ]; - - var user1 = { - 'username': 'user8734', - 'password': 'user8734', - 'email': 'pkgulati23@evgmail.com' - }; - - var user2 = { - 'username': 'user8735', - 'password': 'user8735', - 'email': 'user8735@gmail.com' - }; - - var modeldefs = [ - { - name: modelName1, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - }, - 'description': { - 'type': 'string', - }, - 'category': { - 'type': 'string' - } - }, - relations: { - 'confirmations': { - 'type': 'hasMany', - 'model': modelName2 - } - } - }, - { - name: modelName2, - base: 'BaseEntity', - properties: { - 'remarks': { - 'type': 'string', - }, - 'confirmed': { - 'type': 'string', - } - }, - relations: { - 'parent-item': { - 'type': 'belongsTo', - 'model': modelName1, - 'foreignKey': 'parentId' - } - } - } - ]; - - var items = [ - { - name: 'book1', - category: 'book', - description: "books can be searched by ROLE232" - }, - { - name: 'music1', - category: 'music', - description: "music can be searched by ROLE232" - }, - { - name: 'cloth1', - category: 'clothes', - description: "clothes can not be searched or updated by ROLE232" - } - ]; - - - var user1token; - - var ModelDefinition = bootstrap.models.ModelDefinition; - - this.timeout(10000000); - - var cleanup = function (done) { - async.series([function (cb) { - var model = bootstrap.models[modelName1]; - if (model) { - model.remove({}, defaultContext, function (err, res) { - cb(); - }); - } else { - cb(); - } - }, function (cb) { - models.DataACL.remove({}, defaultContext, function () { - cb(); - }); - }, function (cb) { - ModelDefinition.remove({ - 'name': modelName1 - }, defaultContext, function (err, res) { - cb(); - }); - }, function () { - done(); - }]); - }; - - var dbrecords; - - before('setup model for dataacl', function (done) { - var lc = logger('LOGGER-CONFIG'); - var dlog = logger('data-acl'); - lc.changeLogger(dlog, logger.DEBUG_LEVEL); - - async.series([function (cb) { - cleanup(cb); - }, - function (cb) { - var model = bootstrap.models[modelName1]; - if (model) { - model.remove({}, defaultContext, function () { - cb(); - }); - } else { - cb(); - } - }, - function (cb) { - ModelDefinition.remove({ - 'name': modelName1 - }, defaultContext, function (err, res) { - cb(); - }); - }, - function (cb) { - ModelDefinition.create(modeldefs, defaultContext, function (err, res) { - if (err) { - console.log('unable to create model ', JSON.stringify(err)); - cb(); - } else { - var model = loopback.findModel(modelName1, defaultContext); - model.create(items, defaultContext, function (err, result) { - cb(); - }); - } - }); - }, - function (cb) { - models.DataACL.create(dataacls, defaultContext, function (err, res) { - cb(); - }); - }, - function (cb) { - bootstrap.createTestUser(user1, 'ROLE232', cb); - }, - function (cb) { - bootstrap.createTestUser(user2, 'ROLE232', cb); - }, - function (cb) { - var model = loopback.findModel(modelName1, defaultContext); - model.find({}, defaultContext, function (err, res) { - dbrecords = res; - cb(); - }); - }, - function () { - done(); - }]); - }); - - it('login1', function (done) { - var postData = { - 'username': user1.username, - 'password': user1.password, - 'tenantId': 'test-tenant' - }; - - var postUrl = baseUrl + '/BaseUsers/login'; - - // without jwt token - var api = defaults(supertest(bootstrap.app)); - api.set('Accept', 'application/json') - .set('tenant_id', 'test-tenant') - .post(postUrl) - .send(postData) - .expect(200).end(function (err, response) { - user1token = response.body.id; - done(); - }); - }); - - it('find', function (done) { - var api = defaults(supertest(bootstrap.app)); - var url = bootstrap.basePath + '/' + modelName1 + 's?access_token=' + user1token; - api.set('Accept', 'application/json') - .get(url) - .end(function (err, res) { - expect(res.status).to.be.equal(200); - expect(res.body).to.be.an('array'); - expect(res.body).to.have.length(2); - done(); - }); - }); - - it('find and update book', function (done) { - var api = defaults(supertest(bootstrap.app)); - var filter = { where: { category: 'book' } }; - var url = bootstrap.basePath + '/' + modelName1 + 's?filter='; - url += encodeURIComponent(JSON.stringify(filter)); - url += '&access_token=' + user1token; - api.set('Accept', 'application/json') - .get(url) - .end(function (err, res) { - expect(res.body).to.have.length(1); - var rec = res.body[0]; - var url = bootstrap.basePath + '/' + modelName1 + 's/' + rec.id + '?access_token=' + user1token; - rec.description += 'updated'; - api.set('Accept', 'application/json') - .put(url) - .send(rec) - .end(function (err, res) { - console.log(res.status); - console.log(res.body.error); - expect(res.status).to.be.equal(200); - expect(res.body._version).not.to.be.equal(rec._version); - var url = bootstrap.basePath + '/' + modelName1 + 's/' + rec.id + '/' + 'confirmations' + '?access_token=' + user1token; - var confirmation = { - "remarks": "confirmation for book", - "confirmed": "yes, praveen" - }; - api.set('Accept', 'application/json') - .post(url) - .send(confirmation) - .end(function (err, res) { - expect(res.error.code === 'DATA_ACCESS_DENIED'); - done(); - }); - }); - }); - }); - - it('find and update music', function (done) { - var api = defaults(supertest(bootstrap.app)); - var filter = { where: { category: 'music' } }; - var url = bootstrap.basePath + '/' + modelName1 + 's?filter='; - url += encodeURIComponent(JSON.stringify(filter)); - url += '&access_token=' + user1token; - api.set('Accept', 'application/json') - .get(url) - .end(function (err, res) { - var rec = res.body[0]; - var url = bootstrap.basePath + '/' + modelName1 + 's/' + rec.id + '?access_token=' + user1token; - rec.description += ' and description has been updated by user..'; - api.set('Accept', 'application/json') - .put(url) - .send(rec) - .end(function (err, res) { - var response = res.body; - // earlier it was coming in main body as 403 - // expect(res.status).to.be.equal(403); - expect(res.error.status).to.be.equal(403); - expect(response.error.errCode === 'data-acl-err-003').to.be.true; - var url = bootstrap.basePath + '/' + modelName1 + 's/' + rec.id + '/' + 'confirmations' + '?access_token=' + user1token; - var confirmation = { - "remarks": "confirmation for music", - "confirmed": "yes, praveen" - }; - api.set('Accept', 'application/json') - .post(url) - .send(confirmation) - .end(function (err, res) { - expect(res.status).to.be.equal(200); - done(); - }); - }); - }); - }); - - it('update book to music', function (done) { - var api = defaults(supertest(bootstrap.app)); - var filter = { where: { category: 'book' } }; - var url = bootstrap.basePath + '/' + modelName1 + 's?filter='; - url += encodeURIComponent(JSON.stringify(filter)); - url += '&access_token=' + user1token; - api.set('Accept', 'application/json') - .get(url) - .end(function (err, res) { - expect(res.body).to.have.length(1); - var rec = res.body[0]; - rec.category = 'music'; - rec.description = 'book category can not be music'; - var url = bootstrap.basePath + '/' + modelName1 + 's/' + rec.id + '?access_token=' + user1token; - api.set('Accept', 'application/json') - .put(url) - .send(rec) - .end(function (err, res) { - var response = res.body; - expect(res.error.status).to.be.equal(403); - expect(response.error.errCode === 'data-acl-err-003').to.be.true; - done(); - }); - }); - }); - - after('after clean up', function (done) { - var lc = logger('LOGGER-CONFIG'); - var dlog = logger('data-acl'); - lc.changeLogger(dlog, logger.ERROR_LEVEL); - done(); - //cleanup(done); - }); - -}); - diff --git a/test/update-default-tenant-record.js b/test/update-default-tenant-record.js deleted file mode 100644 index 8551f4d..0000000 --- a/test/update-default-tenant-record.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var chai = require('chai'); -var expect = chai.expect; -chai.use(require('chai-things')); -var api = bootstrap.api; -var models = bootstrap.models; -var logger = require('oe-logger'); -var log = logger('data-personalization-test'); -var loopback = require('loopback'); -var async = require('async'); -var app = bootstrap.app; - -describe(chalk.blue('Update of record belonging to default tenant'), function () { - this.timeout(40000); - var modelName = 'DefaultTenantUpdateTestModel'; - var modelDetails = { - name: modelName, - base: 'BaseEntity', - properties: { - 'name': { - 'type': 'string', - 'unique': true - }, - 'description': { - 'type': 'string' - }, - 'discount': { - 'type': 'number', - 'default': 10 - } - }, - strict: false, - idInjection: true, - plural: modelName - }; - - var TestModel; - - var defaultTenantContext = { - ctx: { - tenantId: 'default', - remoteUser: 'system' - } - }; - - var allScopes = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - }, - fetchAllScopes: true - }; - - var accessToken; - - before('Create model', function (done) { - var query = { - where: { - name: modelName - } - }; - models.ModelDefinition.findOrCreate(query, modelDetails, defaultTenantContext, function (err, res, created) { - if(err){ - done(err); - } - TestModel = loopback.findModel(modelName, defaultTenantContext); - TestModel.purge({}, allScopes, function (err, info) { - done(); - }); - }); - }); - - before('Create Access Token', function (done) { - // accessToken belongs to test-tenant - // createAccessToken uses test-tenant - var user = bootstrap.defaultContext.ctx.remoteUser.username; - bootstrap.createAccessToken(user, function (err, token) { - accessToken = token; - done(); - }); - }); - - it('Update default tenant record using PUT without ID ', function (done) { - var data = { - name: 'Test1', - description: 'default tenant', - discount: 20 - } - TestModel.create(data, defaultTenantContext, function (err, rec) { - expect(rec).to.be.ok; - var url = bootstrap.basePath + '/' + modelName + '?access_token=' + accessToken; - var data = rec.toObject(); - data.discount = 40; - api - .put(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, result) { - if (err) { - done(err); - } else { - // It should create a different record - // with a new ID - expect(result.status).to.be.equal(200); - expect(result.body).to.be.ok; - expect(result.body.id).not.to.be.equal(data.id); - done(); - } - }); - }); - }); - - // This is bug in juggler this must be fixed - it('Update default tenant record using PUT with ID ', function (done) { - var data = { - name: 'Test2', - description: 'default tenant', - discount: 20 - } - - TestModel.create(data, defaultTenantContext, function (err, rec) { - expect(rec).to.be.ok; - var url = bootstrap.basePath + '/' + modelName + '/' + rec.id + '?access_token=' + accessToken; - var data = rec.toObject(); - data.discount = 40; - api - .put(url) - .send(data) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .end(function (err, result) { - if (err) { - done(err); - } else { - expect(result.status).to.be.equal(404); - done(); - } - }); - }); - }); - -}); diff --git a/test/upload-file-data/x.png b/test/upload-file-data/x.png deleted file mode 100644 index 5532531b50ae95b75bbae9f4e91da8dc6a3d32bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2776 zcmcguS3nck5{-qbi%5}P1On1Rliq760jUZs3mT+KAP|}~6@!t0p!D7$pmc%+BBDWw zbQ2RqBC^OLB}A5vfZ@frU+?FA?#Il%Gjryix%bY;q&qu-f8!SA1^@uR+1XmT0stqX zS^HehlPrB+HwMjGP9R*t7J$|X@fDVFI>_A7902IZK67+~jb(GeZM_fx0MFnrIYEqQ zya@ns!|bfg-Q#^3vu(Z$5P_jcxy}4{`L=(JjM&RXE!uyt>MXo#)!J+&^CXeWR*r8t z(ZujvZb%MS$V{$j`P?;_JuMm&R51Y|Z-3uagp= zk}@TG56TPr;{av*W|fPV7nG>t7>l0`?CS0BXA~>O#l=ZUNhKJeH@3I2-YxsmD`vRh z_$UOT7Q0+MHtaOXCXdhVUZT-TC1V?!+1E@*HIqoA6BjpG0+5N zMx2@zJN6_@<W>bUUk@uCI=CxHkHvR0ACKmFzVA!7u$%t# z>*l5_BkFUA-mdS_!H&Zf%AkymZbAzM^2JMgoKB}NEYwmrc6PeI{CF@GAn@~%g`KBz zlZ#VA>YVw4s6+9mR?3^d0uIW$_P-09s*j21i%}b2bwZ97jtACW=*6|`y>AIWP-&f1 z4Gyys_nl@iTEYo0tz$8JdwZsLm`P%nSQ=~Aflj)8n>X(L5 zBuLFx{MK)U-}e>{4rT&ZE|~T0kyNS)T9w4Zgv|FmHngqx;_sHveIU_;6M-mp`|;al z`-XGy4w1*+2N~Ts$KP3>X2dQO$@M|WQ#W4;d!MvBh9v0CpJ;wV2pBtI|HZ5(Y|&TF$Nl&eqoE zxFk~SYj&TWJV^SbD)iI)@Eo~S zE{eBG?2)TWvGb+FADXmvVCCn#TEQ0lA~0F=T6cGOff3UUf1~T;ogQJpfB_=*7<`NN7jb*=x?LQDuaR|>0GE` zwb6Mk0;w@t8_n-4W8i8{HOHxV-FiQLmw=x{5FGd=n)uLz!D1OyQ4JmR#_#+c#-X~D zNg>gi8giB1#JljGj59JrOh%zvBzP?IJAnh7$zesvQCvMhIohCO*pQ~#@{=?-Zq$B^0wA61=~TZhnD1pU^Dl?oM=H$$6dRMDrpYNUiMBX*cL6t~1MHRN^=%hJ6R1b4 zsq~YEcJOJ;5YRGANLoV9#9f4NK>W@_QnO%x_SJ^5N8@eSsi%t%esNA*V0GE#2^c0}J zK0}78&&2u<=d`waHZPyU)MDg)G${t*jNB}#jB;_8BtdHG9TX(U(rJL%6r}J^%zu3) zp`Qoe?gA;!@DuO@=B1LJi!r{v^6Suh7;A>R*%)I)Qz#eD@7=f9@UV~&hT%!!Msal# zydaTKiKX2QU;PSwbX$$m-<5K=bdjUc6ayxMpF@qU^@Vd%z9+}f&bB?{KD;=~n>dh6 z&YvYu7}e}6tw>M(uV2Xj7^L8L>80_)5d_Jig@9YM><{atyoWaHAFH*wCB-P+1MVGR zFba&j@L^|q=Cn!oLlGV8&bZiaZ`FV`%MI$vxL+Xd@|7d1)Z^(eozU_~8m2(){-LkCV%%*=q%xhS`C=VfHms8~VC^G%ypz%~OLZ)g{z}Km$LLO^{2v zGm>&gp;w6c^|l2KJv}}9Ovx?AmaK$6(rd)f0Y|BicoA-(ZG3oUefu7| z*d3c+ePj&B-uBTeDUiFH0$JvCIE z9;L%WmipKD$`;>Vg^?o>2!5s?hkld^HF+d^UV7#oi)BFtKP_#2`1j}0<|M-}9c9Ls zq23&dq@phMI?h5BY6GGt+ft4$+1CWX7`Swcb!e&*fDVGI;-C#IXsc&!ZduHz5^bC7 zbrWsvc2S|J9jW$qoJmbFuk2q zDgO<0Oqr7hZN6c_4X^_@u`b~pwpNxu=5+_5{`M;I$q%yVrNE=nwrU8{q2S=F=RBY1 zn>Ta9I;W<5@B`QTtJXm`R(|1$k%Mn3n~R-$YA(FWMS`qq%cHj88smyN^)?`=fo7W7 zYveE7dqo0J?^_AG$`mw;bz%i)4acVE?At<+kctNlD zNQh1WRpc3UPGbe-itIKR09CbgGwr^1a{yrQ-y6vO@9-y{l)^kmNA~jR<};62( ', info.count); - models.ModelDefinition.destroyAll({ - "name": modelName - }, bootstrap.defaultContext, function (err) { }); - } - }); - done(); - }); - - it('should create a new record with version number', function (done) { - var postData = { - 'name': 'record1' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - expect(res._version).not.to.be.empty; - done(); - } - }); - }); - - it('should create and update a record -upsert', function (done) { - var postData = { - 'name': 'record2' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - res.name = 'updatedRecord2'; - model.upsert(res, bootstrap.defaultContext, function (err, res1) { - if (err) { - done(err); - } else { - expect(res1._version).not.to.be.empty; - expect(res1.name).to.be.equal('updatedRecord2'); - done(); - } - }); - } - }); - }); - - it('should not update a record with wrong version -upsert', function (done) { - // commented out as upsert and autoscope is resulting into new record - // I think upsert should not allow version on new record - // Or upsert should never insert if version is present - // and internally can do update - var postData = { - 'name': 'record3' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.name = 'updatedRecord3'; - postData._version = 'wrongNumber'; - postData.id = res.id; - model.upsert(postData, bootstrap.defaultContext, function (err1, res1) { - if (err1) { - expect(err1.message).not.to.be.empty; - done(); - } else { - done(new Error('record updated with wrong version number')); - } - }); - } - }); - }); - - it('should not update a record without version number -upsert', function (done) { - var postData = { - 'name': 'record3' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.name = 'updatedRecord3'; - postData.id = res.id; - postData._version = undefined; - model.upsert(postData, bootstrap.defaultContext, function (err1, res1) { - if (err1) { - expect(err1.message).not.to.be.empty; - done(); - } else { - done(new Error('record updated without version number')); - } - }); - } - }); - }); - - it('should create and update a record -updateAttributes', function (done) { - var postData = { - 'name': 'record4' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.findOne({ - where: { - id: res.id - } - }, bootstrap.defaultContext, function (err1, instance) { - if (err1 || !instance) { - done(err1 || new Error('record not found')); - } else { - postData.name = 'updatedRecord4'; - postData._version = instance._version; - instance.updateAttributes(postData, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - done(err2); - } else { - expect(res1._version).not.to.be.empty; - expect(res1.name).to.be.equal('updatedRecord4'); - done(); - } - }); - } - }); - } - }); - }); - - it('should not update a record with wrong version -updateAttributes', function (done) { - var postData = { - 'name': 'record5' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.findOne({ - where: { - id: res.id - } - }, bootstrap.defaultContext, function (err1, instance) { - if (err1 || !instance) { - done(err1 || new Error('record not found')); - } else { - postData.name = 'updatedRecord5'; - postData._version = 'WrongVersion'; - postData.id = res.id; - instance.updateAttributes(postData, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - expect(err2.message).not.to.be.empty; - done(); - } else { - done(new Error('record updated with wrong version number')); - } - }); - } - }); - } - }); - }); - it('should update a record without version programitacally -updateAttributes', function (done) { - var postData = { - 'name': 'record6' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.findOne({ - where: { - id: res.id - } - }, bootstrap.defaultContext, function (err1, instance) { - if (err1 || !instance) { - done(err1 || new Error('record not found')); - } else { - postData.name = 'updatedRecord6'; - instance.updateAttributes(postData, bootstrap.defaultContext, function (err2, res1) { - expect(err2).not.to.be.ok; - done(); - }); - } - }); - } - }); - }); - it('should create and update a record with version - upsert 1', function (done) { - var postData = { - 'name': 'record7' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.name = 'updatedRecord7'; - postData._version = res._version; - postData.id = res.id; - model.upsert(postData, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - done(err2); - } else { - expect(res1._version).not.to.be.empty; - done(); - } - }); - } - }); - }); - - it('should delete a record giving id and version number -deleteById', function (done) { - var postData = { - 'name': 'record11' - }; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - model.deleteById(res.id, res._version, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - done(new Error('record not deleted without version number')); - } else { - expect(res1.count).to.be.equal(1); - done(); - } - }); - } - }); - }); - - xit('should create and update a record with version - upsert', function (done) { - var postData = { - 'name': 'record121' - }; - var flag = false; - var flag2 = false; - model.create(postData, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } else { - postData.name = 'updatedRecord121'; - postData._version = res._version; - postData.id = res.id; - model.upsert(postData, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - flag = true; - } else if (res1) { - expect(res1.name).to.be.equal(postData.name); - } else { - flag = true; - } - }); - var postData2 = {}; - postData2.name = 'updatedRecord22222'; - postData2._version = res._version; - postData2.id = res.id; - model.upsert(postData2, bootstrap.defaultContext, function (err2, res1) { - if (err2) { - flag2 = true; - } else if (res1) { - expect(res1.name).to.be.equal(postData.name); - } else { - if (flag) { - flag2 = true; - } - } - }); - setTimeout(function () { - if (flag && flag2) { - done(new Error(' test case failed')); - } else { - done(); - } - }, 1000); - } - }); - }); - - - -}); diff --git a/test/z-jwt-assertion-test.js b/test/z-jwt-assertion-test.js deleted file mode 100644 index a8cb764..0000000 --- a/test/z-jwt-assertion-test.js +++ /dev/null @@ -1,1020 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -var expect = chai.expect; -var loopback = require('loopback'); -var models = bootstrap.models; -var defaults = require('superagent-defaults'); -var supertest = require('supertest'); -// @jsonwebtoken is internal dependency of @oe-jwt-generator -var jwt = require('jsonwebtoken'); -var jwtUtil = require('../lib/jwt-token-util'); -var api = supertest(app); -var appDetails = { - "appId": "abc123", - "appName": "external app", - "supportedRoles": ["manager"] -} - -var roleDetail = { - "id": "manager", - "name": "manager" -} - -var jwtEnvConfig = { - 'issuer': 'mycompany.com', - 'audience': 'mycompany.net', - 'secretOrKey': 'secret', - 'keyToVerify': 'client_id' -} - -var jwtEnvConfig2 = { - 'issuer': 'mycompany.com', - 'audience': 'mycompany.net', - 'secretOrKey': 'tokenKey' -} - - -var testModel = { - "properties": { - "name": { "type": "string" } - }, - "name": "managerTable", - "plural": "managerTables", - "base": "BaseEntity", - "acls": [{ - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "*" - }, - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "manager", - "permission": "ALLOW", - "property": "*" - } - ] - -} - -var demouser1 = { - 'username': 'demouser1@gmail.com', - 'password': 'password++', - 'email': 'demouser1@gmail.com', - 'tenantId': 'test-tenant', - 'id': 10 -}; - -var demouser2 = { - 'username': 'demouser2@gmail.com', - 'password': 'password ++', - 'email': 'demouser2@gmail.com', - 'tenantId': 'test-tenant', - 'id': 100 -}; - -var demouser3 = { - 'username': 'demouser3@gmail.com', - 'password': 'password++', - 'email': 'demouser3@gmail.com', - 'tenantId': 'test-tenant', - 'id': 1000 -} - -var createToken = function (user, key, algo) { - var secret = key || 'secret'; - var token = jwt.sign(user, secret); - return token; -}; - -// Test cases for testing the JWT authentication scheme . -describe(chalk.blue('JWT assertion test'), function () { - var endPointUrl = bootstrap.basePath + '/BaseUsers'; - - this.timeout(20000); - - before('Adding user to BaseUser', function (done) { - var User = loopback.getModelByType('User'); - User.create([demouser1, demouser3], bootstrap.defaultContext, function (err, users) { - if (err) { - done(err); - } else { - var trustedApp = loopback.getModelByType('TrustedApp'); - trustedApp.create(appDetails, bootstrap.defaultContext, function (err, tapp) { - if (err) { - done(err); - } else { - var modelDef = loopback.getModel('ModelDefinition'); - modelDef.create(testModel, bootstrap.defaultContext, function (err, model) { - if (err) { - done(err); - } else { - var role = loopback.getModelByType('BaseRole'); - role.create(roleDetail, bootstrap.defaultContext, function (err, role) { - process.env.JWT_FOR_ACCESS_TOKEN = 'true'; - if (err) { - done(err); - } else { - done() - } - }) - } - }) - } - }) - } - }); - }); - - it('Test - Authorized user - Should give user details ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized User - Should give User access even if app details not present but jwt username matches', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456', - 'sub': 'demouser3@gmail.com', - 'username': 'demouser3@gmail.com', - 'email': 'demouser3@gmail.com' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(endPointUrl + '/1000') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Unauthorized user - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser@gmail.com', - 'username': 'demouser@gmail.com', - 'email': 'demouser@gmail.com' - }; - - var jwt = createToken(jwtOptions); - api.get(endPointUrl + '/30') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Token expired - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1456498420, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - - var jwt = createToken(jwtOptions); - - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid audience - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - - var jwt = createToken(jwtOptions); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid secret - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var secret = "invalid"; - var jwt = createToken(jwtOptions, secret); - - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid issuer - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com', - 'username': 'demouse1r@gmail.com', - 'email': 'demouser1@gmail.com' - }; - - var jwt = createToken(jwtOptions); - - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Username and email missing - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com' - }; - - var jwt = createToken(jwtOptions); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - New user - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser@gmail.com', - 'username': 'demouser@ev.com' - }; - - var jwt = createToken(jwtOptions); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should give app access to test model with access only to manager ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access if trusted app not found ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access to test model with unsupported role passed ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["supplier"]') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access if username email not given in header ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('roles', '["manager"]') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Test - Authorized User - Should not give model access if app details not present but jwt username matches just by role passed in header', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(endPointUrl + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - check env variable and parse it', function(done) { - var jwtcfg = process.env.JWT_CONFIG; - expect(jwtcfg).not.to.be.null; - var tempcfg; - try { - tempcfg = JSON.parse(jwtcfg); - expect(tempcfg.secretOrKey).to.be.equal('secret'); - done(); - } catch (e) { - done(e); - } - }); - it('Test - PublicKey sanitation code returns key', function(done) { - var PublicKey = "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZfwG/2xBCStgRGWTCrkujlw7i8CGiDGERiUppttA2z6SgAhz0SctlCZsPBHEgFKy67NIg4DD2KYlYuUhX50XEjg+QeYC5TGbMinOSyv+i5dgF9EswM5w9mZ4MQ3qO6cIiuvKJeftzlp4Nip3tZgjeSEMP2al6q5wDaEGh269kwQIDAQAB-----END PUBLIC KEY-----"; - var result = jwtUtil.sanitizePublicKey(PublicKey); - expect(result).not.to.be.null - done(); - }); - - it('Test - Authorized user - set jwt secret dynamically from env', function(done) { - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var jwt = createToken(jwtOptions, 'tokenkey'); - process.env.SECRET_OR_KEY = 'tokenkey'; - var api = defaults(supertest(app)); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(200) - .end(function(err, res) { - if (err) { - process.env.SECRET_OR_KEY = 'secret'; - done(err); - } else { - process.env.SECRET_OR_KEY = 'secret'; - done(); - } - }); - }); - it('Test - Authorized app - Should give app access to test model with access only to manager ', function(done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(200) - .end(function(err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access if trusted app not found ', function(done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(401) - .end(function(err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access to test model with unsupported role passed ', function(done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["supplier"]') - .expect(401) - .end(function(err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized app - Should not give app access if username email not given in header ', function(done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc123' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(bootstrap.basePath + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('roles', '["manager"]') - .expect(401) - .end(function(err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - - it('Test - Authorized User - Should give User access even if app details not present but jwt username matches', function(done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(endPointUrl + '/10') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Authorized User - Should not give model access if app details not present but jwt username matches just by role passed in header', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'client_id': 'abc456', - 'sub': 'demouser1@gmail.com', - 'username': 'demouser1@gmail.com', - 'email': 'demouser1@gmail.com' - }; - var jwt = createToken(jwtOptions); - - var api = defaults(supertest(app)); - api.get(endPointUrl + '/managerTables') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .set('username', 'rocky') - .set('email', 'rocky@email.com') - .set('roles', '["manager"]') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); -}); - -describe(chalk.blue('JWT assertion inherited model test'), function () { - this.timeout(20000); - var endPointUrl2 = bootstrap.basePath + '/UserTestModel2'; - before('Changing config to use JWT Assertion', function (done) { - var BaseUser = loopback.findModel('BaseUser'); - var newUserModel = 'UserTestModel2'; - var modelDetails = { - name: newUserModel, - base: BaseUser, - plural: newUserModel - }; - - var newmodel = loopback.createModel(modelDetails); - newmodel.clientModelName = newUserModel; - newmodel.clientPlural = newUserModel + 's'; - app.model(newmodel, { - dataSource: 'db' - }); - - var user = loopback.findModel('UserTestModel2'); - user.create(demouser2, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - // delete app.models["UserTestModel2"]; - } - done(); - }); - }); - - after('Remove Test Model', function (done) { - process.env.JWT_FOR_ACCESS_TOKEN = 'false'; - delete process.env.JWT_FOR_ACCESS_TOKEN; - models.UserTestModel2.destroyById(100, bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } - done(); - }); - }); - - it('Test - Authorized user - Should give user details ', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser2@gmail.com', - 'username': 'demouser2@gmail.com', - 'email': 'demouser2@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(200) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Unauthorized user - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser@gmail.com', - 'username': 'demouser@gmail.com', - 'email': 'demouser@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/200') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Token expired - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1456498420, - 'aud': 'mycompany.net', - 'sub': 'demouser2@gmail.com', - 'username': 'demouser2@gmail.com', - 'email': 'demouser2@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid audience - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany', - 'sub': 'demouser2@gmail.com', - 'username': 'demouser2@gmail.com', - 'email': 'demouser2@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid secret - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'mycompany.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser2@gmail.com', - 'username': 'demouser2@gmail.com', - 'email': 'demouser2@gmail.com' - }; - var secret = "invalid"; - var jwt = createToken(jwtOptions, secret); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Invalid issuer - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser2@gmail.com', - 'username': 'demouse2r@gmail.com', - 'email': 'demouser2@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - Username and email missing - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser2@gmail.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); - it('Test - New user - Should not give user details', function (done) { - // JWT - var jwtOptions = { - 'iss': 'infosys.com', - 'iat': 1489992854, - 'exp': 1837148054, - 'aud': 'mycompany.net', - 'sub': 'demouser@gmail.com', - 'username': 'demouser2@ev.com' - }; - - var jwt = createToken(jwtOptions); - var api = defaults(supertest(app)); - api.get(endPointUrl2 + '/100') - .set('Content-Type', 'application/json') - .set('Accept', 'application/json') - .set('x-jwt-assertion', jwt) - .set('tenant_id', 'test-tenant') - .expect(401) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); - }); -}); diff --git a/test/z-remove-demo-user-test.js b/test/z-remove-demo-user-test.js deleted file mode 100644 index a8b61b1..0000000 --- a/test/z-remove-demo-user-test.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -// var app = bootstrap.app; -var chai = require('chai'); -chai.use(require('chai-things')); -// var loopback = require('loopback'); -var models = bootstrap.models; - -// Test case to remove demo user which was used for testing and logging in using JWT. -describe(chalk.blue('Remove Demo User'), function () { - after('Remove Demo user', function (done) { - models.BaseUser.destroyById('30', bootstrap.defaultContext, function (err, res) { - if (err) { - done(err); - } - done(); - }); - }); -}); diff --git a/test/z-z-batch-job-test.js b/test/z-z-batch-job-test.js deleted file mode 100644 index 6a9d23b..0000000 --- a/test/z-z-batch-job-test.js +++ /dev/null @@ -1,307 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('batch-job-tests'); -var api = bootstrap.api; -var async = require('async'); -var BatchJobRunner = require('../lib/batchJob-runner'); -var app = bootstrap.app; - -var accessToken; -var accountModel; -var intrestModel; -const accountModelName = 'TestAccountBatchJob'; -const accountModelPlural = '/' + accountModelName + 's/'; -const transactionModelName = 'TestTransferBatchJob'; -const transactionModelPlural = '/' + transactionModelName + 's/'; -const intrestModelName = 'IntrestBatchJob'; -const intrestModelPlural = '/' + intrestModelName + 's/'; - -function apiPostRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '0') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err || (new Error(JSON.stringify(res.body.error)))); - // pktippa@gmail.com - // Not sure why we are looping it, it may lead to infinite loop - // and timeout set to 2 secs which is enough to trigger enough requests - // to log error to CI console fail the CI with Build log exceeded. - apiPostRequest(url, postData, callback, done); - //return done(err || (new Error( err.message || JSON.stringify(res.body.error)))); - } else { - return callback(res, done); - } - }); -} - -function apiGetRequest(url, callback, done) { - var version = uuidv4(); - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + url + '?access_token=' + accessToken) - .send() - .end(function(err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err || (new Error(JSON.stringify(res.body.error)))); - apiGetRequest(url, callback, done); - //return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res, done); - } - }); -} - -describe(chalk.blue('batch-job-test'), function () { - this.timeout(20000); - // We should login as testuser since we created models using default context with testuser - // And if the req.callContext doesnt have testuser info, req.url will fail to update - // tenant specific url, resulting in 404 when accessing API's. - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - before('create testAccount models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - - var data = { - 'name': intrestModelName, - 'base': 'BaseEntity', - 'properties': { - 'totalFee' : { - 'type': 'number', - 'default': 0 - } - } - }; - modelDefinition.create(data, bootstrap.defaultContext, createAccountModel); - - function createAccountModel(){ - var data = { - 'name': accountModelName, - 'base': 'BaseActorEntity', - 'properties': { - 'currentMonthFees' : { - 'type': 'number', - 'default': 0 - } - } - }; - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - } - - function createTransferModel() { - var data = { - 'name': transactionModelName, - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions() { - - var transferDefinition = loopback.getModel(transactionModelName, bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - - var accountDefinition = loopback.getModel(accountModelName, bootstrap.defaultContext); - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - accountDefinition.prototype.calculateFeesPerAccount = function (interestCoefficient, ctx, callback) { - - accountModel.find({}, ctx, (err, accounts)=> { - async.each(accounts, function(account, cb) { - var transactionModel = loopback.getModel(transactionModelName, ctx); - var idFieldName = accountModel.definition.idName(); - var accountId = account[idFieldName]; - var query = { where: { startup: { regexp: '[0-9a-zA-Z]*' + accountId } } }; - transactionModel.find(query, ctx, function (err, res) { - var intrest = !res ? 0 : res.length * interestCoefficient; - retryUpdateAttributes(account, intrest, ctx, cb); - }); - }, function(err) { - callback(err); - }); - }); - }; - - accountDefinition.prototype.associatedModels = [transactionModelName]; - return done(); - } - - function retryUpdateAttributes(account, intrest, ctx, callback) { - account.updateAttribute('currentMonthFees', intrest, ctx, function (err) { - if (err) { - log.error(log.defaultContext(), err); - // Enter Monitoring Per Instance Processing - Maybe - if (err.message === 'Instance is already locked') { - return setTimeout(retryUpdateAttributes, 2000, account, intrest, ctx, callback); - } - } - return callback(); - }); - } - - }); - - var ids = []; - - before('create 10 accounts with 20 tnxs in each', function (done){ - var createAccounts = []; - for (var i=0; i<10; i++){ - var accId = accountModelName + '_' + i ; - ids.push(accId); - } - - ids.forEach( (accountId, i) => { - createAccounts.push((callback) => { - apiPostRequest(accountModelPlural, { 'id': accountId, 'stateObj': { 'quantity': 0 } }, postTransaction, callback); - - function postTransaction(result, callback) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id.toString(), - 'payload': { 'value': 20 }, - 'modelName': accountModelName, - 'instructionType': 'CREDIT' - } - ] - }; - apiPostRequest(transactionModelPlural,postData, getRequest, callback); - } - - function getRequest (result, callback) { - apiGetRequest(accountModelPlural + accountId, (res, ck) => {ck();}, callback); - } - }); - }); - - async.parallel(createAccounts, function (err){ - return done(err); - }); - - }); - - it('test batchJob execution', function createModels(done) { - accountModel = loopback.getModel(accountModelName, bootstrap.defaultContext); - intrestModel = loopback.getModel(intrestModelName, bootstrap.defaultContext); - var idFieldName = accountModel.definition.idName(); - - var msg = {}; - msg.options = bootstrap.defaultContext; - msg.jobModelName = accountModelName; - msg.jobFnName = 'calculateFeesPerAccount'; - msg.jobFnParams = [0.5]; - - BatchJobRunner.processMsg(msg, ()=> msg.status = 'succsseful'); - - function checkResults (tryouts, done) { - accountModel.find({}, bootstrap.defaultContext, function(err, accounts) { - if (err && tryouts === 10){ - log.error(log.defaultContext, err); - return done(new Error ("Batch Job was not successful")); - } - if (err) { - return setTimeout(checkResults, 500, tryouts+1, done); - } - var feeSum = 0; - async.each(accounts, function(account, cb){ - feeSum += account.currentMonthFees; - cb(); - }, function(err) { - if (err) return done(err); - if (feeSum == 5) return done(); - else if (tryouts < 10 ) return setTimeout(checkResults, 500, tryouts+1, done); - else return done(new Error ("Batch Job was not successful")); - }); - }); - } - checkResults(1, done); - - }); - - after('delete all the test Models', function(done) { - var deleteContext = {fetchAllScopes: true, ctx: {tenantId: 'test-tenant'}}; - async.each([accountModelName, transactionModelName, intrestModelName], function (modelName, callback) { - var Model = loopback.getModel(modelName, bootstrap.defaultContext); - Model.destroyAll({}, deleteContext, function(err) { - if (err) { - if (err.message == 'Cannot delete journal entry') return callback(); - - log.error(err.message); - return callback(err); - } else { - return callback(); - } - }); - }, function(err) { - if (err) done(err); - else done(); - }); - }); -}); - diff --git a/test/z-z-business-validations-tests.js b/test/z-z-business-validations-tests.js deleted file mode 100644 index 3fae5db..0000000 --- a/test/z-z-business-validations-tests.js +++ /dev/null @@ -1,709 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This is a collection of tests that make sure that the business validations extension point of actor pattern work. - * - * @author Karin Angel - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('business-validations-test'); - -var api = bootstrap.api; - -var accessToken; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -function doSynchronousActions() { - var count = 100000000; - while (count > 0) { - count--; - } - return; -} - -function doAsynchronousActions(cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/BaseRoles/' + '?access_token=' + accessToken) - .end(function (err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err.message || JSON.stringify(res.body.error)); - cb(err || (new Error(JSON.stringify(res.body.error)))); - } else { - cb(); - } - }); -} - -function doAsynchronousActionsFail(cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/BaseRoles/' + '?access_token=' + accessToken) - .end(function (err, res) { - log.error(log.defaultContext(), 'failing IO on purpose'); - cb(new Error('failing IO on purpose')); - }); -} - -describe(chalk.blue('business-validations-tests'), function () { - this.timeout(30000); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - var backupConstants = {}; - var pendingId; - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestAccount', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 1 - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - - function createTransferModel(err) { - if (err) { - console.log(err); - return done(err); - } - var data = { - 'name': 'TestTransfer', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions(err) { - if (err) { - console.log(err); - return done(err); - } - var accountDefinition = loopback.getModel('TestAccount', bootstrap.defaultContext); - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - - accountDefinition.prototype.associatedModels = ['TestTransfer']; - return done(); - } - - }); - - - it('trivial bussiness validation + atomic action pass --> transaction should pass', function (done) { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options,ctx, cb) { - log.debug(log.defaultContext(), 'trivial implementation'); - cb(); - }; - - //credit an account 20 and then debit the same account 10 - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - return done(); - } - }); - } - }); - - it('trivial bussiness validation + atomic action fails --> transaction should fail', function (done) { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'trivial implementation'); - cb(); - }; - - //fail to debit from a new account - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('synchronous bussiness validation pass + atomic action pass --> transaction should pass', function (done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'synchronous implementation'); - doSynchronousActions(); - cb(); - }; - - //credit an account 20 and then debit the same account 10 - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midWait, done); - } - - function midWait(result) { - setTimeout(midTestCheck, 3000, result); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - return done(); - } - }); - } - }); - - it('synchronous bussiness validation pass + atomic action fails --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'synchronous implementation'); - doSynchronousActions(); - cb(); - }; - - //fail to debit from a new account - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('synchronous bussiness validation fails + atomic action should pass but does not start --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'synchronous implementation'); - doSynchronousActions(); - log.error(log.defaultContext(), 'failing synchronous actions on purpose'); - cb(new Error('failing synchronous actions on purpose')); - }; - - //debit the account 0 - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 0 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('synchronous bussiness validation fails + atomic action should fail but does not start --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'synchronous implementation'); - doSynchronousActions(); - log.error(log.defaultContext(), 'failing synchronous actions on purpose'); - cb(new Error('failing sync actions on purpose')); - }; - - //fail to debit from a new account - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('asynchronous bussiness validation pass + atomic action pass --> transaction should pass', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'asynchronous implementation'); - doAsynchronousActions(cb); - }; - - //credit an account 20 and then debit the same account 10 - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - return done(); - } - }); - } - }); - - it('asynchronous bussiness validation pass + atomic action fails --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'asynchronous implementation'); - doAsynchronousActions(cb); - }; - - //fail to debit from a new account - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('asynchronous bussiness validation fails + atomic action should pass but does not start --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'asynchronous implementation'); - doAsynchronousActionsFail(cb); - }; - - //debit an account 0 - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 0 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - it('asynchronous bussiness validation fails + atomic action should fails but does not start --> transaction should fail', function(done) { - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function(options, ctx, cb) { - log.debug(log.defaultContext(), 'asynchronous implementation'); - doAsynchronousActionsFail(cb); - }; - - //fail to debit from a new account - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var version = uuidv4(); - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - log.error(err); - return done(); - }); - } - }); - - var deleteContext = { fetchAllScopes: true, ctx: { tenantId: 'test-tenant' } }; - - after('delete all the test accounts', function (done) { - var testAccount = loopback.getModel('TestAccount', bootstrap.defaultContext); - testAccount.destroyAll({}, deleteContext, function (err) { - if (err) { - log.error(err); - return done(err); - } else { - return done(); - } - }); - }); - - after('delete all the test transfers', function (done) { - var testTransfer = loopback.getModel('TestTransfer', bootstrap.defaultContext); - testTransfer.destroyAll({}, deleteContext, function (err) { - if (err) { - expect(err.message).to.be.equal('Cannot delete journal entry'); - return done(); - } else { - log.debug('deleted alltest transfers'); - return done(new Error('Should not be allowed to delete journal entries!')); - } - }); - }); - - after('delete all the test states', function (done) { - var state = loopback.getModel('State'); - state.destroyAll({}, deleteContext, function (err) { - if (err) { - log.error(err); - return done(err); - } else { - return done(); - } - }); - }); - - after('delete all modelDefinition models', function (done) { - // models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function(err, res) { - // if (err) { - // done(err); - // } else { - // done(); - // } - // }); - done(); - }); -}); diff --git a/test/z-z-rest-api-actors-mixin-tests.js b/test/z-z-rest-api-actors-mixin-tests.js deleted file mode 100644 index b051292..0000000 --- a/test/z-z-rest-api-actors-mixin-tests.js +++ /dev/null @@ -1,334 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -/* This is a collection of tests that make sure that find on a base actor entity is first called on memory, - * and if there is no object with the desired Id - the call proceeds to the DB. - * - * @author Karin Angel - */ - -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('rest-api-actors-mixin-test'); - -var api = bootstrap.api; - -var accessToken; -var testAccountId; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -describe(chalk.blue('rest-api-actors-mixin-tests'), function () { - this.timeout(30000); - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestAccount', - 'base': 'BaseActorEntity' - }; - - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - - function createTransferModel() { - var data = { - 'name': 'TestTransfer', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions() { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - - var accountDefinition = loopback.getModel('TestAccount', bootstrap.defaultContext); - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - accountDefinition.prototype.associatedModels = ['TestTransfer']; - return done(); - } - - }); - - it('actor not in mem not in db. get actor --> empty.', function (done) { - - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id":"1234567"}}&access_token=' + accessToken) - .send() - .expect(200).end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body.length).to.be.equal(0); - return done(); - } - }); - }); - - it('actor not in mem but in db. get actor --> actor from db + actor init.', function (done) { - - log.debug(log.defaultContext(), 'post actor with quantity of 0'); - apiRequest('/TestAccounts/', { 'qqq': 0, 'stateObj': { 'quantity': 0 } }, proceed, done); - - function proceed(result) { - log.debug(log.defaultContext(), 'get the actor and check the quantity is 0'); - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id":"' + result.body.id + '"}}&access_token=' + accessToken) - .send() - .expect(200).end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].id).to.be.equal(result.body.id); - expect(res.body[0].state.stateObj.quantity).to.be.equal(0); - return done(); - } - }); - } - }); - - it('actor in mem and in db with different quantity. get actor --> actor from mem.', function (done) { - //put in DB - log.debug(log.defaultContext(), 'post actor with quantity of 0'); - apiRequest('/TestAccounts/', { 'rrr': 0, 'stateObj': { 'quantity': 0 } }, proceed, done); - - //make a transaction with account --> into memory pool - function proceed(result) { - testAccountId = result.body.id; - log.debug(log.defaultContext(), 'credit the account with 20 --> loading actor to memory and changes quantity in memory'); - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - //get the account and check the quantity. - function finishTestAndCheck(result) { - log.debug(log.defaultContext(), 'get the actor and check the quantity is 20'); - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return done(); - } - }); - } - - }); - - it('Get actors balance not through REST', function (done) { - - var defaultOptions = { - 'ctx': { - 'remoteUser': 'admin', - 'tenantId': 'test-tenant' - } - }; - - var actorModel = loopback.findModel('TestAccount-test-tenant', defaultOptions); - - actorModel.prototype.getEnvelopeState(testAccountId, defaultOptions, function (err, result) { - if (err) { - log.error(err); - return done(err); - } - expect(result.state.stateObj.quantity).to.be.equal(20); - return done(); - }); - - }); - - it('clear actor memory', function (done) { - - var defaultOptions = { - 'ctx': { - 'remoteUser': 'admin', - 'tenantId': 'test-tenant' - } - }; - - var actorModel = loopback.findModel('TestAccount-test-tenant', defaultOptions); - - actorModel.findById(testAccountId, defaultOptions, function (err, result) { - if (err) { - log.error(err); - return done(err); - } - result.clearActorMemory(defaultOptions, function (err, cTime) { - if (err) { - log.error(err); - return done(err); - } - return done(); - }); - }); - }); - - it('Get actors with filter other than id --> actors from db', function (done) { - log.debug(log.defaultContext(), 'get the actor and check the quantity is 0'); - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"_type":"TestAccount"}}&access_token=' + accessToken) - .send() - .expect(200).end((err, res) => { - if (err) { - log.error(err); - return done(err); - } else { - res.body.forEach(function (element) { - expect(element._type).to.be.equal('TestAccount'); - }, this); - return done(); - } - }); - }); - - var deleteContext = { fetchAllScopes: true, ctx: { tenantId: 'test-tenant' } }; - - after('delete all the test accounts', function (done) { - var testAccount = loopback.getModel('TestAccount', bootstrap.defaultContext); - testAccount.destroyAll({}, deleteContext, function (err) { - if (err) { - log.error(err); - return done(err); - } else { - return done(); - } - }); - }); - - after('delete all the test transfers', function (done) { - var testTransfer = loopback.getModel('TestTransfer', bootstrap.defaultContext); - testTransfer.destroyAll({}, deleteContext, function (err) { - if (err) { - expect(err.message).to.be.equal('Cannot delete journal entry'); - return done(); - } else { - log.debug('deleted alltest transfers'); - return done(new Error('Should not be allowed to delete journal entries!')); - } - }); - }); - - after('delete all the test states', function (done) { - var state = loopback.getModel('State'); - state.destroyAll({}, deleteContext, function (err) { - if (err) { - log.error(err); - return done(err); - } else { - return done(); - } - }); - }); - - after('delete all modelDefinition models', function (done) { - // models.ModelDefinition.destroyAll({}, bootstrap.defaultContext, function(err, res) { - // if (err) { - // done(err); - // } else { - // done(); - // } - // }); - done(); - }); -}); diff --git a/test/z-z-z-actor-pattern-activity-check-test.js b/test/z-z-z-actor-pattern-activity-check-test.js deleted file mode 100644 index 63ead7b..0000000 --- a/test/z-z-z-actor-pattern-activity-check-test.js +++ /dev/null @@ -1,208 +0,0 @@ -/** - * - * ©2016-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * Author: Karin Angel - */ - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var loopback = require('loopback'); -var options = bootstrap.defaultContext; -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('actor-pattern-activity-check-test'); -var uuidv4 = require('uuid/v4'); - -xdescribe('Actor startUp Test', function () { - var modelDefinition = loopback.findModel('ModelDefinition'); - var actorModelInstance; - var actorModel; - var actorId; - var actorInstance; - var journalModelInstance; - var journalModel; - var journal1Id; - var journal2Id; - var data; - var afterTest = {}; - - before('create actor Model', function(done) { - var createActorModel = function(asyncCB) { - data = { - 'name': 'TestActivitiesActor', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 10 - } - }; - modelDefinition.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - actorModelInstance = res; - actorModel = loopback.getModel('TestActivitiesActor', options); - actorModel.prototype.atomicTypes = ['DEBIT']; - actorModel.prototype.nonAtomicTypes = ['CREDIT']; - - actorModel.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - actorModel.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - actorModel.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - actorModel.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - actorModel.prototype.stateObj = {quantity: 3000}; - - actorModel.prototype.associatedModels = ['TestActivitiesJournal']; - return asyncCB(); - }); - }; - var createActorInstance = function(asyncCB) { - data = {}; - actorModel.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - actorInstance = res; - actorId = res.id; - return asyncCB(); - }); - }; - var createJournalModel = function(asyncCB) { - data = { - 'name': 'TestActivitiesJournal', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - journalModelInstance = res; - journalModel = loopback.getModel('TestActivitiesJournal', options); - journalModel.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - return asyncCB(); - }); - }; - var createJournalInstance = function(asyncCB) { - data = { - "nonAtomicActivitiesList": [ - { - "entityId": actorId, - "payload": {"value": 1000}, - "modelName": actorModel.clientModelName, - "instructionType": "CREDIT" - } - ], - "atomicActivitiesList": [ - { - "entityId": actorId, - "payload": {"value": 1}, - "modelName": actorModel.clientModelName, - "instructionType": "DEBIT" - } - ], - "_version": uuidv4() - }; - var optionsWithLock = options; - journalModel.create(data, optionsWithLock, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - journalId = res.id; - afterTest[actorInstance.stateId] = 3999; - asyncCB(); - }); - }; - - async.series([createActorModel, createActorInstance, createJournalModel, createJournalInstance], function(err, res) { - done(err); - }); - }); - - xit('Check activities in db', function(done) { - var checkActivities = function(done) { - var activitiesModel = loopback.getModel('ActorActivity', options); - activitiesModel.find({"where": {"entityId": actorId}}, options, function (err, res) { - if (err) { - return done(err); - } else if (activitiesModel.getDataSource().connector.name === 'postgresql') { - expect(res.length).to.be.equal(2); - return done(); - } else { - expect(res.length).to.be.equal(0); - return done(); - } - }); - }; - checkActivities(done); - }); - - after('check state is updated against DB', function (done) { - var stateModel = loopback.getModel('State'); - async.retry({ times: 5 }, function (retrycb) { - async.eachOf(afterTest, function (value, stateId, cb) { - var query = { - where: { id: stateId } - }; - stateModel.find(query, bootstrap.defaultContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - if (res[0].stateObj.quantity === value) { - return cb(); - } else { - log.error(log.defaultContext(), 'quantity is: ', res[0].stateObj.quantity, ' but value is: ', value); - return cb(new Error('error in assertion against db')); - } - } - }); - }, function (err) { - if (err) { - return setTimeout(retrycb, 3000, err); - } else { - return retrycb(null, true); - } - }); - }, function (err, result) { - if (err) { - return done(err); - } else { - return done(); - } - }); - }); -}); \ No newline at end of file diff --git a/test/z-z-z-actor-pattern-aqcuire-db-lock-test-mongo.js b/test/z-z-z-actor-pattern-aqcuire-db-lock-test-mongo.js deleted file mode 100644 index 8bf7a7f..0000000 --- a/test/z-z-z-actor-pattern-aqcuire-db-lock-test-mongo.js +++ /dev/null @@ -1,246 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. - -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/* This is a collection of tests that make sure that the actor-pattern model and its' functions work when db lock mode is on. - * - * @author Karin Angel - */ -var loopback = require('loopback'); -var bootstrap = require('./bootstrap'); -var app = bootstrap.app; -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('actor-pattern-db-lock-tests'); -var api = bootstrap.api; -var async = require('async'); -var MongoClient = require('mongodb').MongoClient; -var mongoHost = process.env.MONGO_HOST || 'localhost'; -var dbName = process.env.DB_NAME || 'db'; - -var accessToken; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -xdescribe(chalk.blue('actor-pattern-db-lock-test'), function () { - this.timeout(40000); - var afterTest = {}; - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestAccount', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 1 - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - - function createTransferModel() { - var data = { - 'name': 'TestTransfer', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions() { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - - var accountDefinition = loopback.getModel('TestAccount', bootstrap.defaultContext); - - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - accountDefinition.prototype.associatedModels = ['TestTransfer']; - return done(); - } - }); - - it('Only actor pattern entities should acquire DB lock.', function(done) { - /** - * After creating an accout and commiting a deposit transaction, the db shold hold: - * - zero records for tnx entity (TestTransfers) lock, since it inherit base entity - * - one record for account entity (TestAccount) lock, since it inherit base actor entity - * implementaion : - * - base entity has dbLockRequired == false. - * - base actor entity has dbLockRequired== true. - */ - var dbname = 'db'; - var dataSource = app.datasources[dbname]; - if (dataSource.name !== 'mongodb') { - return done(); - } - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - var testAccountsId; - - function postTransaction(result) { - testAccountsId = result.body.id; - var postData = { - 'nonAtomicActivitiesList': [ - { - 'entityId': testAccountsId, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - var url = 'mongodb://' + mongoHost + ':27017/' + dbName; - MongoClient.connect(url, function (err, db) { - if (err) return done(err); - else { - db.collection("Lock").find().toArray(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - //console.log ("Results: " + res); - var testAccountLockCounter = 0; - var newModelName1 = loopback.findModel('TestAccount', bootstrap.defaultContext); - res.forEach(function (element) { - if (element.modelName === "TestTransfers") { - return done(new Error("Lock was acquired on entity where dbLockRequired == false")) - } else if (element.modelName === newModelName1.modelName && element.modelId == testAccountsId) testAccountLockCounter++; - }, this); - - if (testAccountLockCounter > 0) { - return done(); - } else { - return done(new Error("No TestAccounts Lock found, should be atleast one.")); - } - } - }); - } - }); - } - }); - - - after('check state is updated against DB', function (done) { - var stateModel = loopback.getModel('State'); - async.retry({ times: 5 }, function (retrycb) { - async.eachOf(afterTest, function (value, stateId, cb) { - var query = { - where: { id: stateId } - }; - stateModel.find(query, bootstrap.defaultContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - if (res[0].stateObj.quantity === value) { - return cb(); - } else { - log.error(log.defaultContext(), 'quantity is: ', res[0].stateObj.quantity, ' but value is: ', value); - return cb(new Error('error in assertion against db')); - } - } - }); - }, function (err) { - if (err) { - return setTimeout(retrycb, 3000, err); - } else { - return retrycb(null, true); - } - }); - }, function (err, result) { - if (err) { - return done(err); - } else { - return done(); - } - }); - }); - - after('unset the dbLock header', function (done) { - api.set('x-evproxy-db-lock', '0'); - return done(); - }); -}); \ No newline at end of file diff --git a/test/z-z-z-actor-pattern-db-lock-test.js b/test/z-z-z-actor-pattern-db-lock-test.js deleted file mode 100644 index 41a0332..0000000 --- a/test/z-z-z-actor-pattern-db-lock-test.js +++ /dev/null @@ -1,1294 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. - -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ -/* This is a collection of tests that make sure that the actor-pattern model and its' functions work when db lock mode is on. - * - * @author Karin Angel - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('actor-pattern-db-lock-tests'); -var api = bootstrap.api; -var async = require('async'); - -var accessToken; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -xdescribe(chalk.blue('actor-pattern-db-lock-test'), function () { - this.timeout(60000); - var afterTest = {}; - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestAccount', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 1 - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - - function createTransferModel() { - var data = { - 'name': 'TestTransfer', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions() { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - - var accountDefinition = loopback.getModel('TestAccount', bootstrap.defaultContext); - - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - accountDefinition.prototype.associatedModels = ['TestTransfer']; - return done(); - } - }); - - it('should create an account and deposit 20 into the account', function (done) { - - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - afterTest[res.body[0].stateId] = 20; - return done(); - } - }); - } - }); - - - it('should create 3 accounts deposit 20 into all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest( - '/TestAccounts/', - { 'stateObj': { 'quantity': 0 } }, - function (result) { - actor.result = result; - return cb(); - }, - done - ); - } - - function continueLogic() { - var postData = - [{ - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }, - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }, - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }]; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - async.each(actors, getActorAndCheck, continueLogicSecond); - } - - function getActorAndCheck(actor, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 20; - return cb(); - } - }); - } - - function continueLogicSecond(err) { - if (err) { - return done(err); - } else { - return done(); - } - } - }); - - it('should fail to debit from a new account', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - } else { - log.debug(log.defaultContext(), log.defaultContext(), 'the debit succeeded, altough it should not!'); - } - return done(); - }); - } - - }); - - it('should credit an account 20 and then debit the same account 10', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where": {"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - afterTest[res.body[0].stateId] = 10; - return done(); - } - }); - } - }); - - it('should credit an account 20 and then debit the same account 10 and then debit it 20 and fail', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, secondCheck, done); - } - - function secondCheck(result) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - return debitAndFail(res); - } - }); - } - - function debitAndFail(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - } else { - log.debug(log.defaultContext(), 'the debit succeeded, altough it should not!'); - } - return done(); - }); - } - }); - - it('should credit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount216', 'stateObj': { 'quantity': 0 } }, creditParallel, done); - - function creditParallel() { - var functionArray = []; - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount216', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - function creditFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 3; i++) { - functionArray.push(creditFactory()); - } - async.parallel(functionArray, finalCheck); - } - - function finalCheck(err) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount216"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(3); - afterTest[res.body[0].stateId] = 3; - done(); - } - }); - } - }); - - it('should debit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount17', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount17', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount17', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - function debitFactory() { - return function debitOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 3; i++) { - functionArray.push(debitFactory()); - } - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount17"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(7); - afterTest[res.body[0].stateId] = 7; - done(); - } - }); - } - }); - - it('should debit an account in parallel and then fail', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount18', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount18', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount18', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - function debitFactory() { - return function debitOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 10; i++) { - functionArray.push(debitFactory()); - } - async.parallel(functionArray, firstCheck); - } - - function firstCheck() { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount18"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(0); - return tryToDebitAndFail(); - } - }); - } - - function tryToDebitAndFail() { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount18', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - return done(); - }); - } - }); - - it('should debit and credit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount19', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount19', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postDataDebit = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount19', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - var postDataCredit = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount19', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - function debitFactory() { - return function debitOne(callback) { - apiRequest('/TestTransfers/', postDataDebit, function (data) { - callback(null, data); - }, done); - }; - } - - function creditFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postDataCredit, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 10; i++) { - functionArray.push(debitFactory()); - } - functionArray.push(creditFactory()); - - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount19"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(1); - afterTest[res.body[0].stateId] = 1; - done(); - } - }); - } - }); - - it('should create 3 accounts and then deposit 10 in all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest( - '/TestAccounts/', - { 'stateObj': { 'quantity': 0 } }, - function (result) { - actor.result = result; - return cb(); - }, - done - ); - } - - function continueLogic() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - async.each(actors, getActorAndCheck, continueLogicSecond); - } - - function getActorAndCheck(actor, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 10; - return cb(); - } - }); - - } - - function continueLogicSecond(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - it('should create 3 accounts and then deposit 10 in all of them and then withdraw 5 from all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest( - '/TestAccounts/', - { 'stateObj': { 'quantity': 0 } }, - function (result) { - actor.result = result; - return cb(); - }, - done - ); - } - - function continueLogic() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midCheck, done); - } - - function midCheck(result) { - async.each(actors, getActorAndmidCheck, continueLogicSecond); - } - - function getActorAndmidCheck(actor, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - return cb(); - } - }); - } - - function continueLogicSecond(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - debitAccount(); - } - } - - - function debitAccount() { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - async.each(actors, getActorsAndFinalCheck, continueLogicThird); - } - - function getActorsAndFinalCheck(actor, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(5); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 5; - return cb(); - } - }); - } - - function continueLogicThird(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - it('should not be able to modify a transaction', function (done) { - - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, modifyTrans, done); - } - - function modifyTrans(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - postData._version = result.body._version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .put(bootstrap.basePath + '/TestTransfers/' + result.body.id + '?access_token=' + accessToken) - .send(postData) - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - // expect(res.body.error.message).to.be.equal('Cannot update existing journal entry'); - expect(res.body.error.message).to.deep.include('has no method handling PUT'); - return done(); - } - }); - } - }); - - it('should delete an account ', function (done) { - var postData = { - id: 'TestAccount116', - 'stateObj': { - 'quantity': 0 - } - }; - var version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .post(bootstrap.basePath + '/TestAccounts/' + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - version = res.body._version; - return postTransaction(); - } - }); - - function postTransaction() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount116', - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, deleteAccount, done); - } - - function deleteAccount() { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .delete(bootstrap.basePath + '/TestAccounts/TestAccount116/' + version + '?access_token=' + accessToken) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return finishTestAndCheck(); - } - }); - } - - function finishTestAndCheck() { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + postData.entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body.length).to.be.equal(0); - return done(); - } - }); - } - }); - - it('should create 9 accounts in parallel. Then credit them with 1000 in parallel. Then debit 3*10 from all of them in parallel', function (done) { - - var ids = []; - ids.push('aa1'); - ids.push('bb1'); - ids.push('cc1'); - ids.push('dd1'); - ids.push('ee1'); - ids.push('ff1'); - ids.push('gg1'); - ids.push('hh1'); - ids.push('ii1'); - - var createData = { - stateObj: { quantity: 0 } - }; - var functionArray = []; - - function actorFactory(id) { - return function createOne(callback) { - createData.id = id; - apiRequest('/TestAccounts/', createData, function (data) { - callback(null, data); - }, done); - }; - } - - ids.forEach(function (id) { - functionArray.push(actorFactory(id)); - }); - - async.parallel(functionArray, addInitialBudjet); - - function addInitialBudjet() { - var addBudjetData = { - 'payload': { 'value': 1000 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }; - - var addTrans = {}; - addTrans.nonAtomicActivitiesList = []; - - ids.forEach(function (id) { - var activity = JSON.parse(JSON.stringify(addBudjetData)); - activity.entityId = id; - addTrans.nonAtomicActivitiesList.push(activity); - }); - - apiRequest('/TestTransfers/', addTrans, midCheck, done); - } - - function midCheck() { - async.each(ids, getActorsAndMidCheck, continueMidLogic); - } - - function getActorsAndMidCheck(id, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(1000); - expect(res.body[0].id).to.be.equal(id); - return cb(); - } - }); - } - - function continueMidLogic(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - atomicTransactions(); - } - } - - function atomicTransactions() { - var debitData = { - 'payload': { 'value': 3 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }; - - var reduceTrans = {}; - reduceTrans.atomicActivitiesList = []; - - ids.forEach(function (id) { - var activity = JSON.parse(JSON.stringify(debitData)); - activity.entityId = id; - reduceTrans.atomicActivitiesList.push(activity); - }); - - var functionArray = []; - - function debitFactory() { - return function debitAll(callback) { - apiRequest('/TestTransfers/', reduceTrans, function (data) { - callback(null, data); - }, done); - }; - } - - for (var y = 0; y < 10; y++) { - functionArray.push(debitFactory()); - } - - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - async.each(ids, getActorsAndFinalCheck, continueFinalLogic); - } - - function getActorsAndFinalCheck(id, cb) { - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '1') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(970); - expect(res.body[0].id).to.be.equal(id); - afterTest[res.body[0].stateId] = 970; - return cb(); - } - }); - } - - function continueFinalLogic(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - after('check state is updated against DB', function (done) { - var stateModel = loopback.getModel('State'); - async.retry({ times: 5 }, function (retrycb) { - async.eachOf(afterTest, function (value, stateId, cb) { - var query = { - where: { id: stateId } - }; - var dbLockContext = { - ctx: { - tenantId: 'test-tenant', - remoteUser: 'test-user' - }, - lockMode : 'dbLock' - }; - stateModel.find(query, dbLockContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - if (res[0].stateObj.quantity === value) { - return cb(); - } else { - log.error(log.defaultContext(), 'quantity is: ', res[0].stateObj.quantity, ' but value is: ', value); - return cb(new Error('error in assertion against db')); - } - } - }); - }, function (err) { - if (err) { - return setTimeout(retrycb, 15000, err); - } else { - return retrycb(null, true); - } - }); - }, function (err, result) { - if (err) { - return done(err); - } else { - return done(); - } - }); - }); - - after('unset the dbLock header', function (done) { - api.set('x-evproxy-db-lock', '0'); - return done(); - }); -}); \ No newline at end of file diff --git a/test/z-z-z-actor-pattern-test.js b/test/z-z-z-actor-pattern-test.js deleted file mode 100644 index 6f1c0af..0000000 --- a/test/z-z-z-actor-pattern-test.js +++ /dev/null @@ -1,1274 +0,0 @@ -/* -©2015-2016 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), Bangalore, India. All Rights Reserved. - -The EdgeVerve proprietary software program ("Program"), is protected by copyrights laws, international treaties and other pending or existing intellectual property rights in India, the United States and other countries. -The Program may contain/reference third party or open source components, the rights to which continue to remain with the applicable third party licensors or the open source community as the case may be and nothing here transfers the rights to the third party and open source components, except as expressly permitted. -Any unauthorized reproduction, storage, transmission in any form or by any means (including without limitation to electronic, mechanical, printing, photocopying, recording or otherwise), or any distribution of this Program, or any portion of it, may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under the law. -*/ - -/* This is a collection of tests that make sure that the actor-pattern model and its' functions work. - * - * @author Ori Press - */ -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var uuidv4 = require('uuid/v4'); -var chai = require('chai'); -var expect = chai.expect; -var logger = require('oe-logger'); -var log = logger('actor-pattern-tests'); -var api = bootstrap.api; -var async = require('async'); - -var accessToken; - -function apiRequest(url, postData, callback, done) { - var version = uuidv4(); - postData._version = version; - api - .set('Accept', 'application/json') - .set('x-evproxy-db-lock', '0') - .post(bootstrap.basePath + url + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - log.error(log.defaultContext(), err || (new Error(JSON.stringify(res.body.error)))); - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return callback(res); - } - }); -} - -xdescribe(chalk.blue('actor-pattern-test'), function () { - this.timeout(40000); - var afterTest = {}; - - before('login using testuser', function fnLogin(done) { - var sendData = { - 'username': 'testuser', - 'password': 'testuser123' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - accessToken = res.body.id; - return done(); - } - }); - }); - - - before('create test models', function createModels(done) { - var modelDefinition = loopback.findModel('ModelDefinition'); - var data = { - 'name': 'TestAccount', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 10 - } - }; - - modelDefinition.create(data, bootstrap.defaultContext, createTransferModel); - - function createTransferModel() { - var data = { - 'name': 'TestTransfer', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, bootstrap.defaultContext, addAllFunctions); - } - - function addAllFunctions() { - - var transferDefinition = loopback.getModel('TestTransfer', bootstrap.defaultContext); - transferDefinition.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - - var accountDefinition = loopback.getModel('TestAccount', bootstrap.defaultContext); - - accountDefinition.prototype.atomicTypes = ['DEBIT']; - accountDefinition.prototype.nonAtomicTypes = ['CREDIT']; - - accountDefinition.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - accountDefinition.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - accountDefinition.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - accountDefinition.prototype.associatedModels = ['TestTransfer']; - return done(); - } - }); - - it('should create an account and deposit 20 into the account', function (done) { - - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - afterTest[res.body[0].stateId] = 20; - return done(); - } - }); - } - }); - - it('should create 3 accounts deposit 20 into all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, function (result) { - actor.result = result; - return cb(); - }, done); - } - - function continueLogic() { - var postData = - [{ - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }, - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }, - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }]; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - async.each(actors, getActorAndCheck, continueLogicSecond); - } - - function getActorAndCheck(actor, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 20; - return cb(); - } - }); - } - - function continueLogicSecond(err) { - if (err) { - return done(err); - } else { - return done(); - } - } - }); - - it('should fail to debit from a new account', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - } else { - log.debug(log.defaultContext(), 'the debit succeeded, altough it should not!'); - } - return done(); - }); - } - - }); - - it('should credit an account 20 and then debit the same account 10', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - afterTest[res.body[0].stateId] = 10; - return done(); - } - }); - } - }); - - it('should credit an account 20 and then debit the same account 10 and then debit it 20 and fail', function (done) { - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midTestCheck, done); - } - - function midTestCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.nonAtomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(20); - expect(res.body[0].id).to.be.equal(result.body.nonAtomicActivitiesList[0].entityId); - return debitAccount(res); - } - }); - } - - function debitAccount(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, secondCheck, done); - } - - function secondCheck(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + result.body.atomicActivitiesList[0].entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(result.body.atomicActivitiesList[0].entityId); - return debitAndFail(res); - } - }); - } - - function debitAndFail(result) { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': result.body[0].id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - } else { - log.debug(log.defaultContext(), 'the debit succeeded, altough it should not!'); - } - return done(); - }); - } - }); - - it('should credit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount6', 'stateObj': { 'quantity': 0 } }, creditParallel, done); - - function creditParallel() { - var functionArray = []; - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount6', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - function creditFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 3; i++) { - functionArray.push(creditFactory()); - } - async.parallel(functionArray, finalCheck); - } - - function finalCheck(err) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount6"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(3); - afterTest[res.body[0].stateId] = 3; - done(); - } - }); - } - }); - - it('should debit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount7', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount7', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount7', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - function debitFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 3; i++) { - functionArray.push(debitFactory()); - } - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount7"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(7); - afterTest[res.body[0].stateId] = 7; - done(); - } - }); - } - }); - - it('should debit an account in parallel and then fail', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount8', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount8', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount8', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - function debitFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postData, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 10; i++) { - functionArray.push(debitFactory()); - } - async.parallel(functionArray, firstCheck); - } - - function firstCheck() { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount8"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(0); - return tryToDebitAndFail(); - } - }); - } - - function tryToDebitAndFail() { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount8', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestTransfers/?access_token=' + accessToken) - .send(postData) - .expect(500).end(function (err, res) { - return done(); - }); - } - }); - - it('should debit and credit an account in parallel', function (done) { - apiRequest('/TestAccounts/', { 'id': 'TestAccount9', 'stateObj': { 'quantity': 0 } }, creditAccount, done); - function creditAccount() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount9', - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - apiRequest('/TestTransfers/', postData, debitParallel, done); - } - - function debitParallel() { - var functionArray = []; - var postDataDebit = - { - 'atomicActivitiesList': [ - { - 'entityId': 'TestAccount9', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - } - ] - }; - - var postDataCredit = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount9', - 'payload': { 'value': 1 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - function debitFactory() { - return function debitOne(callback) { - apiRequest('/TestTransfers/', postDataDebit, function (data) { - callback(null, data); - }, done); - }; - } - - function creditFactory() { - return function creditOne(callback) { - apiRequest('/TestTransfers/', postDataCredit, function (data) { - callback(null, data); - }, done); - }; - } - - for (var i = 0; i < 10; i++) { - functionArray.push(debitFactory()); - } - functionArray.push(creditFactory()); - - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "TestAccount9"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - done(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(1); - afterTest[res.body[0].stateId] = 1; - done(); - } - }); - } - }); - - it('should create 3 accounts and then deposit 10 in all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest( - '/TestAccounts/', - { 'stateObj': { 'quantity': 0 } }, - function (result) { - actor.result = result; - return cb(); - }, - done - ); - } - - function continueLogic() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - ] - }; - - apiRequest('/TestTransfers/', postData, finishTestAndCheck, done); - } - - function finishTestAndCheck(result) { - async.each(actors, getActorAndCheck, continueLogicSecond); - } - - function getActorAndCheck(actor, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 10; - return cb(); - } - }); - - } - - function continueLogicSecond(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - it('should create 3 accounts and then deposit 10 in all of them and then withdraw 5 from all of them', function (done) { - var actors = [{}, {}, {}]; - - async.each(actors, createActor, continueLogic); - - function createActor(actor, cb) { - apiRequest( - '/TestAccounts/', - { 'stateObj': { 'quantity': 0 } }, - function (result) { - actor.result = result; - return cb(); - }, - done - ); - } - - function continueLogic() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 10 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, midCheck, done); - } - - function midCheck(result) { - async.each(actors, getActorAndmidCheck, continueLogicSecond); - } - - function getActorAndmidCheck(actor, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(10); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - return cb(); - } - }); - } - - function continueLogicSecond(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - debitAccount(); - } - } - - - function debitAccount() { - var postData = - { - 'atomicActivitiesList': [ - { - 'entityId': actors[0].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - { - 'entityId': actors[1].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - { - 'entityId': actors[2].result.body.id, - 'payload': { 'value': 5 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }, - ] - }; - - apiRequest('/TestTransfers/', postData, finalCheck, done); - } - - function finalCheck(result) { - async.each(actors, getActorsAndFinalCheck, continueLogicThird); - } - - function getActorsAndFinalCheck(actor, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + actor.result.body.id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(5); - expect(res.body[0].id).to.be.equal(actor.result.body.id); - afterTest[res.body[0].stateId] = 5; - return cb(); - } - }); - } - - function continueLogicThird(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - it('should not be able to modify a transaction', function (done) { - - apiRequest('/TestAccounts/', { 'stateObj': { 'quantity': 0 } }, postTransaction, done); - - function postTransaction(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, modifyTrans, done); - } - - function modifyTrans(result) { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': result.body.id, - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - postData._version = result.body._version; - api - .set('Accept', 'application/json') - .put(bootstrap.basePath + '/TestTransfers/' + result.body.id + '?access_token=' + accessToken) - .send(postData) - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - // expect(res.body.error.message).to.be.equal('Cannot update existing journal entry'); - expect(res.body.error.message).to.deep.include('has no method handling PUT') - return done(); - } - }); - } - }); - - it('should create 9 accounts in parallel. Then credit them with 1000 in parallel. Then debit 3*10 from all of them in parallel', function (done) { - - var ids = []; - ids.push('a'); - ids.push('b'); - ids.push('c'); - ids.push('d'); - ids.push('e'); - ids.push('f'); - ids.push('g'); - ids.push('h'); - ids.push('i'); - - var createData = { - stateObj: { quantity: 0 } - }; - var functionArray = []; - - function actorFactory(id) { - return function createOne(callback) { - createData.id = id; - apiRequest('/TestAccounts/', createData, function (data) { - callback(null, data); - }, done); - }; - } - - ids.forEach(function (id) { - functionArray.push(actorFactory(id)); - }); - - async.parallel(functionArray, addInitialBudjet); - - function addInitialBudjet() { - var addBudjetData = { - 'payload': { 'value': 1000 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - }; - - var addTrans = {}; - addTrans.nonAtomicActivitiesList = []; - - ids.forEach(function (id) { - var activity = JSON.parse(JSON.stringify(addBudjetData)); - activity.entityId = id; - addTrans.nonAtomicActivitiesList.push(activity); - }); - - apiRequest('/TestTransfers/', addTrans, midCheck, done); - } - - function midCheck() { - async.each(ids, getActorsAndMidCheck, continueMidLogic); - } - - function getActorsAndMidCheck(id, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(1000); - expect(res.body[0].id).to.be.equal(id); - return cb(); - } - }); - } - - function continueMidLogic(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - atomicTransactions(); - } - } - - function atomicTransactions() { - var debitData = { - 'payload': { 'value': 3 }, - 'modelName': 'TestAccount', - 'instructionType': 'DEBIT' - }; - - var reduceTrans = {}; - reduceTrans.atomicActivitiesList = []; - - ids.forEach(function (id) { - var activity = JSON.parse(JSON.stringify(debitData)); - activity.entityId = id; - reduceTrans.atomicActivitiesList.push(activity); - }); - - var functionArray = []; - - function debitFactory() { - return function debitAll(callback) { - apiRequest('/TestTransfers/', reduceTrans, function (data) { - callback(null, data); - }, done); - }; - } - - for (var y = 0; y < 10; y++) { - functionArray.push(debitFactory()); - } - - async.parallel(functionArray, finalCheck); - } - - function finalCheck() { - async.each(ids, getActorsAndFinalCheck, continueFinalLogic); - } - - function getActorsAndFinalCheck(id, cb) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + id + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - expect(res.body[0].state.stateObj.quantity).to.be.equal(970); - expect(res.body[0].id).to.be.equal(id); - afterTest[res.body[0].stateId] = 970; - return cb(); - } - }); - } - - function continueFinalLogic(err) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - return done(); - } - } - }); - - it('should delete an account ', function (done) { - var postData = { - id: 'TestAccount16', - 'stateObj': { - 'quantity': 0 - } - }; - var version; - api - .set('Accept', 'application/json') - .post(bootstrap.basePath + '/TestAccounts/' + '?access_token=' + accessToken) - .send(postData) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - version = res.body._version; - return postTransaction(); - } - }); - - function postTransaction() { - var postData = - { - 'nonAtomicActivitiesList': [ - { - 'entityId': 'TestAccount16', - 'payload': { 'value': 20 }, - 'modelName': 'TestAccount', - 'instructionType': 'CREDIT' - } - ] - }; - - apiRequest('/TestTransfers/', postData, deleteAccount, done); - } - - function deleteAccount() { - api - .set('Accept', 'application/json') - .delete(bootstrap.basePath + '/TestAccounts/TestAccount16/' + version + '?access_token=' + accessToken) - .end(function (err, res) { - if (err || res.body.error) { - return done(err || (new Error(JSON.stringify(res.body.error)))); - } else { - return finishTestAndCheck(); - } - }); - } - - function finishTestAndCheck() { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/TestAccounts?filter={"where":{"id": "' + postData.entityId + '"}}&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body.length).to.be.equal(0); - return done(); - } - }); - } - }); - - it('make sure state is not rest enabled', function (done) { - return apiRequest('/TestAccounts/', { 'id': 'TestAccount300', 'stateObj': { 'quantity': 0 } }, checkState, done); - - function checkState(result) { - api - .set('Accept', 'application/json') - .get(bootstrap.basePath + '/State/' + result.body.id + '&access_token=' + accessToken) - .send() - .end((err, res) => { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } else { - expect(res.body.error.message).to.contain('There is no method to handle GET /State'); - return done(); - } - }); - } - }); - - after('check state is updated against DB', function (done) { - var stateModel = loopback.getModel('State'); - async.retry({ times: 5 }, function (retrycb) { - async.eachOf(afterTest, function (value, stateId, cb) { - var query = { - where: { id: stateId } - }; - stateModel.find(query, bootstrap.defaultContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - if (res[0].stateObj.quantity === value) { - return cb(); - } else { - log.error(log.defaultContext(), 'quantity is: ', res[0].stateObj.quantity, ' but value is: ', value); - return cb(new Error('error in assertion against db')); - } - } - }); - }, function (err) { - if (err) { - return setTimeout(retrycb, 3000, err); - } else { - return retrycb(null, true); - } - }); - }, function (err, result) { - if (err) { - return done(err); - } else { - return done(); - } - }); - }); -}); \ No newline at end of file diff --git a/test/z-z-z-actor-startup-test.js b/test/z-z-z-actor-startup-test.js deleted file mode 100644 index a65e517..0000000 --- a/test/z-z-z-actor-startup-test.js +++ /dev/null @@ -1,296 +0,0 @@ -/** - * - * ©2016-2018 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * Author: Karin Angel - */ - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var uuidv4 = require('uuid/v4'); -var expect = chai.expect; -var app = bootstrap.app; -var models = bootstrap.models; -var loopback = require('loopback'); -var postgresHost = process.env.POSTGRES_HOST || 'localhost'; -var os = require('os'); -var host = process.env.HOSTNAME || os.hostname(); -var dsname = 'db'; -var dbName = process.env.DB_NAME || dsname; -var dataSource; -var options = bootstrap.defaultContext; -var async = require('async'); -var logger = require('oe-logger'); -var log = logger('actor-pattern-startup-test'); - -xdescribe('Actor startUp Test', function () { - var modelDefinition = loopback.findModel('ModelDefinition'); - var actorModelInstance; - var actorModel; - var actorId; - var dummyActorId; - var dummyActorInstance; - var journalModelInstance; - var journalModel; - var journal1Id; - var journal2Id; - var data; - var journal3Id; - var actorInstance; - var afterTest = {}; - - before('create actor Model', function(done) { - var createActorModel = function(asyncCB) { - data = { - 'name': 'TestStartUpActor', - 'base': 'BaseActorEntity', - 'options': { - stateThreshold: 10 - } - }; - modelDefinition.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - actorModelInstance = res; - actorModel = loopback.getModel('TestStartUpActor', options); - actorModel.prototype.atomicTypes = ['DEBIT']; - actorModel.prototype.nonAtomicTypes = ['CREDIT']; - - actorModel.prototype.validateCondition = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - return stateObj.quantity >= activity.payload.value; - } - }; - - actorModel.prototype.atomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'DEBIT') { - stateObj.quantity = stateObj.quantity - activity.payload.value; - return stateObj; - } - }; - - actorModel.prototype.nonAtomicInstructions = function (stateObj, activity) { - if (activity.instructionType === 'CREDIT') { - stateObj.quantity = stateObj.quantity + activity.payload.value; - return stateObj; - } - }; - - actorModel.prototype.processPendingMessage = function (message, stateObj) { - if (message.instructionType === 'CREDIT') { - stateObj.quantity += message.payload.value; - } else if (message.instructionType === 'DEBIT') { - stateObj.quantity -= message.payload.value; - } - return stateObj; - }; - - actorModel.prototype.stateObj = {quantity: 3000}; - - actorModel.prototype.associatedModels = ['TestStartUpJournal']; - return asyncCB(); - }); - }; - var createActorInstance = function(asyncCB) { - data = {}; - actorModel.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - actorId = res.id; - actorInstance = res; - return asyncCB(); - }); - }; - var createDummyActorInstance = function(asyncCB) { - data = {}; - actorModel.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - dummyActorId = res.id; - dummyActorInstance = res; - return asyncCB(); - }); - }; - var createJournalModel = function(asyncCB) { - data = { - 'name': 'TestStartUpJournal', - 'base': 'BaseJournalEntity' - }; - modelDefinition.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - journalModelInstance = res; - journalModel = loopback.getModel('TestStartUpJournal', options); - journalModel.prototype.performBusinessValidations = function (options, ctx, cb) { - cb(); - }; - return asyncCB(); - }); - }; - var postJournalInstanceThroughLoopback = function (asyncCB) { - data = { - "nonAtomicActivitiesList": [ - { - "entityId": dummyActorId, - "payload": {"value": 1000}, - "modelName": actorModel.clientModelName, - "instructionType": "CREDIT" - } - ], - "_version": uuidv4() - }; - journalModel.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - journal3Id = res.id; - return asyncCB(); - }); - }; - var createJournalInstance = function(asyncCB) { - var connectionString = "postgres://postgres:postgres@" + postgresHost + ":5432/" + dbName; - var pg = require('pg'); - var client = new pg.Client(connectionString); - client.connect(function (err) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - var finalQueryStr = "INSERT INTO public.\"" + journalModel.modelName.toLowerCase() + "\"(_version,startup,atomicactivitieslist,nonatomicactivitieslist,_type,_createdby,_modifiedby,_createdon,_modifiedon,_scope,_autoscope,_hostname,_fsctx) VALUES($1,$2,$3,$4,$5,$6,$7,$8::TIMESTAMP WITH TIME ZONE,$9::TIMESTAMP WITH TIME ZONE,$10,$11,$12,$13) RETURNING \"id\""; - var _version = 1; - var startup = "undefinedTestStartUpActor-test-tenant" + actorId; - var atomicactivitieslist = []; - var nonatomicactivitieslist = [ - { - seqNum: 1, - entityId: actorId, - modelName: actorModel.modelName, - payload: {value:10000}, - instructionType: "CREDIT" - } - ]; - var _type = journalModel.modelName; - var _createdby = "test-user"; - var _modifiedby = "test-user"; - var _createdon = new Date(); - var _modifiedon = new Date(); - var _scope = ["tenantId:test-tenant"]; - var _autoscope = { - tenantId: "test-tenant" - }; - var _hostname = host; - var _fsctx = { - options: { - ctx: { - tenantId: "test-tenant", - remoteUser: "test-user" - }, - exactMatch: false, - whereKeysModelDefinition: [], - model: "State" - }, - isNewInstance:true - }; - _fsctx.options['whereKeysTestStartUpActor-test-tenant'] = []; - var finalParamsStr = [_version, startup, atomicactivitieslist, nonatomicactivitieslist, _type, _createdby, _modifiedby, _createdon, _modifiedon, _scope, _autoscope, _hostname, _fsctx]; - var query = client.query(finalQueryStr, finalParamsStr , function (err, result) { - if (err) { - log.error(log.defaultContext(), err); - return asyncCB(err); - } - return asyncCB(); - }); - }); - }; - - if (app.dataSources.db.connector.name === "postgresql") { - async.series([createActorModel, createActorInstance, createDummyActorInstance, createJournalModel, postJournalInstanceThroughLoopback, createJournalInstance], function(err, res) { - done(err); - }); - } else { - return done(); - } - }); - - xit('Check Strat Up', function(done) { - var createJournalInstanceAndCheckBalance = function(done) { - data = { - "nonAtomicActivitiesList": [ - { - "entityId": actorId, - "payload": {"value": 1000}, - "modelName": actorModel.clientModelName, - "instructionType": "CREDIT" - } - ], - "_version": uuidv4() - }; - journalModel.create(data, options, function(err, res) { - if (err) { - log.error(log.defaultContext(), err); - return done(err); - } - journal2Id = res.id; - afterTest[actorInstance.stateId] = 14000; - return done(); - }); - }; - if (app.dataSources.db.connector.name === "postgresql") { - createJournalInstanceAndCheckBalance(done); - } else { - return done(); - } - }); - - after('check state is updated against DB', function (done) { - if (app.dataSources.db.connector.name === "postgresql") { - var stateModel = loopback.getModel('State'); - async.retry({ times: 5 }, function (retrycb) { - async.eachOf(afterTest, function (value, stateId, cb) { - var query = { - where: { id: stateId } - }; - stateModel.find(query, bootstrap.defaultContext, function (err, res) { - if (err) { - log.error(log.defaultContext(), err); - return cb(err); - } else { - if (res[0].stateObj.quantity === value) { - return cb(); - } else { - log.error(log.defaultContext(), 'quantity is: ', res[0].stateObj.quantity, ' but value is: ', value); - return cb(new Error('error in assertion against db')); - } - } - }); - }, function (err) { - if (err) { - return setTimeout(retrycb, 3000, err); - } else { - return retrycb(null, true); - } - }); - }, function (err, result) { - if (err) { - return done(err); - } else { - return done(); - } - }); - } else { - return done(); - } - }); -}); \ No newline at end of file diff --git a/test/z-z-z-logger-config-test.js b/test/z-z-z-logger-config-test.js deleted file mode 100644 index 1b840db..0000000 --- a/test/z-z-z-logger-config-test.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ - -var loopback = require('loopback'); -var chalk = require('chalk'); -var bootstrap = require('./bootstrap'); -var loggerConfigUrl = bootstrap.basePath + '/LoggerConfigs/'; -var loggerModule = require('oe-logger'); -var api = bootstrap.api; - -var debug = require('debug')('logger-config-test'); -var accessToken; -var originalLogConfig; - - -var levelMap = { - 'trace': 10, - 'debug': 20, - 'info': 30, - 'warn': 40, - 'error': 50, - 'fatal': 60 -}; - -describe(chalk.blue('logger-config-test'), function () { - - this.timeout(10000); - - before('login using admin', function fnLogin(done) { - var sendData = { - 'username': 'admin', - 'password': 'admin' - }; - - api - .post(bootstrap.basePath + '/BaseUsers/login') - .send(sendData) - .expect(200).end(function (err, res) { - if (err) { - return done(err); - } else { - //console.log("acces token is ", res.body.id); - accessToken = res.body.id; - var loggerModel = loopback.findModel('LoggerConfig'); - loggerModel.findOne({}, { tenantId: 'default' }, function (err, model) { - if (err) { - console.log('unable to save the original loggerConfig'); - return done(new Error('unabled to save the original loggerConfig')); - } - originalLogConfig = model; - return done(); - }); - } - }); - }); - - it('should change the logger configuration of a single test logger', function (done) { - var testLogger = loggerModule('test-logger'); - testLogger.debug(''); - var postData = { - 'data': { 'test-logger': 'info' } - }; - - api - .set('Accept', 'application/json') - .post(loggerConfigUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - console.log('res body error is : ', res.body.error); - return done(err || (new Error(res.body.error))); - } else { - setTimeout(function () { - var checkerArray = (loggerModule('LOGGER-CONFIG')).getLoggers(); //get array of loggers to check them - if (checkerArray['test-logger'].level === levelMap.info) { - done(); - } else { - return done(new Error('Logger level not changed')); - } - }, 1500); - } - }); - }); - - it('should change the logger configuration of all the loggers', function (done) { - var testLogger = loggerModule('test-logger'); - testLogger.debug(''); - var postData = { - 'data': { 'all': 'warn' } - }; - - api - .set('Accept', 'application/json') - .post(loggerConfigUrl + '?access_token=' + accessToken) - .send(postData) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - console.log('res body error is : ', res.body.error); - return done(err || (new Error(res.body.error))); - } else { - setTimeout(function () { - var checkerArray = (loggerModule('LOGGER-CONFIG')).getLoggers(); //get array of loggers to check them - Object.keys(checkerArray).forEach(function (key) { - if (checkerArray[key].level !== levelMap.warn) { - return done(new Error('Logger levels unchanged in all test')); - } - }); - return done(); - }, 1500); - } - }); - }); - - it('should try fetching the list of all loggers', function (done) { - var loggerArray = loggerModule('LOGGER-CONFIG').getLoggers(); - api - .set('Accept', 'application/json') - .post(loggerConfigUrl + '/list' + '?access_token=' + accessToken) - .expect(200).end(function (err, res) { - debug('response body : ' + JSON.stringify(res.body, null, 4)); - if (err || res.body.error) { - console.log('res body error is : ', res.body.error); - return done(err || (new Error(res.body.error))); - } else { - var bodyObj = res.body.Loggers; - Object.keys(loggerArray).forEach(function (value) { - if (!(bodyObj[value] && (levelMap[bodyObj[value]] === (loggerArray[value]).level))) { - return done(new Error('Problem when trying to list all loggers via LoggerConfig\'s remote method')); - } - }); - return done(); - } - }); - }); - - it('set logging in header', function (done) { - api - .set('Accept', 'application/json') - .set('logging', 10) - .get('/api/Literals') - .end(function (err, resp) { - // You will see log statements on console - // So not really a test case, but example - done(err); - }); - }); - - // after('restore the original log configuration', function (done) { - // var loggerModel = loopback.findModel('LoggerConfig'); - // loggerModel.destroyAll({}, { tenantId: 'default' }, function (err) { - // if (err) { - // console.log('unable to destroy loggerConfig models'); - // return done(new Error('unable to destory loggerConfig models')); - // } - // else { - // loggerModel.create(originalLogConfig, { tenantId: 'default' }, function (err) { - // if (err) { - // console.log('unable to restore the original LoggerConfig'); - // return done(new Error('unable to restore the original LoggerConfig')); - // } - // return done(); - // }); - // } - // }); - // }); -}); \ No newline at end of file diff --git a/test/z-z-z-z-mark-as-cache-able-test.js b/test/z-z-z-z-mark-as-cache-able-test.js deleted file mode 100644 index 78cabdc..0000000 --- a/test/z-z-z-z-mark-as-cache-able-test.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * - * ©2016-2017 EdgeVerve Systems Limited (a fully owned Infosys subsidiary), - * Bangalore, India. All Rights Reserved. - * - */ -/** - * This test is for unit-testing the query result caching feature in the framework. - * The test involves creating a test model, inserting a record into it, fetching the - * record (so that it caches), deleting the record from the database by directly accessing - * the DB (bypassing the framework, so that cache is not ecicted), fetching the - * record again to see that the records are still fetched (from cache). - * - * Author: Karin Angel - */ - -var bootstrap = require('./bootstrap'); -var chai = bootstrap.chai; -var expect = chai.expect; -var MarkAsCacheable = require('../server/boot/05_mark-as-cache-able'); -var config = require('../server/config'); - - -describe('Mark As Cache Able Test', function () { - var app1 = {}; - app1.models = {modelToCache: 'mockModelToCache'}; - - var app2 = {}; - app2.models = {modelNotToCache: 'mockModelMyModelName'}; - - var app3 = {}; - app3.models = {modelToCache: 'mockModelToCache'}; - - var disablecachingOld = config.disablecaching; - config.disablecaching = false; - var modelstocacheOld = config.modelstocache; - var evcachablesOld = global.evcacheables; - delete global.evcacheables; - config.modelstocache = ['modelToCache']; - - it('should add cachable model name to globals', function (done) { - config.disablecaching = false; - MarkAsCacheable(app1, function(err) { - if (err) { - return done(err); - } else { - expect(global.evcacheables['modelToCache']).to.be.equal(true); - return done(); - } - }); - }); - - it('should not add non cachable model name to globals', function (done) { - config.disablecaching = false; - MarkAsCacheable(app2, function(err) { - if (err) { - return done(err); - } else { - expect(global.evcacheables['modelNotToCache']).to.be.equal(undefined); - return done(); - } - }); - }); - - it('should not add cachable model name to globals if disablecaching flag is true', function (done) { - config.disablecaching = true; - delete global.evcacheables['modelToCache']; - MarkAsCacheable(app3, function(err) { - if (err) { - return done(err); - } else { - expect(global.evcacheables['modelToCache']).to.be.equal(undefined); - return done(); - } - }); - }); - - after('mark disablecaching flag in config to true and set modelstocache to original', function (done) { - config.disablecaching = disablecachingOld; - config.modelstocache = modelstocacheOld; - global.evcacheables = evcachablesOld; - return done(); - }); -}); \ No newline at end of file diff --git a/tools/cleanPostgresDB.bat b/tools/cleanPostgresDB.bat deleted file mode 100644 index 2dd5f24..0000000 --- a/tools/cleanPostgresDB.bat +++ /dev/null @@ -1,7 +0,0 @@ -set OLD_PATH=%PATH% -set PATH=C:\Program Files\PostgreSQL\9.6\bin;%PATH% - -set PGPASSWORD=postgres -dropdb -U postgres db -set PGPASSWORD= -set PATH=%OLD_PATH% \ No newline at end of file diff --git a/tools/cleandb.bat b/tools/cleandb.bat deleted file mode 100644 index ef0f70e..0000000 --- a/tools/cleandb.bat +++ /dev/null @@ -1,6 +0,0 @@ -set OLD_PATH=%PATH% -set PATH=C:\Program Files\MongoDB\Server\3.0\bin;D:\mongodb\bin;D:\bin\mongodb-win32-i386-3.0.7\bin;C:\Program Files\MongoDB\Server\3.2\bin;D:\mongodb\bin;C:\Program Files\MongoDB\Server\3.4\bin;%PATH% - -mongo server/dropdb.js - -set PATH=%OLD_PATH% \ No newline at end of file From 9b21623c06292158f366a14e5fd9328d8807fd31 Mon Sep 17 00:00:00 2001 From: vamsee Date: Tue, 19 Feb 2019 11:42:40 +0530 Subject: [PATCH 21/25] Updated links to oecloud.io --- README.md | 78 ++++++++++++++++++++++++++-------------------------- package.json | 10 +++---- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e83381f..ca425f9 100644 --- a/README.md +++ b/README.md @@ -40,28 +40,28 @@ oeCloud framework has been in development for almost two years and several devel It has been demonstrated that application development using oeCloud is fast and developer get many levers to play with when he/she developing with oeCloud - due to framework offering and also JavaScript inherent power. -However, there is scope of improvement. +However, there is scope of improvement. * oeCloud itself has got several features and all of these features are bundled into single monolith node module. This causes trouble for application developer as many of features are included even though they are not needed by application developer. * Maintainability of oeCloud framework is getting difficult because features cannot be developed/enhanced in isolation. * Development cycle time is increased due to several CI/CD issues. * some of the node modules of loopback is forked and maintained by oeCloud team. Example is **loopback-datasource-juggler**. This node module is extensively modified and hence there is no way oeCloud can use latest loopback framework. This is tightly coupled with oeCloud. - -To address above concerns, oeCloud is being modularized + +To address above concerns, oeCloud is being modularized * oeCloud application will install oeCloud modules based on requirements. For example if data personalization is really required, he/she will install and use *oe-data-personalization* node module. * node modules will be created based on feature of oeCloud and each feature thus will have its own development life cycle. -* This way, each feature will live independently of each other and will have separate CI / CD. +* This way, each feature will live independently of each other and will have separate CI / CD. * As each feature is separately developed - there will be decoupling with loopback, oeCloud can keep pace with loopback development. -*oe-cloud* is base node module for all oeCloud base application development. +*oe-cloud* is base node module for all oeCloud base application development. # oe-cloud overall modules -![Modularization](http://evgit/oec-next/oe-cloud/raw/master/oe-modularization.png) +![Modularization](http://evgit/oecloud.io/oe-cloud/raw/master/oe-modularization.png) # oe-cloud Features and functionalities -This is most important project of oeCloud. This module needs to be required in application's server.js. +This is most important project of oeCloud. This module needs to be required in application's server.js. Below are responsibilities of oe-cloud ## oe-cloud - What it will do @@ -105,11 +105,11 @@ Typical app-list.json, which would be part of application would look like { "path": "oe-common-mixins", "enabled": true - }, + }, { "path": "oe-cache", "enabled": true - }, + }, { "path": "oe-personalization", "enabled": true @@ -117,11 +117,11 @@ Typical app-list.json, which would be part of application would look like { "path": "oe-validation", "enabled": true - }, + }, { "path": "oe-service-personalization", "enabled": true - }, + }, { "path": "./", "enabled": true @@ -156,7 +156,7 @@ module.exports = function(app){ // oe-cloud will call this function when it loads the module and when init() is not defined. // app object will be passed which has got much of information to manipulate // you can set observer hook as below - + app.observe('loaded', function(ctx, next){ //ctx.app will have app handle return next(); @@ -213,7 +213,7 @@ In above model-config, MyModel will be created as public model. However, definit ``` * Above example will change default behavior. * you can also selectively ON/OFF the mixin attachments by calling **addModuleMixinsToBaseEntity** API as below. This can be important if you have to have some mixins from other dependent module. - + ``` var oecloud = require('oe-cloud'); oecloud.addModuleMixinsToBaseEntity('oe-data-personalization', false); @@ -259,15 +259,15 @@ oecloud.boot(__dirname, function(err){ ```javaScript // boot script with callback module.exports = function(app, cb){ - + // must call cb() otherwise next boot script will not be executed - // should throw error if needed like + // should throw error if needed like // return cb(new Error("something went wrong"); } // boot script without callback module.exports = function(app){ - + // next boot script will be executed when function returns } @@ -292,7 +292,7 @@ and middleware should have code such as below. This file should reside in server ```javaScript module.exports = function (options) { return function(req, res, next) { - + return next(); // must call next() otherwise next middlware will not be executed and system will hang. } @@ -309,7 +309,7 @@ There are simple utility functions which can be used in your module ### IsBaseEntity(Model) -This utility function checks if given Model is derived from BaseEntity. This will be useful many times in programming. Below is code snippet. +This utility function checks if given Model is derived from BaseEntity. This will be useful many times in programming. Below is code snippet. ```javaScript const oecloudUtil = require('oecloud/lib/util'); @@ -414,7 +414,7 @@ This function will boot the application. It will do following typical things, bu ```javaScript const oecloud = require('oe-cloud'); oecloud.boot(__dirname, function(err){ - orcloud.start(); + orcloud.start(); }) ``` @@ -428,7 +428,7 @@ This function will start web server and starts listening on PORT. Default port i const oecloud = require('oe-cloud'); oecloud.start() oecloud.once('started', function(){ - // do something. + // do something. }); ``` @@ -453,13 +453,13 @@ accessToken.evObserve("before save", function (ctx, next) { customerModel.evObserve("access", function(ctx, next){ var context = ctx.options.ctx; - + assert (context.tenantId) }) customerModel.beforeRemote("*", function(req, res.next){ var context = req.callContext; - + assert (context.tenantId) }) @@ -475,7 +475,7 @@ var app = require('oe-cloud'); app.removeForceId('Role'); app.removeForceId('RoleMapping'); ``` -However, please note that, **by default** above code is executed as part of boot script. Meaning, by default, ForceId is deisabled for User, Role and RoleMapping models. Therefore, you can create User/Role/Rolemapping data by passing id field explicitly. +However, please note that, **by default** above code is executed as part of boot script. Meaning, by default, ForceId is deisabled for User, Role and RoleMapping models. Therefore, you can create User/Role/Rolemapping data by passing id field explicitly. If you want to disable this, you can use **disableForceIdForUserModels** setting to true in config.json. **About ForceId** : In loopback 3, ForceId setting is done on model which is **true** by default. In this case, user/programmer cannot create a record on model by passing his/her own id. Id is always generated by loopback. To disable this setting, you can use removeForceId call. @@ -525,9 +525,9 @@ var app = require('oe-cloud'); app.observe('loaded', function(ctx, next){ app.addSettingsToModelDefinition({properties : {_versioning : {type : "boolean", default : false}}}); app.addSettingsToModelDefinition({properties : {HistoryMixin : {type : "boolean", default: false}}}); - + app.addSettingsToBaseEntity({autoscope:["tenantId"]}); - + return next(); }) ``` @@ -536,19 +536,19 @@ app.observe('loaded', function(ctx, next){ | Feature | Exisiting | Proposed | | ------ | ------ | ------- | -| Custom Types Register-Email & timestamp | Data Source juggler Change | Model Builder Wrapper [oe-cloud](http://evgit/oec-next/oe-cloud) | -| **after access** observer notification | DAO Change | Data Source juggler Wrapper [oe-cloud](http://evgit/oec-next/oe-cloud) | -| app-list.json handling | server.js | [oe-cloud](http://evgit/oec-next/oe-cloud) | -| EvObserver | Mixin in file | [oe-cloud](http://evgit/oec-next/oe-cloud) | -| Audit Field | Mixin in file | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | -| Versioning | Mixin in file | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | -| History | Mixin | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins)| +| Custom Types Register-Email & timestamp | Data Source juggler Change | Model Builder Wrapper [oe-cloud](http://evgit/oecloud.io/oe-cloud) | +| **after access** observer notification | DAO Change | Data Source juggler Wrapper [oe-cloud](http://evgit/oecloud.io/oe-cloud) | +| app-list.json handling | server.js | [oe-cloud](http://evgit/oecloud.io/oe-cloud) | +| EvObserver | Mixin in file | [oe-cloud](http://evgit/oecloud.io/oe-cloud) | +| Audit Field | Mixin in file | [oe-common-mixins](http://evgit/oecloud.io/oe-common-mixins) | +| Versioning | Mixin in file | [oe-common-mixins](http://evgit/oecloud.io/oe-common-mixins) | +| History | Mixin | [oe-common-mixins](http://evgit/oecloud.io/oe-common-mixins)| | Idempotency | Mixin+DAO | Not done | -| Soft Delete | Mixin + DAO | [oe-common-mixins](http://evgit/oec-next/oe-common-mixins) | -| Validations | Mixin | [oe-validation](http://evgit/oec-next/oe-validation) | -| Expression Support | Mixin | [oe-expression](http://evgit/oec-next/oe-expression) | -| Model Composite - Implicit and Explicit | DAO Change | DAO Wrapper [oe-model-composite](http://evgit/oec-next/oe-model-composite) | -| Data Personalization | Mixin | [oe-personalization](http://evgit/oec-next/oe-personalization) | -| Service Personalization | Mixin+Boot | Boot [oe-service=personalization](http://evgit/oec-next/oe-service-personalization) | -| Cachinge | Mixin+DAO | DAO Wrapper [oe-cache](http://evgit/oec-next/oe-cache) | +| Soft Delete | Mixin + DAO | [oe-common-mixins](http://evgit/oecloud.io/oe-common-mixins) | +| Validations | Mixin | [oe-validation](http://evgit/oecloud.io/oe-validation) | +| Expression Support | Mixin | [oe-expression](http://evgit/oecloud.io/oe-expression) | +| Model Composite - Implicit and Explicit | DAO Change | DAO Wrapper [oe-model-composite](http://evgit/oecloud.io/oe-model-composite) | +| Data Personalization | Mixin | [oe-personalization](http://evgit/oecloud.io/oe-personalization) | +| Service Personalization | Mixin+Boot | Boot [oe-service=personalization](http://evgit/oecloud.io/oe-service-personalization) | +| Cachinge | Mixin+DAO | DAO Wrapper [oe-cache](http://evgit/oecloud.io/oe-cache) | diff --git a/package.json b/package.json index 1c76bb9..f499e2d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "oe-cloud", "version": "2.0.0", - "description": "oe-cloud modularization aka oec-next", + "description": "oe-cloud modularization aka oecloud.io", "engines": { "node": ">=6" }, @@ -23,7 +23,7 @@ "loopback-boot": "2.27.1", "loopback-component-explorer": "5.4.0", "mustache": "2.3.2", - "oe-logger": "git+http://evgit/oec-next/oe-logger.git#master", + "oe-logger": "git+http://evgit/oecloud.io/oe-logger.git#master", "serve-favicon": "2.5.0", "serve-static": "1.13.2", "strong-error-handler": "2.3.2" @@ -48,14 +48,14 @@ "mocha": "5.2.0", "superagent-defaults": "0.1.14", "supertest": "3.4.2", - "oe-skeleton": "git+http://evgit/oec-next/oe-skeleton.git#master", + "oe-skeleton": "git+http://evgit/oecloud.io/oe-skeleton.git#master", "loopback-connector-mongodb": "git+http://evgit/oecloud.io/loopback-connector-mongodb.git#master", - "oe-connector-postgresql": "git+http://evgit/oec-next/oe-connector-postgresql.git#master" + "oe-connector-postgresql": "git+http://evgit/oecloud.io/oe-connector-postgresql.git#master" }, "author": "Atul Pandit ", "repository": { "type": "git", - "url": "git@evgit:oec-next/oe-cloud.git" + "url": "https://github.com/EdgeVerve/oe-cloud.git" }, "license": "MIT" } From b729b1e00fe339d59fef1dda3e66de3b08b994d0 Mon Sep 17 00:00:00 2001 From: vamsee Date: Tue, 19 Feb 2019 16:11:50 +0530 Subject: [PATCH 22/25] Using oe-connector-mongodb --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f499e2d..fcab7bd 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "superagent-defaults": "0.1.14", "supertest": "3.4.2", "oe-skeleton": "git+http://evgit/oecloud.io/oe-skeleton.git#master", - "loopback-connector-mongodb": "git+http://evgit/oecloud.io/loopback-connector-mongodb.git#master", + "oe-connector-mongodb": "git+http://evgit/oecloud.io/oe-connector-mongodb.git#master", "oe-connector-postgresql": "git+http://evgit/oecloud.io/oe-connector-postgresql.git#master" }, "author": "Atul Pandit ", From 10c88b07fbb88f2303150f7e01b3e341bb5fa0e6 Mon Sep 17 00:00:00 2001 From: Atul Date: Tue, 19 Feb 2019 17:45:36 +0530 Subject: [PATCH 23/25] changing to "connector": "oe-connector-mongodb", --- test/datasources.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/datasources.json b/test/datasources.json index c0b79f1..85eefe8 100644 --- a/test/datasources.json +++ b/test/datasources.json @@ -15,7 +15,7 @@ "database": "oe-cloud-test", "password": "admin", "name": "db", - "connector": "mongodb", + "connector": "oe-connector-mongodb", "user": "admin", "connectionTimeout": 500000, "connectTimeoutMS": 500000, From 9550affdf98ef4d4069680e805e9fda7ddce677c Mon Sep 17 00:00:00 2001 From: Atul Date: Tue, 19 Feb 2019 17:45:51 +0530 Subject: [PATCH 24/25] Update datasources.mongo.js --- test/datasources.mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/datasources.mongo.js b/test/datasources.mongo.js index c4cf4f9..a5c7907 100644 --- a/test/datasources.mongo.js +++ b/test/datasources.mongo.js @@ -23,7 +23,7 @@ module.exports = "database": dbName, "password": "admin", "name": "db", - "connector": "mongodb", + "connector": "oe-connector-mongodb", "user": "admin", "connectionTimeout": 500000 } From 66c97ba3b06d160dac6e738236b710900f6744ff Mon Sep 17 00:00:00 2001 From: vamsee Date: Tue, 19 Feb 2019 22:28:41 +0530 Subject: [PATCH 25/25] [ci skip] Changed deps to 2.0.0 --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fcab7bd..bba5ac5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "loopback-boot": "2.27.1", "loopback-component-explorer": "5.4.0", "mustache": "2.3.2", - "oe-logger": "git+http://evgit/oecloud.io/oe-logger.git#master", + "oe-logger": "git+http://evgit/oecloud.io/oe-logger.git#2.0.0", "serve-favicon": "2.5.0", "serve-static": "1.13.2", "strong-error-handler": "2.3.2" @@ -48,9 +48,9 @@ "mocha": "5.2.0", "superagent-defaults": "0.1.14", "supertest": "3.4.2", - "oe-skeleton": "git+http://evgit/oecloud.io/oe-skeleton.git#master", - "oe-connector-mongodb": "git+http://evgit/oecloud.io/oe-connector-mongodb.git#master", - "oe-connector-postgresql": "git+http://evgit/oecloud.io/oe-connector-postgresql.git#master" + "oe-skeleton": "git+http://evgit/oecloud.io/oe-skeleton.git#2.0.0", + "oe-connector-mongodb": "git+http://evgit/oecloud.io/oe-connector-mongodb.git#2.0.0", + "oe-connector-postgresql": "git+http://evgit/oecloud.io/oe-connector-postgresql.git#2.0.0" }, "author": "Atul Pandit ", "repository": {