2020-01-21 18:12:14 +00:00
|
|
|
|
// Copyright IBM Corp. 2013,2019. All Rights Reserved.
|
2016-04-01 22:25:16 +00:00
|
|
|
|
// Node module: loopback-datasource-juggler
|
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
|
// This test written in mocha+should.js
|
2016-08-22 19:55:22 +00:00
|
|
|
|
'use strict';
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const should = require('./init.js');
|
|
|
|
|
const assert = require('assert');
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const jdb = require('../');
|
|
|
|
|
const ModelBuilder = jdb.ModelBuilder;
|
|
|
|
|
const DataSource = jdb.DataSource;
|
|
|
|
|
const Memory = require('../lib/connectors/memory');
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const ModelDefinition = require('../lib/model-definition');
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
describe('ModelDefinition class', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let memory;
|
2015-04-02 07:49:04 +00:00
|
|
|
|
beforeEach(function() {
|
2016-08-19 17:46:59 +00:00
|
|
|
|
memory = new DataSource({connector: Memory});
|
2015-04-02 07:49:04 +00:00
|
|
|
|
});
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should be able to define plain models', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'string',
|
2014-01-24 17:09:53 +00:00
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
2016-04-01 13:23:42 +00:00
|
|
|
|
age: 'number',
|
2013-10-05 04:21:12 +00:00
|
|
|
|
});
|
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
User.build();
|
|
|
|
|
assert.equal(User.properties.name.type, String);
|
|
|
|
|
assert.equal(User.properties.bio.type, ModelBuilder.Text);
|
|
|
|
|
assert.equal(User.properties.approved.type, Boolean);
|
|
|
|
|
assert.equal(User.properties.joinedAt.type, Date);
|
|
|
|
|
assert.equal(User.properties.age.type, Number);
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const json = User.toJSON();
|
2016-04-01 11:48:17 +00:00
|
|
|
|
assert.equal(json.name, 'User');
|
|
|
|
|
assert.equal(json.properties.name.type, 'String');
|
|
|
|
|
assert.equal(json.properties.bio.type, 'Text');
|
|
|
|
|
assert.equal(json.properties.approved.type, 'Boolean');
|
|
|
|
|
assert.equal(json.properties.joinedAt.type, 'Date');
|
|
|
|
|
assert.equal(json.properties.age.type, 'Number');
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2014-08-17 16:01:52 +00:00
|
|
|
|
assert.deepEqual(User.toJSON(), json);
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should be able to define additional properties', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'string',
|
2014-01-24 17:09:53 +00:00
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
2016-04-01 13:23:42 +00:00
|
|
|
|
age: 'number',
|
2013-10-02 05:14:21 +00:00
|
|
|
|
});
|
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
User.build();
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let json = User.toJSON();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2016-08-19 17:46:59 +00:00
|
|
|
|
User.defineProperty('id', {type: 'number', id: true});
|
2014-01-24 17:09:53 +00:00
|
|
|
|
assert.equal(User.properties.name.type, String);
|
|
|
|
|
assert.equal(User.properties.bio.type, ModelBuilder.Text);
|
|
|
|
|
assert.equal(User.properties.approved.type, Boolean);
|
|
|
|
|
assert.equal(User.properties.joinedAt.type, Date);
|
|
|
|
|
assert.equal(User.properties.age.type, Number);
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
assert.equal(User.properties.id.type, Number);
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2014-08-31 10:17:53 +00:00
|
|
|
|
json = User.toJSON();
|
2016-08-19 17:46:59 +00:00
|
|
|
|
assert.deepEqual(json.properties.id, {type: 'Number', id: true});
|
2015-01-20 19:58:52 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should be able to define nesting models', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2014-01-24 17:09:53 +00:00
|
|
|
|
name: String,
|
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
|
|
|
|
age: Number,
|
|
|
|
|
address: {
|
|
|
|
|
street: String,
|
|
|
|
|
city: String,
|
|
|
|
|
zipCode: String,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
state: String,
|
|
|
|
|
},
|
2013-10-02 05:14:21 +00:00
|
|
|
|
});
|
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
User.build();
|
|
|
|
|
assert.equal(User.properties.name.type, String);
|
|
|
|
|
assert.equal(User.properties.bio.type, ModelBuilder.Text);
|
|
|
|
|
assert.equal(User.properties.approved.type, Boolean);
|
|
|
|
|
assert.equal(User.properties.joinedAt.type, Date);
|
|
|
|
|
assert.equal(User.properties.age.type, Number);
|
|
|
|
|
assert.equal(typeof User.properties.address.type, 'function');
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const json = User.toJSON();
|
2016-04-01 11:48:17 +00:00
|
|
|
|
assert.equal(json.name, 'User');
|
|
|
|
|
assert.equal(json.properties.name.type, 'String');
|
|
|
|
|
assert.equal(json.properties.bio.type, 'Text');
|
|
|
|
|
assert.equal(json.properties.approved.type, 'Boolean');
|
|
|
|
|
assert.equal(json.properties.joinedAt.type, 'Date');
|
|
|
|
|
assert.equal(json.properties.age.type, 'Number');
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2016-08-19 17:46:59 +00:00
|
|
|
|
assert.deepEqual(json.properties.address.type, {street: {type: 'String'},
|
|
|
|
|
city: {type: 'String'},
|
|
|
|
|
zipCode: {type: 'String'},
|
|
|
|
|
state: {type: 'String'}});
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should be able to define referencing models', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Address = modelBuilder.define('Address', {
|
2014-01-24 17:09:53 +00:00
|
|
|
|
street: String,
|
|
|
|
|
city: String,
|
|
|
|
|
zipCode: String,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
state: String,
|
2014-01-24 17:09:53 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2014-01-24 17:09:53 +00:00
|
|
|
|
name: String,
|
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
|
|
|
|
age: Number,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
address: Address,
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
});
|
2013-10-05 04:21:12 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
User.build();
|
|
|
|
|
assert.equal(User.properties.name.type, String);
|
|
|
|
|
assert.equal(User.properties.bio.type, ModelBuilder.Text);
|
|
|
|
|
assert.equal(User.properties.approved.type, Boolean);
|
|
|
|
|
assert.equal(User.properties.joinedAt.type, Date);
|
|
|
|
|
assert.equal(User.properties.age.type, Number);
|
|
|
|
|
assert.equal(User.properties.address.type, Address);
|
2013-10-05 04:21:12 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const json = User.toJSON();
|
2016-04-01 11:48:17 +00:00
|
|
|
|
assert.equal(json.name, 'User');
|
|
|
|
|
assert.equal(json.properties.name.type, 'String');
|
|
|
|
|
assert.equal(json.properties.bio.type, 'Text');
|
|
|
|
|
assert.equal(json.properties.approved.type, 'Boolean');
|
|
|
|
|
assert.equal(json.properties.joinedAt.type, 'Date');
|
|
|
|
|
assert.equal(json.properties.age.type, 'Number');
|
2013-10-05 04:21:12 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
assert.equal(json.properties.address.type, 'Address');
|
2013-10-02 05:14:21 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
done();
|
|
|
|
|
});
|
2013-10-05 04:21:12 +00:00
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should be able to define referencing models by name', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2013-10-05 18:13:10 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Address = modelBuilder.define('Address', {
|
2014-01-24 17:09:53 +00:00
|
|
|
|
street: String,
|
|
|
|
|
city: String,
|
|
|
|
|
zipCode: String,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
state: String,
|
2013-10-05 18:13:10 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2014-01-24 17:09:53 +00:00
|
|
|
|
name: String,
|
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
|
|
|
|
age: Number,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
address: 'Address',
|
2013-10-05 18:13:10 +00:00
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
|
});
|
2013-10-05 04:21:12 +00:00
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
User.build();
|
|
|
|
|
assert.equal(User.properties.name.type, String);
|
|
|
|
|
assert.equal(User.properties.bio.type, ModelBuilder.Text);
|
|
|
|
|
assert.equal(User.properties.approved.type, Boolean);
|
|
|
|
|
assert.equal(User.properties.joinedAt.type, Date);
|
|
|
|
|
assert.equal(User.properties.age.type, Number);
|
|
|
|
|
assert.equal(User.properties.address.type, Address);
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const json = User.toJSON();
|
2016-04-01 11:48:17 +00:00
|
|
|
|
assert.equal(json.name, 'User');
|
|
|
|
|
assert.equal(json.properties.name.type, 'String');
|
|
|
|
|
assert.equal(json.properties.bio.type, 'Text');
|
|
|
|
|
assert.equal(json.properties.approved.type, 'Boolean');
|
|
|
|
|
assert.equal(json.properties.joinedAt.type, 'Date');
|
|
|
|
|
assert.equal(json.properties.age.type, 'Number');
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
|
|
|
|
assert.equal(json.properties.address.type, 'Address');
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should report correct id names', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2016-08-19 17:46:59 +00:00
|
|
|
|
userId: {type: String, id: true},
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'string',
|
2014-01-24 17:09:53 +00:00
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
2016-04-01 13:23:42 +00:00
|
|
|
|
age: 'number',
|
2013-10-05 04:21:12 +00:00
|
|
|
|
});
|
|
|
|
|
|
2014-01-24 17:09:53 +00:00
|
|
|
|
assert.equal(User.idName(), 'userId');
|
|
|
|
|
assert.deepEqual(User.idNames(), ['userId']);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should sort id properties by its index', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2015-01-21 12:16:34 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2016-08-19 17:46:59 +00:00
|
|
|
|
userId: {type: String, id: 2},
|
|
|
|
|
userType: {type: String, id: 1},
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'string',
|
2015-01-21 12:16:34 +00:00
|
|
|
|
bio: ModelBuilder.Text,
|
|
|
|
|
approved: Boolean,
|
|
|
|
|
joinedAt: Date,
|
2016-04-01 13:23:42 +00:00
|
|
|
|
age: 'number',
|
2015-01-21 12:16:34 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const ids = User.ids();
|
2015-01-21 12:16:34 +00:00
|
|
|
|
assert.ok(Array.isArray(ids));
|
|
|
|
|
assert.equal(ids.length, 2);
|
|
|
|
|
assert.equal(ids[0].id, 1);
|
|
|
|
|
assert.equal(ids[0].name, 'userType');
|
|
|
|
|
assert.equal(ids[1].id, 2);
|
|
|
|
|
assert.equal(ids[1].name, 'userId');
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should report correct table/column names', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const modelBuilder = new ModelBuilder();
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const User = new ModelDefinition(modelBuilder, 'User', {
|
2016-08-19 17:46:59 +00:00
|
|
|
|
userId: {type: String, id: true, oracle: {column: 'ID'}},
|
2016-04-01 13:23:42 +00:00
|
|
|
|
name: 'string',
|
2016-08-19 17:46:59 +00:00
|
|
|
|
}, {oracle: {table: 'USER'}});
|
2014-01-24 17:09:53 +00:00
|
|
|
|
|
|
|
|
|
assert.equal(User.tableName('oracle'), 'USER');
|
|
|
|
|
assert.equal(User.tableName('mysql'), 'User');
|
|
|
|
|
assert.equal(User.columnName('oracle', 'userId'), 'ID');
|
|
|
|
|
assert.equal(User.columnName('mysql', 'userId'), 'userId');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
2018-10-30 19:14:52 +00:00
|
|
|
|
describe('maxDepthOfQuery', function() {
|
|
|
|
|
it('should report errors for deep query than maxDepthOfQuery', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const MyModel = memory.createModel('my-model', {}, {
|
2018-10-30 19:14:52 +00:00
|
|
|
|
maxDepthOfQuery: 5,
|
|
|
|
|
});
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const filter = givenComplexFilter();
|
2018-10-30 19:14:52 +00:00
|
|
|
|
|
|
|
|
|
MyModel.find(filter, function(err) {
|
|
|
|
|
should.exist(err);
|
|
|
|
|
err.message.should.match('The query object exceeds maximum depth 5');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should honor maxDepthOfQuery setting', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const MyModel = memory.createModel('my-model', {}, {
|
2018-10-30 19:14:52 +00:00
|
|
|
|
maxDepthOfQuery: 20,
|
|
|
|
|
});
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const filter = givenComplexFilter();
|
2018-10-30 19:14:52 +00:00
|
|
|
|
|
|
|
|
|
MyModel.find(filter, function(err) {
|
|
|
|
|
should.not.exist(err);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2018-11-09 17:33:05 +00:00
|
|
|
|
it('should honor maxDepthOfQuery in options', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const MyModel = memory.createModel('my-model', {}, {
|
2018-11-09 17:33:05 +00:00
|
|
|
|
maxDepthOfQuery: 5,
|
|
|
|
|
});
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const filter = givenComplexFilter();
|
2018-11-09 17:33:05 +00:00
|
|
|
|
|
|
|
|
|
MyModel.find(filter, {maxDepthOfQuery: 20}, function(err) {
|
|
|
|
|
should.not.exist(err);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2018-10-30 19:14:52 +00:00
|
|
|
|
function givenComplexFilter() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const filter = {where: {and: [{and: [{and: [{and: [{and: [{and:
|
2018-10-30 19:14:52 +00:00
|
|
|
|
[{and: [{and: [{and: [{x: 1}]}]}]}]}]}]}]}]}]}};
|
|
|
|
|
return filter;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-01-20 19:58:52 +00:00
|
|
|
|
it('should serialize protected properties into JSON', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const ProtectedModel = memory.createModel('protected', {}, {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
protected: ['protectedProperty'],
|
2015-01-20 19:58:52 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const pm = new ProtectedModel({
|
2016-04-01 11:48:17 +00:00
|
|
|
|
id: 1, foo: 'bar', protectedProperty: 'protected',
|
2015-01-20 19:58:52 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const serialized = pm.toJSON();
|
2015-01-20 19:58:52 +00:00
|
|
|
|
assert.deepEqual(serialized, {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
id: 1, foo: 'bar', protectedProperty: 'protected',
|
2015-01-20 19:58:52 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should not serialize protected properties of nested models into JSON', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Parent = memory.createModel('parent');
|
|
|
|
|
const Child = memory.createModel('child', {}, {protected: ['protectedProperty']});
|
2015-01-20 19:58:52 +00:00
|
|
|
|
Parent.hasMany(Child);
|
|
|
|
|
Parent.create({
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'parent',
|
2015-01-20 19:58:52 +00:00
|
|
|
|
}, function(err, parent) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2015-01-20 19:58:52 +00:00
|
|
|
|
parent.children.create({
|
|
|
|
|
name: 'child',
|
2016-04-01 11:48:17 +00:00
|
|
|
|
protectedProperty: 'protectedValue',
|
2015-01-20 19:58:52 +00:00
|
|
|
|
}, function(err, child) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2016-08-19 17:46:59 +00:00
|
|
|
|
Parent.find({include: 'children'}, function(err, parents) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const serialized = parents[0].toJSON();
|
|
|
|
|
const child = serialized.children[0];
|
2015-01-20 19:58:52 +00:00
|
|
|
|
assert.equal(child.name, 'child');
|
|
|
|
|
assert.notEqual(child.protectedProperty, 'protectedValue');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should not serialize hidden properties into JSON', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const HiddenModel = memory.createModel('hidden', {}, {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
hidden: ['secret'],
|
2014-04-11 18:39:57 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const hm = new HiddenModel({
|
2014-04-11 18:39:57 +00:00
|
|
|
|
id: 1,
|
|
|
|
|
foo: 'bar',
|
2016-04-01 11:48:17 +00:00
|
|
|
|
secret: 'secret',
|
2014-04-11 18:39:57 +00:00
|
|
|
|
});
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const serialized = hm.toJSON();
|
2014-04-11 18:39:57 +00:00
|
|
|
|
assert.deepEqual(serialized, {
|
|
|
|
|
id: 1,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
foo: 'bar',
|
2014-04-11 18:39:57 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2016-04-01 11:48:17 +00:00
|
|
|
|
it('should not serialize hidden properties of nested models into JSON', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Parent = memory.createModel('parent');
|
|
|
|
|
const Child = memory.createModel('child', {}, {hidden: ['secret']});
|
2014-04-11 18:39:57 +00:00
|
|
|
|
Parent.hasMany(Child);
|
|
|
|
|
Parent.create({
|
2016-04-01 11:48:17 +00:00
|
|
|
|
name: 'parent',
|
2014-04-11 18:39:57 +00:00
|
|
|
|
}, function(err, parent) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2014-04-11 18:39:57 +00:00
|
|
|
|
parent.children.create({
|
|
|
|
|
name: 'child',
|
2016-04-01 11:48:17 +00:00
|
|
|
|
secret: 'secret',
|
2014-04-11 18:39:57 +00:00
|
|
|
|
}, function(err, child) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2016-08-19 17:46:59 +00:00
|
|
|
|
Parent.find({include: 'children'}, function(err, parents) {
|
2018-10-19 18:56:51 +00:00
|
|
|
|
if (err) return done(err);
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const serialized = parents[0].toJSON();
|
|
|
|
|
const child = serialized.children[0];
|
2014-04-11 18:39:57 +00:00
|
|
|
|
assert.equal(child.name, 'child');
|
|
|
|
|
assert.notEqual(child.secret, 'secret');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2015-04-02 07:49:04 +00:00
|
|
|
|
|
2018-10-19 18:56:51 +00:00
|
|
|
|
describe('hidden properties', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Child;
|
2018-10-12 19:40:33 +00:00
|
|
|
|
|
2018-10-27 14:47:46 +00:00
|
|
|
|
describe('with hidden array', function() {
|
|
|
|
|
beforeEach(function() { givenChildren(); });
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {secret: 'guess'},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-27 14:47:46 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where.and', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {and: [{secret: 'guess'}]},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-27 14:47:46 +00:00
|
|
|
|
});
|
2018-10-28 17:03:12 +00:00
|
|
|
|
|
|
|
|
|
it('should be allowed for update', function() {
|
2018-11-12 21:54:22 +00:00
|
|
|
|
return Child.update({name: 'childA'}, {secret: 'new-secret'}, optionsFromRemoteReq).then(
|
2018-10-28 17:03:12 +00:00
|
|
|
|
function(result) {
|
|
|
|
|
result.count.should.equal(1);
|
2019-12-03 09:09:16 +00:00
|
|
|
|
},
|
2018-10-28 17:03:12 +00:00
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be allowed if prohibitHiddenPropertiesInQuery is `false`', function() {
|
|
|
|
|
Child.definition.settings.prohibitHiddenPropertiesInQuery = false;
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {secret: 'guess'},
|
|
|
|
|
}).then(function(children) {
|
|
|
|
|
children.length.should.equal(1);
|
|
|
|
|
children[0].secret.should.equal('guess');
|
|
|
|
|
});
|
|
|
|
|
});
|
2018-11-09 17:33:05 +00:00
|
|
|
|
|
2018-11-12 21:54:22 +00:00
|
|
|
|
it('should be allowed by default if not remote call', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {secret: 'guess'},
|
|
|
|
|
}).then(function(children) {
|
|
|
|
|
children.length.should.equal(1);
|
|
|
|
|
children[0].secret.should.equal('guess');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2018-11-09 17:33:05 +00:00
|
|
|
|
it('should be allowed if prohibitHiddenPropertiesInQuery is `false` in options', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {secret: 'guess'},
|
|
|
|
|
}, {
|
|
|
|
|
prohibitHiddenPropertiesInQuery: false,
|
|
|
|
|
}).then(function(children) {
|
|
|
|
|
children.length.should.equal(1);
|
|
|
|
|
children[0].secret.should.equal('guess');
|
|
|
|
|
});
|
|
|
|
|
});
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-10-27 14:47:46 +00:00
|
|
|
|
describe('with hidden object', function() {
|
|
|
|
|
beforeEach(function() { givenChildren({hiddenProperties: {secret: true}}); });
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {secret: 'guess'},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-27 14:47:46 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where.and', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {and: [{secret: 'guess'}]},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-27 14:47:46 +00:00
|
|
|
|
});
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-10-19 18:56:51 +00:00
|
|
|
|
/**
|
|
|
|
|
* Create two children with a hidden property, one with a matching
|
|
|
|
|
* value, the other with a non-matching value
|
|
|
|
|
*/
|
2018-10-27 14:47:46 +00:00
|
|
|
|
function givenChildren(hiddenProps) {
|
|
|
|
|
hiddenProps = hiddenProps || {hidden: ['secret']};
|
2018-10-29 14:57:55 +00:00
|
|
|
|
Child = memory.createModel('child', {
|
|
|
|
|
name: String,
|
|
|
|
|
secret: String,
|
|
|
|
|
}, hiddenProps);
|
2018-10-19 18:56:51 +00:00
|
|
|
|
return Child.create([{
|
|
|
|
|
name: 'childA',
|
2018-10-12 19:40:33 +00:00
|
|
|
|
secret: 'secret',
|
2018-10-19 18:56:51 +00:00
|
|
|
|
}, {
|
|
|
|
|
name: 'childB',
|
|
|
|
|
secret: 'guess',
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertHiddenPropertyIsIgnored(children) {
|
|
|
|
|
// All children are found whether the `secret` condition matches or not
|
|
|
|
|
// as the condition is removed because it's hidden
|
|
|
|
|
children.length.should.equal(2);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-11-12 21:54:22 +00:00
|
|
|
|
/**
|
|
|
|
|
* Mock up for default values set by the remote model
|
|
|
|
|
*/
|
|
|
|
|
const optionsFromRemoteReq = {
|
|
|
|
|
prohibitHiddenPropertiesInQuery: true,
|
|
|
|
|
maxDepthOfQuery: 12,
|
|
|
|
|
maxDepthOfQuery: 32,
|
|
|
|
|
};
|
|
|
|
|
|
2018-10-29 14:57:55 +00:00
|
|
|
|
describe('hidden nested properties', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Child;
|
2018-10-29 14:57:55 +00:00
|
|
|
|
beforeEach(givenChildren);
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where as a composite key - x.secret', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {'x.secret': 'guess'},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-29 14:57:55 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where as a composite key - secret.y', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {'secret.y': 'guess'},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-29 14:57:55 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in where as a composite key - a.secret.b', function() {
|
|
|
|
|
return Child.find({
|
|
|
|
|
where: {'a.secret.b': 'guess'},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertHiddenPropertyIsIgnored);
|
2018-10-29 14:57:55 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function givenChildren() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const hiddenProps = {hidden: ['secret']};
|
2018-10-29 14:57:55 +00:00
|
|
|
|
Child = memory.createModel('child', {
|
|
|
|
|
name: String,
|
|
|
|
|
x: {
|
|
|
|
|
secret: String,
|
|
|
|
|
},
|
|
|
|
|
secret: {
|
|
|
|
|
y: String,
|
|
|
|
|
},
|
|
|
|
|
a: {
|
|
|
|
|
secret: {
|
|
|
|
|
b: String,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}, hiddenProps);
|
|
|
|
|
return Child.create([{
|
|
|
|
|
name: 'childA',
|
|
|
|
|
x: {secret: 'secret'},
|
|
|
|
|
secret: {y: 'secret'},
|
|
|
|
|
a: {secret: {b: 'secret'}},
|
|
|
|
|
}, {
|
|
|
|
|
name: 'childB',
|
|
|
|
|
x: {secret: 'guess'},
|
|
|
|
|
secret: {y: 'guess'},
|
|
|
|
|
a: {secret: {b: 'guess'}},
|
|
|
|
|
}]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertHiddenPropertyIsIgnored(children) {
|
|
|
|
|
// All children are found whether the `secret` condition matches or not
|
|
|
|
|
// as the condition is removed because it's hidden
|
|
|
|
|
children.length.should.equal(2);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2018-10-19 18:56:51 +00:00
|
|
|
|
function assertParentIncludeChildren(parents) {
|
|
|
|
|
parents[0].toJSON().children.length.should.equal(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('protected properties', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Parent;
|
|
|
|
|
let Child;
|
2018-10-19 18:56:51 +00:00
|
|
|
|
beforeEach(givenParentAndChild);
|
|
|
|
|
|
|
|
|
|
it('should be removed if used in include scope', function() {
|
|
|
|
|
Parent.find({
|
|
|
|
|
include: {
|
|
|
|
|
relation: 'children',
|
|
|
|
|
scope: {
|
|
|
|
|
where: {
|
|
|
|
|
secret: 'x',
|
2018-10-12 19:40:33 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
2018-10-19 18:56:51 +00:00
|
|
|
|
},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertParentIncludeChildren);
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-10-19 18:56:51 +00:00
|
|
|
|
it('should be rejected if used in include scope.where.and', function() {
|
|
|
|
|
return Parent.find({
|
|
|
|
|
include: {
|
|
|
|
|
relation: 'children',
|
|
|
|
|
scope: {
|
|
|
|
|
where: {
|
|
|
|
|
and: [{secret: 'x'}],
|
2018-10-12 19:40:33 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
2018-10-19 18:56:51 +00:00
|
|
|
|
},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertParentIncludeChildren);
|
2018-10-19 18:56:51 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should be removed if a hidden property is used in include scope', function() {
|
|
|
|
|
return Parent.find({
|
|
|
|
|
include: {
|
|
|
|
|
relation: 'children',
|
|
|
|
|
scope: {
|
|
|
|
|
where: {
|
|
|
|
|
secret: 'x',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertParentIncludeChildren);
|
2018-10-19 18:56:51 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function givenParentAndChild() {
|
|
|
|
|
Parent = memory.createModel('parent');
|
|
|
|
|
Child = memory.createModel('child', {}, {protected: ['secret']});
|
|
|
|
|
Parent.hasMany(Child);
|
|
|
|
|
return Parent.create({
|
|
|
|
|
name: 'parent',
|
|
|
|
|
}).then(parent => {
|
|
|
|
|
return parent.children.create({
|
|
|
|
|
name: 'child',
|
|
|
|
|
secret: 'secret',
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
2018-10-19 18:56:51 +00:00
|
|
|
|
}
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-10-19 18:56:51 +00:00
|
|
|
|
describe('hidden properties in include', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Parent;
|
|
|
|
|
let Child;
|
2018-10-19 18:56:51 +00:00
|
|
|
|
beforeEach(givenParentAndChildWithHiddenProperty);
|
|
|
|
|
|
|
|
|
|
it('should be rejected if used in scope', function() {
|
|
|
|
|
return Parent.find({
|
|
|
|
|
include: {
|
|
|
|
|
relation: 'children',
|
|
|
|
|
scope: {
|
|
|
|
|
where: {
|
|
|
|
|
secret: 'x',
|
2018-10-12 19:40:33 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
2018-10-19 18:56:51 +00:00
|
|
|
|
},
|
2018-11-12 21:54:22 +00:00
|
|
|
|
}, optionsFromRemoteReq).then(assertParentIncludeChildren);
|
2018-10-19 18:56:51 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function givenParentAndChildWithHiddenProperty() {
|
|
|
|
|
Parent = memory.createModel('parent');
|
|
|
|
|
Child = memory.createModel('child', {}, {hidden: ['secret']});
|
|
|
|
|
Parent.hasMany(Child);
|
|
|
|
|
return Parent.create({
|
|
|
|
|
name: 'parent',
|
|
|
|
|
}).then(parent => {
|
|
|
|
|
return parent.children.create({
|
|
|
|
|
name: 'child',
|
|
|
|
|
secret: 'secret',
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
2018-10-19 18:56:51 +00:00
|
|
|
|
}
|
2018-10-12 19:40:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2016-05-26 21:21:37 +00:00
|
|
|
|
it('should throw error for property names containing dot', function() {
|
2016-08-19 17:46:59 +00:00
|
|
|
|
(function() { memory.createModel('Dotted', {'dot.name': String}); })
|
2016-05-26 21:21:37 +00:00
|
|
|
|
.should
|
|
|
|
|
.throw(/dot\(s\).*Dotted.*dot\.name/);
|
2015-04-02 07:49:04 +00:00
|
|
|
|
});
|
|
|
|
|
|
2016-01-19 02:48:58 +00:00
|
|
|
|
it('should report deprecation warning for property named constructor', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let message = 'deprecation not reported';
|
2016-01-19 02:48:58 +00:00
|
|
|
|
process.once('deprecation', function(err) { message = err.message; });
|
|
|
|
|
|
2016-08-19 17:46:59 +00:00
|
|
|
|
memory.createModel('Ctor', {'constructor': String});
|
2016-01-19 02:48:58 +00:00
|
|
|
|
|
|
|
|
|
message.should.match(/Property name should not be "constructor" in Model: Ctor/);
|
|
|
|
|
});
|
|
|
|
|
|
2016-05-26 21:21:37 +00:00
|
|
|
|
it('should throw error for dynamic property names containing dot',
|
|
|
|
|
function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Model = memory.createModel('DynamicDotted');
|
2016-08-19 17:46:59 +00:00
|
|
|
|
Model.create({'dot.name': 'dot.value'}, function(err) {
|
2016-05-26 21:21:37 +00:00
|
|
|
|
err.should.be.instanceOf(Error);
|
|
|
|
|
err.message.should.match(/dot\(s\).*DynamicDotted.*dot\.name/);
|
|
|
|
|
done();
|
|
|
|
|
});
|
2015-04-02 07:49:04 +00:00
|
|
|
|
});
|
2015-05-04 13:00:06 +00:00
|
|
|
|
|
2016-01-19 02:48:58 +00:00
|
|
|
|
it('should throw error for dynamic property named constructor', function(done) {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Model = memory.createModel('DynamicCtor');
|
2016-08-19 17:46:59 +00:00
|
|
|
|
Model.create({'constructor': 'myCtor'}, function(err) {
|
2016-01-19 02:48:58 +00:00
|
|
|
|
assert.equal(err.message, 'Property name "constructor" is not allowed in DynamicCtor data');
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2015-05-04 13:00:06 +00:00
|
|
|
|
it('should support "array" type shortcut', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const Model = memory.createModel('TwoArrays', {
|
2015-05-04 13:00:06 +00:00
|
|
|
|
regular: Array,
|
2016-04-01 11:48:17 +00:00
|
|
|
|
sugar: 'array',
|
2015-05-04 13:00:06 +00:00
|
|
|
|
});
|
|
|
|
|
|
2018-12-07 14:54:29 +00:00
|
|
|
|
const props = Model.definition.properties;
|
2015-05-04 13:00:06 +00:00
|
|
|
|
props.regular.type.should.equal(props.sugar.type);
|
|
|
|
|
});
|
2015-08-26 22:23:35 +00:00
|
|
|
|
|
|
|
|
|
context('hasPK', function() {
|
|
|
|
|
context('with primary key defined', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Todo;
|
2015-08-26 22:23:35 +00:00
|
|
|
|
before(function prepModel() {
|
|
|
|
|
Todo = new ModelDefinition(new ModelBuilder(), 'Todo', {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
content: 'string',
|
2015-08-26 22:23:35 +00:00
|
|
|
|
});
|
|
|
|
|
Todo.defineProperty('id', {
|
|
|
|
|
type: 'number',
|
2016-04-01 11:48:17 +00:00
|
|
|
|
id: true,
|
2015-08-26 22:23:35 +00:00
|
|
|
|
});
|
|
|
|
|
Todo.build();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return true', function() {
|
|
|
|
|
Todo.hasPK().should.be.ok;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
context('without primary key defined', function() {
|
2018-12-07 14:54:29 +00:00
|
|
|
|
let Todo;
|
2015-08-26 22:23:35 +00:00
|
|
|
|
before(function prepModel() {
|
|
|
|
|
Todo = new ModelDefinition(new ModelBuilder(), 'Todo', {
|
2016-04-01 11:48:17 +00:00
|
|
|
|
content: 'string',
|
2015-08-26 22:23:35 +00:00
|
|
|
|
});
|
|
|
|
|
Todo.build();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should return false', function() {
|
|
|
|
|
Todo.hasPK().should.not.be.ok;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2013-10-02 05:14:21 +00:00
|
|
|
|
});
|