2928 lines
88 KiB
JavaScript
2928 lines
88 KiB
JavaScript
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
|
|
// Node module: loopback-boot
|
|
// This file is licensed under the MIT License.
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
'use strict';
|
|
|
|
var boot = require('../');
|
|
var fs = require('fs-extra');
|
|
var path = require('path');
|
|
var expect = require('chai').expect;
|
|
var sandbox = require('./helpers/sandbox');
|
|
var appdir = require('./helpers/appdir');
|
|
|
|
// add coffee-script to require.extensions
|
|
require('coffee-script/register');
|
|
|
|
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
|
|
|
|
describe('compiler', function() {
|
|
beforeEach(sandbox.reset);
|
|
beforeEach(appdir.init);
|
|
|
|
function expectCompileToThrow(err, options, done) {
|
|
if (typeof options === 'function') {
|
|
done = options;
|
|
options = undefined;
|
|
}
|
|
boot.compile(options || appdir.PATH, function(err) {
|
|
expect(function() {
|
|
if (err) throw err;
|
|
}).to.throw(err);
|
|
done();
|
|
});
|
|
}
|
|
|
|
function expectCompileToNotThrow(options, done) {
|
|
if (typeof options === 'function') {
|
|
done = options;
|
|
options = undefined;
|
|
}
|
|
boot.compile(options || appdir.PATH, function(err) {
|
|
expect(function() {
|
|
if (err) throw err;
|
|
}).to.not.throw();
|
|
done();
|
|
});
|
|
}
|
|
|
|
describe('from options', function() {
|
|
var options, instructions, appConfig;
|
|
|
|
beforeEach(function(done) {
|
|
options = {
|
|
application: {
|
|
port: 3000,
|
|
host: '127.0.0.1',
|
|
restApiRoot: '/rest-api',
|
|
foo: {bar: 'bat'},
|
|
baz: true,
|
|
},
|
|
models: {
|
|
'foo-bar-bat-baz': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
dataSources: {
|
|
'the-db': {
|
|
connector: 'memory',
|
|
defaultForType: 'db',
|
|
},
|
|
},
|
|
};
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
appConfig = context.instructions.application;
|
|
instructions = context.instructions;
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('has port setting', function() {
|
|
expect(appConfig).to.have.property('port', 3000);
|
|
});
|
|
|
|
it('has host setting', function() {
|
|
expect(appConfig).to.have.property('host', '127.0.0.1');
|
|
});
|
|
|
|
it('has restApiRoot setting', function() {
|
|
expect(appConfig).to.have.property('restApiRoot', '/rest-api');
|
|
});
|
|
|
|
it('has other settings', function() {
|
|
expect(appConfig).to.have.property('baz', true);
|
|
expect(appConfig.foo, 'appConfig.foo').to.eql({
|
|
bar: 'bat',
|
|
});
|
|
});
|
|
|
|
it('has models definition', function() {
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'foo-bar-bat-baz',
|
|
config: {
|
|
dataSource: 'the-db',
|
|
},
|
|
definition: undefined,
|
|
sourceFile: undefined,
|
|
});
|
|
});
|
|
|
|
it('has datasources definition', function() {
|
|
expect(instructions.dataSources).to.eql(options.dataSources);
|
|
});
|
|
|
|
describe('with custom model definitions', function(done) {
|
|
var dataSources = {
|
|
'the-db': {connector: 'memory'},
|
|
};
|
|
|
|
it('loads model without definition', function(done) {
|
|
var instruction = boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-without-definition': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name)
|
|
.to.equal('model-without-definition');
|
|
expect(instructions.models[0].definition).to.equal(undefined);
|
|
expect(instructions.models[0].sourceFile).to.equal(undefined);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads coffeescript models', function(done) {
|
|
var modelScript = appdir.writeFileSync(
|
|
'custom-models/coffee-model-with-definition.coffee', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'coffee-model-with-definition': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [
|
|
{
|
|
definition: {
|
|
name: 'coffee-model-with-definition',
|
|
},
|
|
sourceFile: modelScript,
|
|
},
|
|
],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name)
|
|
.to.equal('coffee-model-with-definition');
|
|
expect(instructions.models[0].definition).to.eql({
|
|
name: 'coffee-model-with-definition',
|
|
});
|
|
expect(instructions.models[0].sourceFile).to.equal(modelScript);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('handles sourceFile path without extension (.js)', function(done) {
|
|
var modelScript = appdir.writeFileSync(
|
|
'custom-models/model-without-ext.coffee',
|
|
'');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-without-ext': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [{
|
|
definition: {
|
|
name: 'model-without-ext',
|
|
},
|
|
sourceFile: pathWithoutExtension(modelScript),
|
|
}],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name).to.equal('model-without-ext');
|
|
expect(instructions.models[0].sourceFile).to.equal(modelScript);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('handles sourceFile path without extension (.coffee)', function(done) {
|
|
var modelScript = appdir.writeFileSync(
|
|
'custom-models/model-without-ext.coffee',
|
|
'');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-without-ext': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [{
|
|
definition: {
|
|
name: 'model-without-ext',
|
|
},
|
|
sourceFile: pathWithoutExtension(modelScript),
|
|
}],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name).to.equal('model-without-ext');
|
|
expect(instructions.models[0].sourceFile).to.equal(modelScript);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('sets source file path if the file exist', function(done) {
|
|
var modelScript = appdir.writeFileSync(
|
|
'custom-models/model-with-definition.js',
|
|
'');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-with-definition': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [
|
|
{
|
|
definition: {
|
|
name: 'model-with-definition',
|
|
},
|
|
sourceFile: modelScript,
|
|
},
|
|
],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name).to.equal('model-with-definition');
|
|
expect(instructions.models[0].definition).not.to.equal(undefined);
|
|
expect(instructions.models[0].sourceFile).to.equal(modelScript);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not set source file path if the file does not exist.',
|
|
function(done) {
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-with-definition-with-falsey-source-file': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [
|
|
{
|
|
definition: {
|
|
name: 'model-with-definition-with-falsey-source-file',
|
|
},
|
|
sourceFile: appdir.resolve('custom-models',
|
|
'file-does-not-exist.js'),
|
|
},
|
|
],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name)
|
|
.to.equal('model-with-definition-with-falsey-source-file');
|
|
expect(instructions.models[0].definition).not.to.equal(undefined);
|
|
expect(instructions.models[0].sourceFile).to.equal(undefined);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not set source file path if no source file supplied.',
|
|
function(done) {
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'model-with-definition-without-source-file-property': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [
|
|
{
|
|
definition: {
|
|
name: 'model-with-definition-without-source-file-property',
|
|
},
|
|
// sourceFile is not set
|
|
},
|
|
],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models[0].name)
|
|
.to.equal('model-with-definition-without-source-file-property');
|
|
expect(instructions.models[0].definition).not.to.equal(undefined);
|
|
expect(instructions.models[0].sourceFile).to.equal(undefined);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads models defined in `models` only.', function(done) {
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
models: {
|
|
'some-model': {
|
|
dataSource: 'the-db',
|
|
},
|
|
},
|
|
modelDefinitions: [
|
|
{
|
|
definition: {
|
|
name: 'some-model',
|
|
},
|
|
},
|
|
{
|
|
definition: {
|
|
name: 'another-model',
|
|
},
|
|
},
|
|
],
|
|
dataSources: dataSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.models.map(getNameProperty))
|
|
.to.eql(['some-model']);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('from directory', function(done) {
|
|
it('loads config files', function(done) {
|
|
boot.compile(SIMPLE_APP, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'User',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: undefined,
|
|
sourceFile: undefined,
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('merges datasource configs from multiple files', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('datasources.local.json', {
|
|
db: {local: 'applied'},
|
|
});
|
|
|
|
var env = process.env.NODE_ENV || 'development';
|
|
appdir.writeConfigFileSync('datasources.' + env + '.json', {
|
|
db: {env: 'applied'},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var db = instructions.dataSources.db;
|
|
expect(db).to.have.property('local', 'applied');
|
|
expect(db).to.have.property('env', 'applied');
|
|
|
|
var expectedLoadOrder = ['local', 'env'];
|
|
var actualLoadOrder = Object.keys(db).filter(function(k) {
|
|
return expectedLoadOrder.indexOf(k) !== -1;
|
|
});
|
|
|
|
expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports .js for custom datasource config files', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeFileSync('datasources.local.js',
|
|
'module.exports = { db: { fromJs: true } };');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var db = instructions.dataSources.db;
|
|
expect(db).to.have.property('fromJs', true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('merges new Object values', function(done) {
|
|
var objectValue = {key: 'value'};
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('datasources.local.json', {
|
|
db: {nested: objectValue},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var db = instructions.dataSources.db;
|
|
expect(db).to.have.property('nested');
|
|
expect(db.nested).to.eql(objectValue);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('deeply merges Object values', function(done) {
|
|
appdir.createConfigFilesSync({}, {
|
|
email: {
|
|
transport: {
|
|
host: 'localhost',
|
|
},
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('datasources.local.json', {
|
|
email: {
|
|
transport: {
|
|
host: 'mail.example.com',
|
|
},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
var email = instructions.dataSources.email;
|
|
expect(email.transport.host).to.equal('mail.example.com');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('deeply merges Array values of the same length', function(done) {
|
|
appdir.createConfigFilesSync({}, {
|
|
rest: {
|
|
operations: [
|
|
{
|
|
template: {
|
|
method: 'POST',
|
|
url: 'http://localhost:12345',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
});
|
|
appdir.writeConfigFileSync('datasources.local.json', {
|
|
rest: {
|
|
operations: [
|
|
{
|
|
template: {
|
|
url: 'http://api.example.com',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var rest = instructions.dataSources.rest;
|
|
expect(rest.operations[0].template).to.eql({
|
|
method: 'POST', // the value from datasources.json
|
|
url: 'http://api.example.com', // overriden in datasources.local.json
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('merges Array properties', function(done) {
|
|
var arrayValue = ['value'];
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('datasources.local.json', {
|
|
db: {nested: arrayValue},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var db = instructions.dataSources.db;
|
|
expect(db).to.have.property('nested');
|
|
expect(db.nested).to.eql(arrayValue);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not cache loaded values', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'strong-error-handler': {params: {debug: false}},
|
|
});
|
|
appdir.writeConfigFileSync('middleware.development.json', {
|
|
'strong-error-handler': {params: {debug: true}},
|
|
});
|
|
|
|
// Here we load main config and merge it with DEV overrides
|
|
var bootOptions = {
|
|
appRootDir: appdir.PATH,
|
|
env: 'development',
|
|
phases: ['load'],
|
|
};
|
|
var productionBootOptions = {
|
|
appRootDir: appdir.PATH,
|
|
env: 'production',
|
|
phases: ['load'],
|
|
};
|
|
boot.compile(bootOptions, function(err, context) {
|
|
var config = context.configurations.middleware;
|
|
expect(config['strong-error-handler'].params.debug,
|
|
'debug in development').to.equal(true);
|
|
|
|
boot.compile(productionBootOptions, function(err, context2) {
|
|
var config = context2.configurations.middleware;
|
|
expect(config['strong-error-handler'].params.debug,
|
|
'debug in production').to.equal(false);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('allows env specific model-config json', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('model-config.local.json', {
|
|
foo: {dataSource: 'db'},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.have.property('name', 'foo');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('allows env specific model-config json to be merged', function(done) {
|
|
appdir.createConfigFilesSync(null, null,
|
|
{foo: {dataSource: 'mongo', public: false}});
|
|
appdir.writeConfigFileSync('model-config.local.json', {
|
|
foo: {dataSource: 'db'},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.have.property('name', 'foo');
|
|
expect(instructions.models[0].config).to.eql({
|
|
dataSource: 'db',
|
|
public: false,
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('allows env specific model-config js', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeFileSync('model-config.local.js',
|
|
'module.exports = { foo: { dataSource: \'db\' } };');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.have.property('name', 'foo');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('refuses to merge Array properties of different length', function(done) {
|
|
appdir.createConfigFilesSync({
|
|
nest: {
|
|
array: [],
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('config.local.json', {
|
|
nest: {
|
|
array: [
|
|
{
|
|
key: 'value',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expectCompileToThrow(/array values of different length.*nest\.array/,
|
|
done);
|
|
});
|
|
|
|
it('refuses to merge Array of different length in Array', function(done) {
|
|
appdir.createConfigFilesSync({
|
|
key: [[]],
|
|
});
|
|
|
|
appdir.writeConfigFileSync('config.local.json', {
|
|
key: [['value']],
|
|
});
|
|
|
|
expectCompileToThrow(/array values of different length.*key\[0\]/, done);
|
|
});
|
|
|
|
it('returns full key of an incorrect Array value', function(done) {
|
|
appdir.createConfigFilesSync({
|
|
toplevel: [
|
|
{
|
|
nested: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
appdir.writeConfigFileSync('config.local.json', {
|
|
toplevel: [
|
|
{
|
|
nested: ['value'],
|
|
},
|
|
],
|
|
});
|
|
|
|
expectCompileToThrow(
|
|
/array values of different length.*toplevel\[0\]\.nested/,
|
|
done);
|
|
});
|
|
|
|
it('refuses to merge incompatible object properties', function(done) {
|
|
appdir.createConfigFilesSync({
|
|
key: [],
|
|
});
|
|
appdir.writeConfigFileSync('config.local.json', {
|
|
key: {},
|
|
});
|
|
|
|
expectCompileToThrow(/incompatible types.*key/, done);
|
|
});
|
|
|
|
it('refuses to merge incompatible array items', function(done) {
|
|
appdir.createConfigFilesSync({
|
|
key: [[]],
|
|
});
|
|
appdir.writeConfigFileSync('config.local.json', {
|
|
key: [{}],
|
|
});
|
|
|
|
expectCompileToThrow(/incompatible types.*key\[0\]/, done);
|
|
});
|
|
|
|
it('merges app configs from multiple files', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
|
|
appdir.writeConfigFileSync('config.local.json', {cfgLocal: 'applied'});
|
|
|
|
var env = process.env.NODE_ENV || 'development';
|
|
appdir.writeConfigFileSync('config.' + env + '.json',
|
|
{cfgEnv: 'applied'});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
var appConfig = instructions.application;
|
|
|
|
expect(appConfig).to.have.property('cfgLocal', 'applied');
|
|
expect(appConfig).to.have.property('cfgEnv', 'applied');
|
|
|
|
var expectedLoadOrder = ['cfgLocal', 'cfgEnv'];
|
|
var actualLoadOrder = Object.keys(appConfig).filter(function(k) {
|
|
return expectedLoadOrder.indexOf(k) !== -1;
|
|
});
|
|
|
|
expect(actualLoadOrder, 'load order').to.eql(expectedLoadOrder);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports .js for custom app config files', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeFileSync('config.local.js',
|
|
'module.exports = { fromJs: true };');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
var appConfig = instructions.application;
|
|
|
|
expect(appConfig).to.have.property('fromJs', true);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `appConfigRootDir` option', function(done) {
|
|
appdir.createConfigFilesSync({port: 3000});
|
|
|
|
var customDir = path.resolve(appdir.PATH, 'custom');
|
|
fs.mkdirsSync(customDir);
|
|
fs.renameSync(
|
|
path.resolve(appdir.PATH, 'config.json'),
|
|
path.resolve(customDir, 'config.json'));
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
appConfigRootDir: path.resolve(appdir.PATH, 'custom'),
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.application).to.have.property('port');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `dsRootDir` option', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
|
|
var customDir = path.resolve(appdir.PATH, 'custom');
|
|
fs.mkdirsSync(customDir);
|
|
fs.renameSync(
|
|
path.resolve(appdir.PATH, 'datasources.json'),
|
|
path.resolve(customDir, 'datasources.json'));
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
dsRootDir: path.resolve(appdir.PATH, 'custom'),
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.dataSources).to.have.property('db');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `modelsRootDir` option', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('custom/model-config.json', {
|
|
foo: {dataSource: 'db'},
|
|
});
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
modelsRootDir: path.resolve(appdir.PATH, 'custom'),
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.have.property('name', 'foo');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('includes boot/*.js scripts', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `bootDirs` option', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: [path.dirname(initJs)],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should resolve relative path in `bootDirs`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: ['./custom-boot'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should resolve non-relative path in `bootDirs`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: ['custom-boot'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('ignores index.js in `bootDirs`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeFileSync('custom-boot/index.js', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: ['./custom-boot'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.have.length(0);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers coffeescript over json in `appRootDir/bootDir`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var coffee = appdir.writeFileSync('./custom-boot/init.coffee', '');
|
|
appdir.writeFileSync('./custom-boot/init.json', {});
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: ['./custom-boot'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([coffee]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers coffeescript over json in `bootDir` non-relative path',
|
|
function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var coffee = appdir.writeFileSync('custom-boot/init.coffee',
|
|
'');
|
|
appdir.writeFileSync('custom-boot/init.json', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: ['custom-boot'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([coffee]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `bootScripts` option', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: [initJs],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should remove duplicate scripts', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootDirs: [path.dirname(initJs)],
|
|
bootScripts: [initJs],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should resolve relative path in `bootScripts`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js',
|
|
'module.exports = function(app) { app.fnCalled = true; };');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['./custom-boot/init.js'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should resolve non-relative path in `bootScripts`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['custom-boot/init.js'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves missing extensions in `bootScripts`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('custom-boot/init.js', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['./custom-boot/init'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves missing extensions in `bootScripts` in module relative path',
|
|
function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync(
|
|
'node_modules/custom-boot/init.js', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['custom-boot/init'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves module relative path for `bootScripts`', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var initJs = appdir.writeFileSync('node_modules/custom-boot/init.js', '');
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['custom-boot/init.js'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([initJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('explores `bootScripts` in app relative path', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
var appJs = appdir.writeFileSync('./custom-boot/init.js', '');
|
|
|
|
appdir.writeFileSync('node_modules/custom-boot/init.js', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
bootScripts: ['custom-boot/init.js'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.bootScripts).to.eql([appJs]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('ignores models/ subdirectory', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeFileSync('models/my-model.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.bootScripts).to.not.have.property('models');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('throws when models-config.json contains 1.x `properties`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
foo: {properties: {name: 'string'}},
|
|
});
|
|
|
|
expectCompileToThrow(/unsupported 1\.x format/, done);
|
|
});
|
|
|
|
it('throws when model-config.json contains 1.x `options.base`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Customer: {options: {base: 'User'}},
|
|
});
|
|
|
|
expectCompileToThrow(/unsupported 1\.x format/, done);
|
|
});
|
|
|
|
it('loads models from `./models`', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {name: 'Car'});
|
|
appdir.writeFileSync('models/car.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'Car',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'Car',
|
|
},
|
|
sourceFile: path.resolve(appdir.PATH, 'models', 'car.js'),
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads coffeescript models from `./models`', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {name: 'Car'});
|
|
appdir.writeFileSync('models/car.coffee', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'Car',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'Car',
|
|
},
|
|
sourceFile: path.resolve(appdir.PATH, 'models', 'car.coffee'),
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `modelSources` option', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('custom-models/car.json', {name: 'Car'});
|
|
appdir.writeFileSync('custom-models/car.js', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
modelSources: ['./custom-models'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'Car',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'Car',
|
|
},
|
|
sourceFile: path.resolve(appdir.PATH, 'custom-models', 'car.js'),
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `sources` option in `model-config.json`', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
_meta: {
|
|
sources: ['./custom-models'],
|
|
},
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('custom-models/car.json', {name: 'Car'});
|
|
appdir.writeFileSync('custom-models/car.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'Car',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'Car',
|
|
},
|
|
sourceFile: path.resolve(appdir.PATH, 'custom-models', 'car.js'),
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports sources relative to node_modules', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
User: {dataSource: 'db'},
|
|
});
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
modelSources: [
|
|
'loopback/common/models',
|
|
'loopback/common/dir-does-not-exist',
|
|
],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0]).to.eql({
|
|
name: 'User',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: require('loopback/common/models/user.json'),
|
|
sourceFile: require.resolve('loopback/common/models/user.js'),
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves relative path in `modelSources` option', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('custom-models/car.json', {name: 'Car'});
|
|
var appJS = appdir.writeFileSync('custom-models/car.js', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
modelSources: ['./custom-models'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0].sourceFile).to.equal(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves module relative path in `modelSources` option',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('node_modules/custom-models/car.json',
|
|
{name: 'Car'});
|
|
var appJS = appdir.writeFileSync(
|
|
'node_modules/custom-models/car.js', '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
modelSources: ['custom-models'],
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0].sourceFile).to.equal(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves relative path in `sources` option in `model-config.json`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
_meta: {
|
|
sources: ['./custom-models'],
|
|
},
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('custom-models/car.json', {name: 'Car'});
|
|
var appJS = appdir.writeFileSync('custom-models/car.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0].sourceFile).to.equal(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves module relative path in `sources` option in model-config.json',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
_meta: {
|
|
sources: ['custom-models'],
|
|
},
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('node_modules/custom-models/car.json',
|
|
{name: 'Car'});
|
|
|
|
var appJS = appdir.writeFileSync(
|
|
'node_modules/custom-models/car.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.have.length(1);
|
|
expect(instructions.models[0].sourceFile).to.equal(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('handles model definitions with no code', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {name: 'Car'});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.eql([{
|
|
name: 'Car',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'Car',
|
|
},
|
|
sourceFile: undefined,
|
|
}]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('excludes models not listed in `model-config.json`', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {name: 'Car'});
|
|
appdir.writeConfigFileSync('models/bar.json', {name: 'Bar'});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var models = instructions.models.map(getNameProperty);
|
|
expect(models).to.eql(['Car']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('includes models used as Base models', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
base: 'Vehicle',
|
|
});
|
|
appdir.writeConfigFileSync('models/vehicle.json', {
|
|
name: 'Vehicle',
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
var models = instructions.models;
|
|
var modelNames = models.map(getNameProperty);
|
|
|
|
expect(modelNames).to.eql(['Vehicle', 'Car']);
|
|
expect(models[0].config).to.equal(undefined);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('excludes pre-built base models', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
base: 'Model',
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['Car']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('sorts models, base models first', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Vehicle: {dataSource: 'db'},
|
|
FlyingCar: {dataSource: 'db'},
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
base: 'Vehicle',
|
|
});
|
|
appdir.writeConfigFileSync('models/vehicle.json', {
|
|
name: 'Vehicle',
|
|
});
|
|
appdir.writeConfigFileSync('models/flying-car.json', {
|
|
name: 'FlyingCar',
|
|
base: 'Car',
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['Vehicle', 'Car', 'FlyingCar']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('detects circular Model dependencies', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Vehicle: {dataSource: 'db'},
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
base: 'Vehicle',
|
|
});
|
|
appdir.writeConfigFileSync('models/vehicle.json', {
|
|
name: 'Vehicle',
|
|
base: 'Car',
|
|
});
|
|
|
|
expectCompileToThrow(/cyclic dependency/i, done);
|
|
});
|
|
|
|
it('uses file name as default value for model name', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['Car']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('uses `OrderItem` as default model name for file with name `order-item`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
OrderItem: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/order-item.json', {});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['OrderItem']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('uses `OrderItem` as default model name for file with name `order_item`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
OrderItem: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/order_item.json', {});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['OrderItem']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('uses `OrderItem` as default model name for file with name `order item`',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
OrderItem: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/order item.json', {});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['OrderItem']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('overrides `default model name` by `name` in model definition',
|
|
function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
overrideCar: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {name: 'overrideCar'});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var modelNames = instructions.models.map(getNameProperty);
|
|
expect(modelNames).to.eql(['overrideCar']);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('overwrites model with same default name', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
'OrderItem': {dataSource: 'db'},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('models/order-item.json', {
|
|
properties: {
|
|
price: {type: 'number'},
|
|
},
|
|
});
|
|
appdir.writeFileSync('models/order-item.js', '');
|
|
|
|
appdir.writeConfigFileSync('models/orderItem.json', {
|
|
properties: {
|
|
quantity: {type: 'number'},
|
|
},
|
|
});
|
|
var appJS = appdir.writeFileSync('models/orderItem.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.eql([{
|
|
name: 'OrderItem',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'OrderItem',
|
|
properties: {
|
|
quantity: {type: 'number'},
|
|
},
|
|
},
|
|
sourceFile: appJS,
|
|
}]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('overwrites model with same name in model definition', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
'customOrder': {dataSource: 'db'},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('models/order1.json', {
|
|
name: 'customOrder',
|
|
properties: {
|
|
price: {type: 'number'},
|
|
},
|
|
});
|
|
appdir.writeFileSync('models/order1.js', '');
|
|
|
|
appdir.writeConfigFileSync('models/order2.json', {
|
|
name: 'customOrder',
|
|
properties: {
|
|
quantity: {type: 'number'},
|
|
},
|
|
});
|
|
var appJS = appdir.writeFileSync('models/order2.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.models).to.eql([{
|
|
name: 'customOrder',
|
|
config: {
|
|
dataSource: 'db',
|
|
},
|
|
definition: {
|
|
name: 'customOrder',
|
|
properties: {
|
|
quantity: {type: 'number'},
|
|
},
|
|
},
|
|
sourceFile: appJS,
|
|
}]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('returns a new copy of JSON data', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
instructions.application.modified = true;
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.application).to.not.have.property('modified');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('for mixins', function() {
|
|
describe(' - mixinDirs', function(done) {
|
|
function verifyMixinIsFoundViaMixinDirs(sourceFile, mixinDirs, done) {
|
|
var appJS = appdir.writeFileSync(sourceFile, '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
mixinDirs: mixinDirs,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
}
|
|
|
|
it('supports `mixinDirs` option', function(done) {
|
|
verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
|
|
['./custom-mixins'], done);
|
|
});
|
|
|
|
it('resolves relative path in `mixinDirs` option', function(done) {
|
|
verifyMixinIsFoundViaMixinDirs('custom-mixins/other.js',
|
|
['./custom-mixins'], done);
|
|
});
|
|
|
|
it('resolves module relative path in `mixinDirs` option',
|
|
function(done) {
|
|
verifyMixinIsFoundViaMixinDirs(
|
|
'node_modules/custom-mixins/other.js',
|
|
['custom-mixins'], done);
|
|
});
|
|
});
|
|
|
|
describe(' - mixinSources', function() {
|
|
beforeEach(function() {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
Car: {dataSource: 'db'},
|
|
});
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
mixins: {'TimeStamps': {}},
|
|
});
|
|
});
|
|
|
|
function verifyMixinIsFoundViaMixinSources(sourceFile, mixinSources,
|
|
done) {
|
|
var appJS = appdir.writeFileSync(sourceFile, '');
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
mixinSources: mixinSources,
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
}
|
|
|
|
it('supports `mixinSources` option', function(done) {
|
|
verifyMixinIsFoundViaMixinSources('mixins/time-stamps.js',
|
|
['./mixins'], done);
|
|
});
|
|
|
|
it('resolves relative path in `mixinSources` option', function(done) {
|
|
verifyMixinIsFoundViaMixinSources('custom-mixins/time-stamps.js',
|
|
['./custom-mixins'], done);
|
|
});
|
|
|
|
it('resolves module relative path in `mixinSources` option',
|
|
function(done) {
|
|
verifyMixinIsFoundViaMixinSources(
|
|
'node_modules/custom-mixins/time-stamps.js',
|
|
['custom-mixins'], done);
|
|
});
|
|
|
|
it('supports `mixins` option in `model-config.json`', function(done) {
|
|
appdir.createConfigFilesSync({}, {}, {
|
|
_meta: {
|
|
mixins: ['./custom-mixins'],
|
|
},
|
|
Car: {
|
|
dataSource: 'db',
|
|
},
|
|
});
|
|
|
|
var appJS = appdir.writeFileSync('custom-mixins/time-stamps.js', '');
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('sets by default `mixinSources` to `mixins` directory',
|
|
function(done) {
|
|
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads only mixins used by models', function(done) {
|
|
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
|
|
appdir.writeFileSync('mixins/foo.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins).to.have.length(1);
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads mixins from model using mixin name in JSON file',
|
|
function(done) {
|
|
var appJS = appdir.writeFileSync('mixins/time-stamps.js', '');
|
|
appdir.writeConfigFileSync('mixins/time-stamps.json', {
|
|
name: 'Timestamping',
|
|
});
|
|
|
|
appdir.writeConfigFileSync('models/car.json', {
|
|
name: 'Car',
|
|
mixins: {'Timestamping': {}},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins).to.have.length(1);
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads mixin only once for dirs common to mixinDirs & mixinSources',
|
|
function(done) {
|
|
var appJS = appdir.writeFileSync(
|
|
'custom-mixins/time-stamps.js', '');
|
|
|
|
var options = {
|
|
appRootDir: appdir.PATH,
|
|
mixinDirs: ['./custom-mixins'],
|
|
mixinSources: ['./custom-mixins'],
|
|
};
|
|
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins).to.have.length(1);
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads mixin from mixinSources, when it is also found in mixinDirs',
|
|
function(done) {
|
|
appdir.writeFileSync('mixinDir/time-stamps.js', '');
|
|
var appJS = appdir.writeFileSync('mixinSource/time-stamps.js', '');
|
|
|
|
var options = {
|
|
appRootDir: appdir.PATH,
|
|
mixinDirs: ['./mixinDir'],
|
|
mixinSources: ['./mixinSource'],
|
|
};
|
|
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins).to.have.length(1);
|
|
expect(instructions.mixins[0].sourceFile).to.eql(appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads mixin from the most recent mixin definition',
|
|
function(done) {
|
|
appdir.writeFileSync('mixins1/time-stamps.js', '');
|
|
var mixins2 = appdir.writeFileSync('mixins2/time-stamps.js', '');
|
|
|
|
var options = {
|
|
appRootDir: appdir.PATH,
|
|
mixinSources: ['./mixins1', './mixins2'],
|
|
};
|
|
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.mixins).to.have.length(1);
|
|
expect(instructions.mixins[0].sourceFile).to.eql(mixins2);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('name normalization', function() {
|
|
var options;
|
|
beforeEach(function() {
|
|
options = {appRootDir: appdir.PATH, mixinDirs: ['./custom-mixins']};
|
|
|
|
appdir.writeFileSync('custom-mixins/foo.js', '');
|
|
appdir.writeFileSync('custom-mixins/time-stamps.js', '');
|
|
appdir.writeFileSync('custom-mixins/camelCase.js', '');
|
|
appdir.writeFileSync('custom-mixins/PascalCase.js', '');
|
|
appdir.writeFileSync('custom-mixins/space name.js', '');
|
|
});
|
|
|
|
it('supports classify', function(done) {
|
|
options.normalization = 'classify';
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'CamelCase', 'Foo', 'PascalCase', 'SpaceName', 'TimeStamps',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports dasherize', function(done) {
|
|
options.normalization = 'dasherize';
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'camel-case', 'foo', 'pascal-case', 'space-name', 'time-stamps',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports custom function', function(done) {
|
|
var normalize = function(name) {
|
|
return name.toUpperCase();
|
|
};
|
|
options.normalization = normalize;
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'CAMELCASE', 'FOO', 'PASCALCASE', 'SPACE NAME', 'TIME-STAMPS',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports none', function(done) {
|
|
options.normalization = 'none';
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'camelCase', 'foo', 'PascalCase', 'space name', 'time-stamps',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports false', function(done) {
|
|
options.normalization = false;
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'camelCase', 'foo', 'PascalCase', 'space name', 'time-stamps',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('defaults to classify', function(done) {
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var mixins = instructions.mixins;
|
|
var mixinNames = mixins.map(getNameProperty);
|
|
|
|
expect(mixinNames).to.eql([
|
|
'CamelCase', 'Foo', 'PascalCase', 'SpaceName', 'TimeStamps',
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('throws error for invalid normalization format', function(done) {
|
|
options.normalization = 'invalidFormat';
|
|
|
|
expectCompileToThrow(/Invalid normalization format - "invalidFormat"/,
|
|
options, done);
|
|
});
|
|
});
|
|
|
|
it('overrides default mixin name, by `name` in JSON', function(done) {
|
|
appdir.writeFileSync('mixins/foo.js', '');
|
|
appdir.writeConfigFileSync('mixins/foo.json', {name: 'fooBar'});
|
|
|
|
var options = {
|
|
appRootDir: appdir.PATH,
|
|
mixinDirs: ['./mixins'],
|
|
};
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.mixins[0].name).to.eql('fooBar');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('extends definition from JSON with same file name', function(done) {
|
|
var appJS = appdir.writeFileSync('custom-mixins/foo-bar.js', '');
|
|
|
|
appdir.writeConfigFileSync('custom-mixins/foo-bar.json', {
|
|
description: 'JSON file name same as JS file name',
|
|
});
|
|
appdir.writeConfigFileSync('custom-mixins/FooBar.json', {
|
|
description: 'JSON file name same as normalized name of mixin',
|
|
});
|
|
|
|
var options = {
|
|
appRootDir: appdir.PATH,
|
|
mixinDirs: ['./custom-mixins'],
|
|
normalization: 'classify',
|
|
};
|
|
boot.compile(options, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.mixins).to.eql([
|
|
{
|
|
name: 'FooBar',
|
|
description: 'JSON file name same as JS file name',
|
|
sourceFile: appJS,
|
|
},
|
|
]);
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('for middleware', function() {
|
|
function testMiddlewareRegistration(middlewareId, sourceFile, done) {
|
|
var json = {
|
|
initial: {},
|
|
custom: {},
|
|
};
|
|
|
|
json.custom[middlewareId] = {
|
|
params: 'some-config-data',
|
|
};
|
|
|
|
appdir.writeConfigFileSync('middleware.json', json);
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware).to.eql({
|
|
phases: ['initial', 'custom'],
|
|
middleware: [
|
|
{
|
|
sourceFile: sourceFile,
|
|
config: {
|
|
phase: 'custom',
|
|
params: 'some-config-data',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
done();
|
|
});
|
|
}
|
|
|
|
var sourceFileForUrlNotFound;
|
|
beforeEach(function() {
|
|
fs.copySync(SIMPLE_APP, appdir.PATH);
|
|
sourceFileForUrlNotFound = require.resolve(
|
|
'loopback/server/middleware/url-not-found');
|
|
});
|
|
|
|
it('emits middleware instructions', function(done) {
|
|
testMiddlewareRegistration('loopback/server/middleware/url-not-found',
|
|
sourceFileForUrlNotFound, done);
|
|
});
|
|
|
|
it('emits middleware instructions for fragment', function(done) {
|
|
testMiddlewareRegistration('loopback#url-not-found',
|
|
sourceFileForUrlNotFound, done);
|
|
});
|
|
|
|
it('supports `middlewareRootDir` option', function(done) {
|
|
var middlewareJson = {
|
|
initial: {},
|
|
custom: {
|
|
'loopback/server/middleware/url-not-found': {
|
|
params: 'some-config-data',
|
|
},
|
|
},
|
|
};
|
|
var customDir = path.resolve(appdir.PATH, 'custom');
|
|
fs.mkdirsSync(customDir);
|
|
fs.writeJsonSync(path.resolve(customDir, 'middleware.json'),
|
|
middlewareJson);
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
middlewareRootDir: customDir,
|
|
}, function(err, context) {
|
|
var instructions = context.instructions;
|
|
expect(instructions.middleware).to.eql({
|
|
phases: ['initial', 'custom'],
|
|
middleware: [
|
|
{
|
|
sourceFile: sourceFileForUrlNotFound,
|
|
config: {
|
|
phase: 'custom',
|
|
params: 'some-config-data',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('fails when a module middleware cannot be resolved', function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
final: {
|
|
'loopback/path-does-not-exist': {},
|
|
},
|
|
});
|
|
|
|
expectCompileToThrow(/path-does-not-exist/, done);
|
|
});
|
|
|
|
it('does not fail when an optional middleware cannot be resolved',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
final: {
|
|
'loopback/path-does-not-exist': {
|
|
optional: 'this middleware is optional',
|
|
},
|
|
},
|
|
});
|
|
|
|
expectCompileToNotThrow(done);
|
|
});
|
|
|
|
it('fails when a module middleware fragment cannot be resolved',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
final: {
|
|
'loopback#path-does-not-exist': {},
|
|
},
|
|
});
|
|
|
|
expectCompileToThrow(/path-does-not-exist/, done);
|
|
});
|
|
|
|
it('does not fail when an optional middleware fragment cannot be resolved',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
final: {
|
|
'loopback#path-does-not-exist': {
|
|
optional: 'this middleware is optional',
|
|
},
|
|
},
|
|
});
|
|
|
|
expectCompileToNotThrow(done);
|
|
});
|
|
|
|
it('resolves paths relatively to appRootDir', function(done) {
|
|
appdir.writeFileSync('my-middleware.js', '');
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
// resolves to ./my-middleware.js
|
|
'./my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware).to.eql({
|
|
phases: ['routes'],
|
|
middleware: [{
|
|
sourceFile: path.resolve(appdir.PATH, 'my-middleware.js'),
|
|
config: {phase: 'routes'},
|
|
}],
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('merges config.params', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions).to.eql({
|
|
key: 'custom value',
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('merges config.enabled', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
enabled: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0].config)
|
|
.to.have.property('enabled', false);
|
|
done();
|
|
});
|
|
});
|
|
|
|
function verifyMiddlewareConfig(done) {
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware)
|
|
.to.eql([
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'middleware'),
|
|
config: {
|
|
phase: 'routes',
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'middleware'),
|
|
config: {
|
|
phase: 'routes',
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
done();
|
|
});
|
|
}
|
|
|
|
it('merges config.params array to array', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
verifyMiddlewareConfig(done);
|
|
});
|
|
|
|
it('merges config.params array to object', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
verifyMiddlewareConfig(done);
|
|
});
|
|
|
|
it('merges config.params object to array', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': {
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
verifyMiddlewareConfig(done);
|
|
});
|
|
|
|
it('merges config.params array to empty object', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': {},
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware)
|
|
.to.eql([
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'middleware'),
|
|
config: {
|
|
phase: 'routes',
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
done();
|
|
});
|
|
|
|
it('merges config.params array to array by name', function(done) {
|
|
appdir.writeConfigFileSync('./middleware.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
name: 'a',
|
|
params: {
|
|
key: 'initial value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
appdir.writeConfigFileSync('./middleware.local.json', {
|
|
routes: {
|
|
'./middleware': [{
|
|
name: 'a',
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
}, {
|
|
params: {
|
|
key: '2nd value',
|
|
},
|
|
}],
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware)
|
|
.to.eql([
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'middleware'),
|
|
config: {
|
|
name: 'a',
|
|
phase: 'routes',
|
|
params: {
|
|
key: 'custom value',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'middleware'),
|
|
config: {
|
|
phase: 'routes',
|
|
params: {
|
|
key: '2nd value',
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('flattens sub-phases', function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'initial:after': {},
|
|
'custom:before': {
|
|
'loopback/server/middleware/url-not-found': {
|
|
params: 'some-config-data',
|
|
},
|
|
},
|
|
'custom:after': {},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.phases, 'phases')
|
|
.to.eql(['initial', 'custom']);
|
|
expect(instructions.middleware.middleware, 'middleware')
|
|
.to.eql([{
|
|
sourceFile: require.resolve(
|
|
'loopback/server/middleware/url-not-found'),
|
|
config: {
|
|
phase: 'custom:before',
|
|
params: 'some-config-data',
|
|
},
|
|
}]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports multiple instances of the same middleware', function(done) {
|
|
appdir.writeFileSync('my-middleware.js', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'final': {
|
|
'./my-middleware': [
|
|
{
|
|
params: 'first',
|
|
},
|
|
{
|
|
params: 'second',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware)
|
|
.to.eql([
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'my-middleware.js'),
|
|
config: {
|
|
phase: 'final',
|
|
params: 'first',
|
|
},
|
|
},
|
|
{
|
|
sourceFile: path.resolve(appdir.PATH, 'my-middleware.js'),
|
|
config: {
|
|
phase: 'final',
|
|
params: 'second',
|
|
},
|
|
},
|
|
]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports shorthand notation for middleware paths', function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'final': {
|
|
'loopback#url-not-found': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0].sourceFile).to.equal(
|
|
require.resolve('loopback/server/middleware/url-not-found'));
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports shorthand notation for relative paths', function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'./middleware/index#myMiddleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0].sourceFile)
|
|
.to.equal(path.resolve(appdir.PATH,
|
|
'./middleware/index.js'));
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'fragment',
|
|
'myMiddleware');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports shorthand notation when the fragment name matches a property',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'final': {
|
|
'loopback#errorHandler': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile',
|
|
pathWithoutIndex(require.resolve('loopback')));
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'fragment',
|
|
'errorHandler');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('resolves modules relative to appRootDir', function(done) {
|
|
var HANDLER_FILE = 'node_modules/handler/index.js';
|
|
appdir.writeFileSync(
|
|
HANDLER_FILE,
|
|
'module.exports = function(req, res, next) { next(); }');
|
|
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'initial': {
|
|
'handler': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile',
|
|
pathWithoutIndex(appdir.resolve(HANDLER_FILE)));
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers appRootDir over node_modules for middleware', function(done) {
|
|
var appJS = appdir.writeFileSync('./my-middleware.js', '');
|
|
appdir.writeFileSync('node_modules/my-middleware.js', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'./my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware).to.have.length(1);
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', appJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not treat module relative path as `appRootDir` relative',
|
|
function(done) {
|
|
appdir.writeFileSync('./my-middleware.js', '');
|
|
var moduleJS = appdir.writeFileSync(
|
|
'node_modules/my-middleware.js', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware).to.have.length(1);
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', moduleJS);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads middleware from coffeescript in appRootdir', function(done) {
|
|
var coffee = appdir.writeFileSync('my-middleware.coffee', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'./my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', coffee);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads coffeescript from middleware under node_modules',
|
|
function(done) {
|
|
var file = appdir.writeFileSync(
|
|
'node_modules/my-middleware/index.coffee',
|
|
'');
|
|
appdir.writeFileSync('node_modules/my-middleware/index.json', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware).to.have.length(1);
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', pathWithoutIndex(file));
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers coffeescript over json for relative middleware path',
|
|
function(done) {
|
|
var coffee = appdir.writeFileSync('my-middleware.coffee', '');
|
|
appdir.writeFileSync('my-middleware.json', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'./my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware).to.have.length(1);
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', coffee);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers coffeescript over json for module relative middleware path',
|
|
function(done) {
|
|
var coffee = appdir.writeFileSync('node_modules/my-middleware.coffee',
|
|
'');
|
|
appdir.writeFileSync('node_modules/my-middleware.json', '');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware).to.have.length(1);
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', coffee);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('ignores sourcmap files when loading middleware',
|
|
function(done) {
|
|
var middleware = appdir.writeFileSync('my-middleware.js',
|
|
'// I am the middleware');
|
|
var sourcemap = appdir.writeFileSync('my-middleware.js.map',
|
|
'// I am a sourcemap');
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
'routes': {
|
|
'./my-middleware': {},
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.middleware.middleware[0]).have.property(
|
|
'sourceFile', middleware);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('config with relative paths in params', function() {
|
|
var RELATIVE_PATH_PARAMS = [
|
|
'$!./here',
|
|
'$!../there',
|
|
];
|
|
|
|
var absolutePathParams;
|
|
beforeEach(function resolveRelativePathParams() {
|
|
absolutePathParams = RELATIVE_PATH_PARAMS.map(function(p) {
|
|
return appdir.resolve(p.slice(2));
|
|
});
|
|
});
|
|
|
|
it('converts paths in top-level array items', function(done) {
|
|
givenMiddlewareEntrySync({params: RELATIVE_PATH_PARAMS});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions)
|
|
.to.eql(absolutePathParams);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('converts paths in top-level object properties', function(done) {
|
|
givenMiddlewareEntrySync({
|
|
params: {
|
|
path: RELATIVE_PATH_PARAMS[0],
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions)
|
|
.to.eql({path: absolutePathParams[0]});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('converts path value when params is a string', function(done) {
|
|
givenMiddlewareEntrySync({params: RELATIVE_PATH_PARAMS[0]});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions)
|
|
.to.eql(absolutePathParams[0]);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('converts paths in nested properties', function(done) {
|
|
givenMiddlewareEntrySync({
|
|
params: {
|
|
nestedObject: {
|
|
path: RELATIVE_PATH_PARAMS[0],
|
|
},
|
|
nestedArray: RELATIVE_PATH_PARAMS,
|
|
},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions)
|
|
.to.eql({
|
|
nestedObject: {
|
|
path: absolutePathParams[0],
|
|
},
|
|
nestedArray: absolutePathParams,
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('does not convert values not starting with `./` or `../`',
|
|
function(done) {
|
|
var PARAMS = ['$!.somerc', '$!/root', '$!hello!'];
|
|
givenMiddlewareEntrySync({params: PARAMS});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expectFirstMiddlewareParams(instructions).to.eql(PARAMS);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('for components', function() {
|
|
it('loads component configs from multiple files', function(done) {
|
|
appdir.createConfigFilesSync();
|
|
appdir.writeConfigFileSync('component-config.json', {
|
|
debug: {option: 'value'},
|
|
});
|
|
appdir.writeConfigFileSync('component-config.local.json', {
|
|
debug: {local: 'applied'},
|
|
});
|
|
|
|
var env = process.env.NODE_ENV || 'development';
|
|
appdir.writeConfigFileSync('component-config.' + env + '.json', {
|
|
debug: {env: 'applied'},
|
|
});
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
var component = instructions.components[0];
|
|
expect(component).to.eql({
|
|
sourceFile: require.resolve('debug'),
|
|
config: {
|
|
option: 'value',
|
|
local: 'applied',
|
|
env: 'applied',
|
|
},
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('supports `componentRootDir` option', function(done) {
|
|
var componentJson = {
|
|
debug: {
|
|
option: 'value',
|
|
},
|
|
};
|
|
var customDir = path.resolve(appdir.PATH, 'custom');
|
|
fs.mkdirsSync(customDir);
|
|
fs.writeJsonSync(
|
|
path.resolve(customDir, 'component-config.json'), componentJson);
|
|
|
|
boot.compile({
|
|
appRootDir: appdir.PATH,
|
|
componentRootDir: path.resolve(appdir.PATH, 'custom'),
|
|
}, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
var component = instructions.components[0];
|
|
expect(component).to.eql({
|
|
sourceFile: require.resolve('debug'),
|
|
config: {
|
|
option: 'value',
|
|
},
|
|
});
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads component relative to appRootDir', function(done) {
|
|
appdir.writeConfigFileSync('./component-config.json', {
|
|
'./index': {},
|
|
});
|
|
var appJS = appdir.writeConfigFileSync('index.js', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.components[0]).have.property(
|
|
'sourceFile', appJS
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('loads component relative to node modules', function(done) {
|
|
appdir.writeConfigFileSync('component-config.json', {
|
|
'mycomponent': {},
|
|
});
|
|
var js = appdir.writeConfigFileSync('node_modules/mycomponent/index.js',
|
|
'');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
expect(instructions.components[0]).have.property(
|
|
'sourceFile', js
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('retains backward compatibility for non-relative path in `appRootDir`',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('component-config.json', {
|
|
'my-component/component.js': {},
|
|
});
|
|
appdir.writeConfigFileSync('./my-component/component.js', '');
|
|
|
|
expectCompileToThrow(
|
|
'Cannot resolve path \"my-component/component.js\"',
|
|
done);
|
|
});
|
|
|
|
it('prefers coffeescript over json for relative path component',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('component-config.json', {
|
|
'./component': {},
|
|
});
|
|
|
|
var coffee = appdir.writeFileSync('component.coffee', '');
|
|
appdir.writeFileSync('component.json', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.components).to.have.length(1);
|
|
expect(instructions.components[0]).have.property(
|
|
'sourceFile', coffee);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('prefers coffeescript over json for module relative component path',
|
|
function(done) {
|
|
appdir.writeConfigFileSync('component-config.json', {
|
|
'component': {},
|
|
});
|
|
|
|
var coffee = appdir.writeFileSync('node_modules/component.coffee', '');
|
|
appdir.writeFileSync('node_modules/component.json', '');
|
|
|
|
boot.compile(appdir.PATH, function(err, context) {
|
|
if (err) return done(err);
|
|
var instructions = context.instructions;
|
|
|
|
expect(instructions.components).to.have.length(1);
|
|
expect(instructions.components[0]).have.property(
|
|
'sourceFile', coffee);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
function getNameProperty(obj) {
|
|
return obj.name;
|
|
}
|
|
|
|
function givenMiddlewareEntrySync(config) {
|
|
appdir.writeConfigFileSync('middleware.json', {
|
|
initial: {
|
|
// resolves to ./middleware.json
|
|
'./middleware': config,
|
|
},
|
|
});
|
|
}
|
|
|
|
function expectFirstMiddlewareParams(instructions) {
|
|
return expect(instructions.middleware.middleware[0].config.params);
|
|
}
|
|
|
|
function pathWithoutExtension(value) {
|
|
return path.join(
|
|
path.dirname(value),
|
|
path.basename(value, path.extname(value)));
|
|
}
|
|
|
|
function pathWithoutIndex(filePath) {
|
|
return filePath.replace(/[\\\/]index\.[^.]+$/, '');
|
|
}
|
|
|