710 lines
19 KiB
JavaScript
710 lines
19 KiB
JavaScript
// Copyright IBM Corp. 2015,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
|
|
|
|
'use strict';
|
|
|
|
const jdb = require('../');
|
|
const DataSource = jdb.DataSource;
|
|
const should = require('./init.js');
|
|
|
|
describe('Memory connector with mocked discovery', function() {
|
|
let ds;
|
|
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
const models = [{type: 'table', name: 'CUSTOMER', owner: 'STRONGLOOP'},
|
|
{type: 'table', name: 'INVENTORY', owner: 'STRONGLOOP'},
|
|
{type: 'table', name: 'LOCATION', owner: 'STRONGLOOP'}];
|
|
|
|
ds.discoverModelDefinitions = function(options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, models);
|
|
});
|
|
};
|
|
|
|
const modelProperties = [{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'PRODUCT_ID',
|
|
dataType: 'varchar',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
nullable: 0,
|
|
generated: true,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'LOCATION_ID',
|
|
dataType: 'varchar',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
nullable: 0,
|
|
generated: false,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'AVAILABLE',
|
|
dataType: 'int',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
nullable: 1,
|
|
generated: false,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'TOTAL',
|
|
dataType: 'int',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
nullable: 1,
|
|
generated: false,
|
|
}];
|
|
|
|
ds.discoverModelProperties = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, modelProperties);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should convert table names to pascal cases and column names to camel case', function(done) {
|
|
ds.discoverSchemas('INVENTORY', {}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('Inventory');
|
|
Object.keys(s.properties).should.be.eql(
|
|
['productId', 'locationId', 'available', 'total'],
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should have jsonSchema: {nullable: true} in property for `available`', function(done) {
|
|
ds.discoverSchemas('INVENTORY', {}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('Inventory');
|
|
s.properties.available.should.have.property('jsonSchema');
|
|
s.properties.available.jsonSchema.should.have.property('nullable');
|
|
s.properties.available.jsonSchema.nullable.should.be.eql(true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should keep the column names the same as database', function(done) {
|
|
ds.discoverSchemas('INVENTORY', {disableCamelCase: true}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('Inventory');
|
|
Object.keys(s.properties).should.be.eql(
|
|
['PRODUCT_ID', 'LOCATION_ID', 'AVAILABLE', 'TOTAL'],
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should convert table/column names with custom mapper', function(done) {
|
|
ds.discoverSchemas('INVENTORY', {
|
|
nameMapper: function(type, name) {
|
|
// Convert all names to lower case
|
|
return name.toLowerCase();
|
|
},
|
|
}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('inventory');
|
|
Object.keys(s.properties).should.be.eql(
|
|
['product_id', 'location_id', 'available', 'total'],
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should not convert table/column names with null custom mapper',
|
|
function(done) {
|
|
ds.discoverSchemas('INVENTORY', {nameMapper: null}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('INVENTORY');
|
|
Object.keys(s.properties).should.be.eql(
|
|
['PRODUCT_ID', 'LOCATION_ID', 'AVAILABLE', 'TOTAL'],
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should honor connector\'s discoverSchemas implementation',
|
|
function(done) {
|
|
const models = {
|
|
inventory: {
|
|
product: {type: 'string'},
|
|
location: {type: 'string'},
|
|
},
|
|
};
|
|
ds.connector.discoverSchemas = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, models);
|
|
});
|
|
};
|
|
ds.discoverSchemas('INVENTORY', {nameMapper: null}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.be.eql(models);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter',
|
|
function(done) {
|
|
const models = {
|
|
inventory: {
|
|
product: {type: 'string'},
|
|
location: {type: 'string'},
|
|
},
|
|
};
|
|
ds.connector.discoverSchemas = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, models);
|
|
});
|
|
};
|
|
|
|
const options = function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.be.eql(models);
|
|
done();
|
|
};
|
|
|
|
ds.discoverSchemas('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover schemas using `discoverSchemas` - promise variant',
|
|
function(done) {
|
|
ds.connector.discoverSchemas = null;
|
|
ds.discoverSchemas('INVENTORY', {})
|
|
.then(function(schemas) {
|
|
schemas.should.have.property('STRONGLOOP.INVENTORY');
|
|
|
|
const s = schemas['STRONGLOOP.INVENTORY'];
|
|
s.name.should.be.eql('Inventory');
|
|
|
|
Object.keys(s.properties).should.be.eql(
|
|
['productId', 'locationId', 'available', 'total'],
|
|
);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
|
|
describe('discoverSchema', function() {
|
|
let models;
|
|
let schema;
|
|
before(function() {
|
|
schema = {
|
|
name: 'Inventory',
|
|
options: {
|
|
idInjection: false,
|
|
memory: {schema: 'STRONGLOOP', table: 'INVENTORY'},
|
|
},
|
|
properties: {
|
|
available: {
|
|
length: null,
|
|
jsonSchema: {
|
|
nullable: true,
|
|
},
|
|
memory: {
|
|
columnName: 'AVAILABLE',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
dataType: 'int',
|
|
nullable: 1,
|
|
generated: false,
|
|
},
|
|
precision: 10,
|
|
required: false,
|
|
scale: 0,
|
|
type: undefined,
|
|
generated: false,
|
|
},
|
|
locationId: {
|
|
length: 20,
|
|
jsonSchema: {
|
|
nullable: false,
|
|
},
|
|
memory: {
|
|
columnName: 'LOCATION_ID',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
dataType: 'varchar',
|
|
nullable: 0,
|
|
generated: false,
|
|
},
|
|
precision: null,
|
|
required: true,
|
|
scale: null,
|
|
type: undefined,
|
|
generated: false,
|
|
},
|
|
productId: {
|
|
length: 20,
|
|
jsonSchema: {
|
|
nullable: false,
|
|
},
|
|
memory: {
|
|
columnName: 'PRODUCT_ID',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
dataType: 'varchar',
|
|
nullable: 0,
|
|
generated: true,
|
|
},
|
|
precision: null,
|
|
required: false,
|
|
scale: null,
|
|
type: undefined,
|
|
generated: true,
|
|
},
|
|
total: {
|
|
length: null,
|
|
jsonSchema: {
|
|
nullable: true,
|
|
},
|
|
memory: {
|
|
columnName: 'TOTAL',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
dataType: 'int',
|
|
nullable: 1,
|
|
generated: false,
|
|
},
|
|
precision: 10,
|
|
required: false,
|
|
scale: 0,
|
|
type: undefined,
|
|
generated: false,
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
it('should discover schema using `discoverSchema`', function(done) {
|
|
ds.discoverSchema('INVENTORY', {}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.be.eql(schema);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, schemas) {
|
|
if (err) return done(err);
|
|
schemas.should.be.eql(schema);
|
|
done();
|
|
};
|
|
|
|
ds.discoverSchema('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover schema using `discoverSchema` - promise variant', function(done) {
|
|
ds.discoverSchema('INVENTORY', {})
|
|
.then(function(schemas) {
|
|
schemas.should.be.eql(schema);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('discoverModelDefinitions', function() {
|
|
let ds;
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
const models = [{type: 'table', name: 'CUSTOMER', owner: 'STRONGLOOP'},
|
|
{type: 'table', name: 'INVENTORY', owner: 'STRONGLOOP'},
|
|
{type: 'table', name: 'LOCATION', owner: 'STRONGLOOP'}];
|
|
|
|
ds.connector.discoverModelDefinitions = function(options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, models);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should discover model using `discoverModelDefinitions`', function(done) {
|
|
ds.discoverModelDefinitions({}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
|
|
const tableNames = schemas.map(function(s) {
|
|
return s.name;
|
|
});
|
|
|
|
tableNames.should.be.eql(
|
|
['CUSTOMER', 'INVENTORY', 'LOCATION'],
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, schemas) {
|
|
if (err) return done(err);
|
|
|
|
const tableNames = schemas.map(function(s) {
|
|
return s.name;
|
|
});
|
|
|
|
tableNames.should.be.eql(
|
|
['CUSTOMER', 'INVENTORY', 'LOCATION'],
|
|
);
|
|
done();
|
|
};
|
|
|
|
ds.discoverModelDefinitions(options);
|
|
});
|
|
|
|
it('should discover model using `discoverModelDefinitions` - promise variant', function(done) {
|
|
ds.discoverModelDefinitions({})
|
|
.then(function(schemas) {
|
|
const tableNames = schemas.map(function(s) {
|
|
return s.name;
|
|
});
|
|
|
|
tableNames.should.be.eql(
|
|
['CUSTOMER', 'INVENTORY', 'LOCATION'],
|
|
);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('discoverModelProperties', function() {
|
|
let ds;
|
|
let modelProperties;
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
modelProperties = [{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'PRODUCT_ID',
|
|
dataType: 'varchar',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
nullable: 0,
|
|
generated: false,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'LOCATION_ID',
|
|
dataType: 'varchar',
|
|
dataLength: 20,
|
|
dataPrecision: null,
|
|
dataScale: null,
|
|
nullable: 0,
|
|
generated: false,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'AVAILABLE',
|
|
dataType: 'int',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
nullable: 1,
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'TOTAL',
|
|
dataType: 'int',
|
|
dataLength: null,
|
|
dataPrecision: 10,
|
|
dataScale: 0,
|
|
nullable: 1,
|
|
}];
|
|
|
|
ds.connector.discoverModelProperties = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, modelProperties);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, schemas) {
|
|
if (err) return done(err);
|
|
|
|
schemas.should.be.eql(modelProperties);
|
|
done();
|
|
};
|
|
|
|
ds.discoverModelProperties('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover model metadata using `discoverModelProperties`', function(done) {
|
|
ds.discoverModelProperties('INVENTORY', {}, function(err, schemas) {
|
|
if (err) return done(err);
|
|
|
|
schemas.should.be.eql(modelProperties);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should discover model metadata using `discoverModelProperties` - promise variant', function(done) {
|
|
ds.discoverModelProperties('INVENTORY', {})
|
|
.then(function(schemas) {
|
|
schemas.should.be.eql(modelProperties);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('discoverPrimaryKeys', function() {
|
|
let ds;
|
|
let modelProperties, primaryKeys;
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
primaryKeys = [
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'PRODUCT_ID',
|
|
keySeq: 1,
|
|
pkName: 'ID_PK',
|
|
},
|
|
{
|
|
owner: 'STRONGLOOP',
|
|
tableName: 'INVENTORY',
|
|
columnName: 'LOCATION_ID',
|
|
keySeq: 2,
|
|
pkName: 'ID_PK',
|
|
}];
|
|
|
|
ds.connector.discoverPrimaryKeys = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, primaryKeys);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should discover primary key definitions using `discoverPrimaryKeys`', function(done) {
|
|
ds.discoverPrimaryKeys('INVENTORY', {}, function(err, modelPrimaryKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelPrimaryKeys.should.be.eql(primaryKeys);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, modelPrimaryKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelPrimaryKeys.should.be.eql(primaryKeys);
|
|
done();
|
|
};
|
|
ds.discoverPrimaryKeys('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover primary key definitions using `discoverPrimaryKeys` - promise variant', function(done) {
|
|
ds.discoverPrimaryKeys('INVENTORY', {})
|
|
.then(function(modelPrimaryKeys) {
|
|
modelPrimaryKeys.should.be.eql(primaryKeys);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('discoverForeignKeys', function() {
|
|
let ds;
|
|
let modelProperties, foreignKeys;
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
foreignKeys = [{
|
|
fkOwner: 'STRONGLOOP',
|
|
fkName: 'PRODUCT_FK',
|
|
fkTableName: 'INVENTORY',
|
|
fkColumnName: 'PRODUCT_ID',
|
|
keySeq: 1,
|
|
pkOwner: 'STRONGLOOP',
|
|
pkName: 'PRODUCT_PK',
|
|
pkTableName: 'PRODUCT',
|
|
pkColumnName: 'ID',
|
|
}];
|
|
|
|
ds.connector.discoverForeignKeys = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, foreignKeys);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should discover foreign key definitions using `discoverForeignKeys`', function(done) {
|
|
ds.discoverForeignKeys('INVENTORY', {}, function(err, modelForeignKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelForeignKeys.should.be.eql(foreignKeys);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, modelForeignKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelForeignKeys.should.be.eql(foreignKeys);
|
|
done();
|
|
};
|
|
|
|
ds.discoverForeignKeys('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover foreign key definitions using `discoverForeignKeys` - promise variant', function(done) {
|
|
ds.discoverForeignKeys('INVENTORY', {})
|
|
.then(function(modelForeignKeys) {
|
|
modelForeignKeys.should.be.eql(foreignKeys);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('discoverExportedForeignKeys', function() {
|
|
let ds;
|
|
let modelProperties, exportedForeignKeys;
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
|
|
exportedForeignKeys = [{
|
|
fkName: 'PRODUCT_FK',
|
|
fkOwner: 'STRONGLOOP',
|
|
fkTableName: 'INVENTORY',
|
|
fkColumnName: 'PRODUCT_ID',
|
|
keySeq: 1,
|
|
pkName: 'PRODUCT_PK',
|
|
pkOwner: 'STRONGLOOP',
|
|
pkTableName: 'PRODUCT',
|
|
pkColumnName: 'ID',
|
|
}];
|
|
|
|
ds.connector.discoverExportedForeignKeys = function(modelName, options, cb) {
|
|
process.nextTick(function() {
|
|
cb(null, exportedForeignKeys);
|
|
});
|
|
};
|
|
});
|
|
|
|
it('should discover foreign key definitions using `discoverExportedForeignKeys`', function(done) {
|
|
ds.discoverExportedForeignKeys('INVENTORY', {}, function(err, modelForeignKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelForeignKeys.should.be.eql(exportedForeignKeys);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should callback function, passed as options parameter', function(done) {
|
|
const options = function(err, modelForeignKeys) {
|
|
if (err) return done(err);
|
|
|
|
modelForeignKeys.should.be.eql(exportedForeignKeys);
|
|
done();
|
|
};
|
|
|
|
ds.discoverExportedForeignKeys('INVENTORY', options);
|
|
});
|
|
|
|
it('should discover foreign key definitions using `discoverExportedForeignKeys` - promise variant',
|
|
function(done) {
|
|
ds.discoverExportedForeignKeys('INVENTORY', {})
|
|
.then(function(modelForeignKeys) {
|
|
modelForeignKeys.should.be.eql(exportedForeignKeys);
|
|
done();
|
|
})
|
|
.catch(function(err) {
|
|
done(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Mock connector', function() {
|
|
const mockConnectors = require('./mock-connectors');
|
|
describe('customFieldSettings', function() {
|
|
const ds = new DataSource(mockConnectors.customFieldSettings);
|
|
|
|
it('should be present in discoverSchemas', function(done) {
|
|
ds.discoverSchemas('person', function(err, schemas) {
|
|
should.not.exist(err);
|
|
schemas.should.be.an.Object;
|
|
schemas['public.person'].properties.name
|
|
.custom.storage.should.equal('quantum');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Default memory connector', function() {
|
|
const nonExistantError = 'Table \'NONEXISTENT\' does not exist.';
|
|
let ds;
|
|
|
|
before(function() {
|
|
ds = new DataSource({connector: 'memory'});
|
|
});
|
|
|
|
it('discoverSchema should return an error when table does not exist', function(done) {
|
|
ds.discoverSchema('NONEXISTENT', {}, function(err, schemas) {
|
|
should.exist(err);
|
|
err.message.should.eql(nonExistantError);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('discoverSchemas should return an error when table does not exist', function(done) {
|
|
ds.discoverSchemas('NONEXISTENT', {}, function(err, schemas) {
|
|
should.exist(err);
|
|
err.message.should.eql(nonExistantError);
|
|
done();
|
|
});
|
|
});
|
|
});
|