// Copyright IBM Corp. 2014,2019. All Rights Reserved. // Node module: loopback-datasource-juggler // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT // This test written in mocha+should.js 'use strict'; /* global getSchema:false */ const should = require('./init.js'); const async = require('async'); let db, Category, Product, Tool, Widget, Thing, Person; // This test requires a connector that can // handle a custom collection or table name // TODO [fabien] add table for pgsql/mysql // TODO [fabien] change model definition - see #293 const setupProducts = function(ids, done) { async.series([ function(next) { Tool.create({name: 'Tool Z'}, function(err, inst) { ids.toolZ = inst.id; next(); }); }, function(next) { Widget.create({name: 'Widget Z'}, function(err, inst) { ids.widgetZ = inst.id; next(); }); }, function(next) { Tool.create({name: 'Tool A', active: false}, function(err, inst) { ids.toolA = inst.id; next(); }); }, function(next) { Widget.create({name: 'Widget A'}, function(err, inst) { ids.widgetA = inst.id; next(); }); }, function(next) { Widget.create({name: 'Widget B', active: false}, function(err, inst) { ids.widgetB = inst.id; next(); }); }, ], done); }; describe('default scope', function() { before(function(done) { db = getSchema(); Category = db.define('Category', { name: String, }); Product = db.define('Product', { name: String, kind: String, description: String, active: {type: Boolean, default: true}, }, { scope: {order: 'name'}, scopes: {active: {where: {active: true}}}, }); Product.lookupModel = function(data) { const m = this.dataSource.models[data.kind]; if (m.base === this) return m; return this; }; Tool = db.define('Tool', Product.definition.properties, { base: 'Product', scope: {where: {kind: 'Tool'}, order: 'name'}, scopes: {active: {where: {active: true}}}, arangodb: {collection: 'Product'}, mongodb: {collection: 'Product'}, memory: {collection: 'Product'}, }); Widget = db.define('Widget', Product.definition.properties, { base: 'Product', properties: {kind: 'Widget'}, scope: {where: {kind: 'Widget'}, order: 'name'}, scopes: {active: {where: {active: true}}}, arangodb: {collection: 'Product'}, mongodb: {collection: 'Product'}, memory: {collection: 'Product'}, }); Person = db.define('Person', {name: String}, { scope: {include: 'things'}, forceId: false, }); // inst is only valid for instance methods // like save, updateAttributes const scopeFn = function(target, inst) { return {where: {kind: this.modelName}}; }; const propertiesFn = function(target, inst) { return {kind: this.modelName}; }; Thing = db.define('Thing', Product.definition.properties, { base: 'Product', attributes: propertiesFn, scope: scopeFn, arangodb: {collection: 'Product'}, mongodb: {collection: 'Product'}, memory: {collection: 'Product'}, }); Category.hasMany(Product); Category.hasMany(Tool, {scope: {order: 'name DESC'}}); Category.hasMany(Widget); Category.hasMany(Thing); Product.belongsTo(Category); Tool.belongsTo(Category); Widget.belongsTo(Category); Thing.belongsTo(Category); Person.hasMany(Thing); Thing.belongsTo(Person); db.automigrate(done); }); describe('manipulation', function() { const ids = {}; before(function(done) { db.automigrate(done); }); it('should return a scoped instance', function() { const p = new Tool({name: 'Product A', kind: 'ignored'}); p.name.should.equal('Product A'); p.kind.should.equal('Tool'); p.setAttributes({kind: 'ignored'}); p.kind.should.equal('Tool'); p.setAttribute('kind', 'other'); // currently not enforced p.kind.should.equal('other'); }); it('should create a scoped instance - tool', function(done) { Tool.create({name: 'Product A', kind: 'ignored'}, function(err, p) { should.not.exist(err); p.name.should.equal('Product A'); p.kind.should.equal('Tool'); ids.productA = p.id; done(); }); }); it('should create a scoped instance - widget', function(done) { Widget.create({name: 'Product B', kind: 'ignored'}, function(err, p) { should.not.exist(err); p.name.should.equal('Product B'); p.kind.should.equal('Widget'); ids.productB = p.id; done(); }); }); it('should update a scoped instance - updateAttributes', function(done) { Tool.findById(ids.productA, function(err, p) { p.updateAttributes({description: 'A thing...', kind: 'ingored'}, function(err, inst) { should.not.exist(err); p.name.should.equal('Product A'); p.kind.should.equal('Tool'); p.description.should.equal('A thing...'); done(); }); }); }); it('should update a scoped instance - save', function(done) { Tool.findById(ids.productA, function(err, p) { p.description = 'Something...'; p.kind = 'ignored'; p.save(function(err, inst) { should.not.exist(err); p.name.should.equal('Product A'); p.kind.should.equal('Tool'); p.description.should.equal('Something...'); Tool.findById(ids.productA, function(err, p) { p.kind.should.equal('Tool'); done(); }); }); }); }); it('should update a scoped instance - updateOrCreate', function(done) { const data = {id: ids.productA, description: 'Anything...', kind: 'ingored'}; Tool.updateOrCreate(data, function(err, p) { should.not.exist(err); p.name.should.equal('Product A'); p.kind.should.equal('Tool'); p.description.should.equal('Anything...'); done(); }); }); }); describe('findById', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope', function(done) { Product.findById(ids.toolA, function(err, inst) { should.not.exist(err); inst.name.should.equal('Tool A'); inst.should.be.instanceof(Tool); done(); }); }); it('should apply default scope - tool', function(done) { Tool.findById(ids.toolA, function(err, inst) { should.not.exist(err); inst.name.should.equal('Tool A'); done(); }); }); it('should apply default scope (no match)', function(done) { Widget.findById(ids.toolA, function(err, inst) { should.not.exist(err); should.not.exist(inst); done(); }); }); }); describe('find', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope - order', function(done) { Product.find(function(err, products) { should.not.exist(err); products.should.have.length(5); products[0].name.should.equal('Tool A'); products[1].name.should.equal('Tool Z'); products[2].name.should.equal('Widget A'); products[3].name.should.equal('Widget B'); products[4].name.should.equal('Widget Z'); products[0].should.be.instanceof(Product); products[0].should.be.instanceof(Tool); products[2].should.be.instanceof(Product); products[2].should.be.instanceof(Widget); done(); }); }); it('should apply default scope - order override', function(done) { Product.find({order: 'name DESC'}, function(err, products) { should.not.exist(err); products.should.have.length(5); products[0].name.should.equal('Widget Z'); products[1].name.should.equal('Widget B'); products[2].name.should.equal('Widget A'); products[3].name.should.equal('Tool Z'); products[4].name.should.equal('Tool A'); done(); }); }); it('should apply default scope - tool', function(done) { Tool.find(function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].name.should.equal('Tool A'); products[1].name.should.equal('Tool Z'); done(); }); }); it('should apply default scope - where (widget)', function(done) { Widget.find({where: {active: true}}, function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].name.should.equal('Widget A'); products[1].name.should.equal('Widget Z'); done(); }); }); it('should apply default scope - order (widget)', function(done) { Widget.find({order: 'name DESC'}, function(err, products) { should.not.exist(err); products.should.have.length(3); products[0].name.should.equal('Widget Z'); products[1].name.should.equal('Widget B'); products[2].name.should.equal('Widget A'); done(); }); }); }); describe('exists', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope', function(done) { Product.exists(ids.widgetA, function(err, exists) { should.not.exist(err); exists.should.be.true; done(); }); }); it('should apply default scope - tool', function(done) { Tool.exists(ids.toolZ, function(err, exists) { should.not.exist(err); exists.should.be.true; done(); }); }); it('should apply default scope - widget', function(done) { Widget.exists(ids.widgetA, function(err, exists) { should.not.exist(err); exists.should.be.true; done(); }); }); it('should apply default scope - tool (no match)', function(done) { Tool.exists(ids.widgetA, function(err, exists) { should.not.exist(err); exists.should.be.false; done(); }); }); it('should apply default scope - widget (no match)', function(done) { Widget.exists(ids.toolZ, function(err, exists) { should.not.exist(err); exists.should.be.false; done(); }); }); }); describe('count', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope - order', function(done) { Product.count(function(err, count) { should.not.exist(err); count.should.equal(5); done(); }); }); it('should apply default scope - tool', function(done) { Tool.count(function(err, count) { should.not.exist(err); count.should.equal(2); done(); }); }); it('should apply default scope - widget', function(done) { Widget.count(function(err, count) { should.not.exist(err); count.should.equal(3); done(); }); }); it('should apply default scope - where', function(done) { Widget.count({name: 'Widget Z'}, function(err, count) { should.not.exist(err); count.should.equal(1); done(); }); }); it('should apply default scope - no match', function(done) { Tool.count({name: 'Widget Z'}, function(err, count) { should.not.exist(err); count.should.equal(0); done(); }); }); }); describe('removeById', function() { const ids = {}; function isDeleted(id, done) { Product.exists(id, function(err, exists) { should.not.exist(err); exists.should.be.false; done(); }); } before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope', function(done) { Product.removeById(ids.widgetZ, function(err) { should.not.exist(err); isDeleted(ids.widgetZ, done); }); }); it('should apply default scope - tool', function(done) { Tool.removeById(ids.toolA, function(err) { should.not.exist(err); isDeleted(ids.toolA, done); }); }); it('should apply default scope - no match', function(done) { Tool.removeById(ids.widgetA, function(err) { should.not.exist(err); Product.exists(ids.widgetA, function(err, exists) { should.not.exist(err); exists.should.be.true; done(); }); }); }); it('should apply default scope - widget', function(done) { Widget.removeById(ids.widgetA, function(err) { should.not.exist(err); isDeleted(ids.widgetA, done); }); }); it('should apply default scope - verify', function(done) { Product.find(function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].name.should.equal('Tool Z'); products[1].name.should.equal('Widget B'); done(); }); }); }); describe('update', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope', function(done) { Widget.update({active: false}, {active: true, kind: 'ignored'}, function(err) { should.not.exist(err); Widget.find({where: {active: true}}, function(err, products) { should.not.exist(err); products.should.have.length(3); products[0].name.should.equal('Widget A'); products[1].name.should.equal('Widget B'); products[2].name.should.equal('Widget Z'); done(); }); }); }); it('should apply default scope - no match', function(done) { Tool.update({name: 'Widget A'}, {name: 'Ignored'}, function(err) { should.not.exist(err); Product.findById(ids.widgetA, function(err, product) { should.not.exist(err); product.name.should.equal('Widget A'); done(); }); }); }); it('should have updated within scope', function(done) { Product.find({where: {active: true}}, function(err, products) { should.not.exist(err); products.should.have.length(4); products[0].name.should.equal('Tool Z'); products[1].name.should.equal('Widget A'); products[2].name.should.equal('Widget B'); products[3].name.should.equal('Widget Z'); done(); }); }); }); describe('remove', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should apply default scope - custom where', function(done) { Widget.remove({name: 'Widget A'}, function(err) { should.not.exist(err); Product.find(function(err, products) { products.should.have.length(4); products[0].name.should.equal('Tool A'); products[1].name.should.equal('Tool Z'); products[2].name.should.equal('Widget B'); products[3].name.should.equal('Widget Z'); done(); }); }); }); it('should apply default scope - custom where (no match)', function(done) { Tool.remove({name: 'Widget Z'}, function(err) { should.not.exist(err); Product.find(function(err, products) { products.should.have.length(4); products[0].name.should.equal('Tool A'); products[1].name.should.equal('Tool Z'); products[2].name.should.equal('Widget B'); products[3].name.should.equal('Widget Z'); done(); }); }); }); it('should apply default scope - deleteAll', function(done) { Tool.deleteAll(function(err) { should.not.exist(err); Product.find(function(err, products) { products.should.have.length(2); products[0].name.should.equal('Widget B'); products[1].name.should.equal('Widget Z'); done(); }); }); }); it('should create a scoped instance - tool', function(done) { Tool.create({name: 'Tool B'}, function(err, p) { should.not.exist(err); Product.find(function(err, products) { products.should.have.length(3); products[0].name.should.equal('Tool B'); products[1].name.should.equal('Widget B'); products[2].name.should.equal('Widget Z'); done(); }); }); }); it('should apply default scope - destroyAll', function(done) { Widget.destroyAll(function(err) { should.not.exist(err); Product.find(function(err, products) { products.should.have.length(1); products[0].name.should.equal('Tool B'); done(); }); }); }); }); describe('scopes', function() { const ids = {}; before(function(done) { db.automigrate(setupProducts.bind(null, ids, done)); }); it('should merge with default scope', function(done) { Product.active(function(err, products) { should.not.exist(err); products.should.have.length(3); products[0].name.should.equal('Tool Z'); products[1].name.should.equal('Widget A'); products[2].name.should.equal('Widget Z'); done(); }); }); it('should merge with default scope - tool', function(done) { Tool.active(function(err, products) { should.not.exist(err); products.should.have.length(1); products[0].name.should.equal('Tool Z'); done(); }); }); it('should merge with default scope - widget', function(done) { Widget.active(function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].name.should.equal('Widget A'); products[1].name.should.equal('Widget Z'); done(); }); }); }); describe('scope function', function() { before(function(done) { db.automigrate(done); }); it('should create a scoped instance - widget', function(done) { Widget.create({name: 'Product', kind: 'ignored'}, function(err, p) { p.name.should.equal('Product'); p.kind.should.equal('Widget'); done(); }); }); it('should create a scoped instance - thing', function(done) { Thing.create({name: 'Product', kind: 'ignored'}, function(err, p) { p.name.should.equal('Product'); p.kind.should.equal('Thing'); done(); }); }); it('should find a scoped instance - widget', function(done) { Widget.findOne({where: {name: 'Product'}}, function(err, p) { p.name.should.equal('Product'); p.kind.should.equal('Widget'); done(); }); }); it('should find a scoped instance - thing', function(done) { Thing.findOne({where: {name: 'Product'}}, function(err, p) { p.name.should.equal('Product'); p.kind.should.equal('Thing'); done(); }); }); // eslint-disable-next-line mocha/no-identical-title it('should find a scoped instance - thing', function(done) { Product.find({where: {name: 'Product'}}, function(err, products) { products.should.have.length(2); products[0].name.should.equal('Product'); products[1].name.should.equal('Product'); const kinds = products.map(function(p) { return p.kind; }); kinds.sort(); kinds.should.eql(['Thing', 'Widget']); done(); }); }); }); describe('relations', function() { const ids = {}; before(function(done) { db.automigrate(done); }); before(function(done) { Category.create({name: 'Category A'}, function(err, cat) { ids.categoryA = cat.id; async.series([ function(next) { cat.widgets.create({name: 'Widget B', kind: 'ignored'}, next); }, function(next) { cat.widgets.create({name: 'Widget A'}, next); }, function(next) { cat.tools.create({name: 'Tool A'}, next); }, function(next) { cat.things.create({name: 'Thing A'}, next); }, ], done); }); }); it('should apply default scope - products', function(done) { Category.findById(ids.categoryA, function(err, cat) { should.not.exist(err); cat.products(function(err, products) { should.not.exist(err); products.should.have.length(4); products[0].name.should.equal('Thing A'); products[1].name.should.equal('Tool A'); products[2].name.should.equal('Widget A'); products[3].name.should.equal('Widget B'); products[0].should.be.instanceof(Product); products[0].should.be.instanceof(Thing); products[1].should.be.instanceof(Product); products[1].should.be.instanceof(Tool); products[2].should.be.instanceof(Product); products[2].should.be.instanceof(Widget); done(); }); }); }); it('should apply default scope - widgets', function(done) { Category.findById(ids.categoryA, function(err, cat) { should.not.exist(err); cat.widgets(function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].should.be.instanceof(Widget); products[0].name.should.equal('Widget A'); products[1].name.should.equal('Widget B'); products[0].category(function(err, inst) { inst.name.should.equal('Category A'); done(); }); }); }); }); it('should apply default scope - tools', function(done) { Category.findById(ids.categoryA, function(err, cat) { should.not.exist(err); cat.tools(function(err, products) { should.not.exist(err); products.should.have.length(1); products[0].should.be.instanceof(Tool); products[0].name.should.equal('Tool A'); products[0].category(function(err, inst) { inst.name.should.equal('Category A'); done(); }); }); }); }); it('should apply default scope - things', function(done) { Category.findById(ids.categoryA, function(err, cat) { should.not.exist(err); cat.things(function(err, products) { should.not.exist(err); products.should.have.length(1); products[0].should.be.instanceof(Thing); products[0].name.should.equal('Thing A'); products[0].category(function(err, inst) { inst.name.should.equal('Category A'); done(); }); }); }); }); it('should create related item with default scope', function(done) { Category.findById(ids.categoryA, function(err, cat) { cat.tools.create({name: 'Tool B'}, done); }); }); it('should use relation scope order', function(done) { Category.findById(ids.categoryA, function(err, cat) { should.not.exist(err); cat.tools(function(err, products) { should.not.exist(err); products.should.have.length(2); products[0].name.should.equal('Tool B'); products[1].name.should.equal('Tool A'); done(); }); }); }); }); describe('with include option', function() { before(function(done) { db.automigrate(done); }); before(function(done) { Person.create({id: 1, name: 'Person A'}, function(err, person) { person.things.create({name: 'Thing A'}, done); }); }); it('should find a scoped instance with included relation - things', function(done) { Person.findById(1, function(err, person) { should.not.exist(err); should.exist(person); const things = person.things(); should.exist(things); things.should.be.an.instanceOf(Array); things.should.have.length(1); done(); }); }); }); });