2018-01-03 04:05:53 +00:00
|
|
|
// Copyright IBM Corp. 2013,2018. All Rights Reserved.
|
2016-05-03 22:50:21 +00:00
|
|
|
// Node module: loopback
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
var async = require('async');
|
2013-12-18 03:15:48 +00:00
|
|
|
var path = require('path');
|
2014-11-11 16:51:50 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
var http = require('http');
|
2014-12-10 08:36:45 +00:00
|
|
|
var express = require('express');
|
2014-05-03 04:19:14 +00:00
|
|
|
var loopback = require('../');
|
2014-06-05 07:45:09 +00:00
|
|
|
var PersistedModel = loopback.PersistedModel;
|
2013-12-18 03:15:48 +00:00
|
|
|
|
2014-06-03 08:39:54 +00:00
|
|
|
var describe = require('./util/describe');
|
2016-11-22 14:30:04 +00:00
|
|
|
var expect = require('./helpers/expect');
|
2014-06-03 08:39:54 +00:00
|
|
|
var it = require('./util/it');
|
2016-11-15 21:46:23 +00:00
|
|
|
var request = require('supertest');
|
2018-04-16 12:07:30 +00:00
|
|
|
const sinon = require('sinon');
|
2014-06-03 08:39:54 +00:00
|
|
|
|
2013-06-07 19:57:51 +00:00
|
|
|
describe('app', function() {
|
2016-10-21 15:17:02 +00:00
|
|
|
var app;
|
|
|
|
beforeEach(function() {
|
2018-04-16 12:07:30 +00:00
|
|
|
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
2016-10-21 15:17:02 +00:00
|
|
|
});
|
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
describe.onServer('.middleware(phase, handler)', function() {
|
2016-10-21 15:17:02 +00:00
|
|
|
var steps;
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
|
|
|
|
beforeEach(function setup() {
|
|
|
|
steps = [];
|
|
|
|
});
|
|
|
|
|
|
|
|
it('runs middleware in phases', function(done) {
|
|
|
|
var PHASES = [
|
|
|
|
'initial', 'session', 'auth', 'parse',
|
2016-04-01 09:14:26 +00:00
|
|
|
'routes', 'files', 'final',
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
PHASES.forEach(function(name) {
|
|
|
|
app.middleware(name, namedHandler(name));
|
|
|
|
});
|
|
|
|
app.use(namedHandler('main'));
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
expect(steps).to.eql([
|
|
|
|
'initial', 'session', 'auth', 'parse',
|
2016-04-01 09:14:26 +00:00
|
|
|
'main', 'routes', 'files', 'final',
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
]);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
it('preserves order of handlers in the same phase', function(done) {
|
|
|
|
app.middleware('initial', namedHandler('first'));
|
|
|
|
app.middleware('initial', namedHandler('second'));
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(steps).to.eql(['first', 'second']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('supports `before:` and `after:` prefixes', function(done) {
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
app.middleware('routes:before', namedHandler('routes:before'));
|
|
|
|
app.middleware('routes:after', namedHandler('routes:after'));
|
|
|
|
app.use(namedHandler('main'));
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
expect(steps).to.eql(['routes:before', 'main', 'routes:after']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
it('allows extra handlers on express stack during app.use', function(done) {
|
|
|
|
function handlerThatAddsHandler(name) {
|
|
|
|
app.use(namedHandler('extra-handler'));
|
|
|
|
return namedHandler(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
var myHandler;
|
|
|
|
app.middleware('routes:before',
|
|
|
|
myHandler = handlerThatAddsHandler('my-handler'));
|
|
|
|
var found = app._findLayerByHandler(myHandler);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(found).to.be.an('object');
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(myHandler).to.equal(found.handle);
|
|
|
|
expect(found).have.property('phase', 'routes:before');
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(steps).to.eql(['my-handler', 'extra-handler']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('allows handlers to be wrapped as __NR_handler on express stack',
|
|
|
|
function(done) {
|
|
|
|
var myHandler = namedHandler('my-handler');
|
|
|
|
var wrappedHandler = function(req, res, next) {
|
|
|
|
myHandler(req, res, next);
|
|
|
|
};
|
|
|
|
wrappedHandler['__NR_handler'] = myHandler;
|
|
|
|
app.middleware('routes:before', wrappedHandler);
|
|
|
|
var found = app._findLayerByHandler(myHandler);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(found).to.be.an('object');
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(found).have.property('phase', 'routes:before');
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(steps).to.eql(['my-handler']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('allows handlers to be wrapped as a property on express stack',
|
|
|
|
function(done) {
|
|
|
|
var myHandler = namedHandler('my-handler');
|
|
|
|
var wrappedHandler = function(req, res, next) {
|
|
|
|
myHandler(req, res, next);
|
|
|
|
};
|
|
|
|
wrappedHandler['__handler'] = myHandler;
|
|
|
|
app.middleware('routes:before', wrappedHandler);
|
|
|
|
var found = app._findLayerByHandler(myHandler);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(found).to.be.an('object');
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(found).have.property('phase', 'routes:before');
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
expect(steps).to.eql(['my-handler']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2015-02-25 17:47:18 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
it('injects error from previous phases into the router', function(done) {
|
|
|
|
var expectedError = new Error('expected error');
|
|
|
|
|
|
|
|
app.middleware('initial', function(req, res, next) {
|
|
|
|
steps.push('initial');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
next(expectedError);
|
|
|
|
});
|
|
|
|
|
|
|
|
// legacy solution for error handling
|
|
|
|
app.use(function errorHandler(err, req, res, next) {
|
|
|
|
expect(err).to.equal(expectedError);
|
|
|
|
steps.push('error');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
expect(steps).to.eql(['initial', 'error']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('passes unhandled error to callback', function(done) {
|
|
|
|
var expectedError = new Error('expected error');
|
|
|
|
|
|
|
|
app.middleware('initial', function(req, res, next) {
|
|
|
|
next(expectedError);
|
|
|
|
});
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
expect(err).to.equal(expectedError);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-11-12 09:09:20 +00:00
|
|
|
it('passes errors to error handlers in the same phase', function(done) {
|
|
|
|
var expectedError = new Error('this should be handled by middleware');
|
|
|
|
var handledError;
|
|
|
|
|
|
|
|
app.middleware('initial', function(req, res, next) {
|
|
|
|
// continue in the next tick, this verifies that the next
|
|
|
|
// handler waits until the previous one is done
|
|
|
|
process.nextTick(function() {
|
|
|
|
next(expectedError);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
app.middleware('initial', function(err, req, res, next) {
|
|
|
|
handledError = err;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-12 09:09:20 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-12 09:09:20 +00:00
|
|
|
expect(handledError).to.equal(expectedError);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-12 09:09:20 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
it('scopes middleware to a string path', function(done) {
|
|
|
|
app.middleware('initial', '/scope', pathSavingHandler());
|
|
|
|
|
|
|
|
async.eachSeries(
|
|
|
|
['/', '/scope', '/scope/item', '/other'],
|
|
|
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
|
|
|
function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
expect(steps).to.eql(['/scope', '/scope/item']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('scopes middleware to a regex path', function(done) {
|
|
|
|
app.middleware('initial', /^\/(a|b)/, pathSavingHandler());
|
|
|
|
|
|
|
|
async.eachSeries(
|
|
|
|
['/', '/a', '/b', '/c'],
|
|
|
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
|
|
|
function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
expect(steps).to.eql(['/a', '/b']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('scopes middleware to a list of scopes', function(done) {
|
|
|
|
app.middleware('initial', ['/scope', /^\/(a|b)/], pathSavingHandler());
|
|
|
|
|
|
|
|
async.eachSeries(
|
|
|
|
['/', '/a', '/b', '/c', '/scope', '/other'],
|
|
|
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
|
|
|
function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
expect(steps).to.eql(['/a', '/b', '/scope']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
it('sets req.url to a sub-path', function(done) {
|
|
|
|
app.middleware('initial', ['/scope'], function(req, res, next) {
|
|
|
|
steps.push(req.url);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, '/scope/id', function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(steps).to.eql(['/id']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('exposes express helpers on req and res objects', function(done) {
|
2016-04-01 09:14:26 +00:00
|
|
|
var req, res;
|
2014-12-10 08:36:45 +00:00
|
|
|
|
|
|
|
app.middleware('initial', function(rq, rs, next) {
|
|
|
|
req = rq;
|
|
|
|
res = rs;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(getObjectAndPrototypeKeys(req), 'request').to.include.members([
|
|
|
|
'accepts',
|
|
|
|
'get',
|
|
|
|
'param',
|
|
|
|
'params',
|
|
|
|
'query',
|
2016-04-01 09:14:26 +00:00
|
|
|
'res',
|
2014-12-10 08:36:45 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
expect(getObjectAndPrototypeKeys(res), 'response').to.include.members([
|
|
|
|
'cookie',
|
|
|
|
'download',
|
|
|
|
'json',
|
|
|
|
'jsonp',
|
|
|
|
'redirect',
|
|
|
|
'req',
|
|
|
|
'send',
|
|
|
|
'sendFile',
|
2016-04-01 09:14:26 +00:00
|
|
|
'set',
|
2014-12-10 08:36:45 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('sets req.baseUrl and req.originalUrl', function(done) {
|
|
|
|
var reqProps;
|
|
|
|
app.middleware('initial', function(req, res, next) {
|
2016-11-15 21:46:23 +00:00
|
|
|
reqProps = {baseUrl: req.baseUrl, originalUrl: req.originalUrl};
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, '/test/url', function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
expect(reqProps).to.eql({baseUrl: '', originalUrl: '/test/url'});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('preserves correct order of routes vs. middleware', function(done) {
|
|
|
|
// This test verifies that `app.route` triggers sort of layers
|
|
|
|
app.middleware('files', namedHandler('files'));
|
|
|
|
app.get('/test', namedHandler('route'));
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, '/test', function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(steps).to.eql(['route', 'files']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2014-12-15 07:14:26 +00:00
|
|
|
|
|
|
|
it('preserves order of middleware in the same phase', function(done) {
|
|
|
|
// while we are discouraging developers from depending on
|
|
|
|
// the registration order of middleware in the same phase,
|
|
|
|
// we must preserve the order for compatibility with `app.use`
|
|
|
|
// and `app.route`.
|
|
|
|
|
|
|
|
// we need at least 9 elements to expose non-stability
|
|
|
|
// of the built-in sort function
|
|
|
|
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
|
|
numbers.forEach(function(n) {
|
|
|
|
app.middleware('routes', namedHandler(n));
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-15 07:14:26 +00:00
|
|
|
expect(steps).to.eql(numbers);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-15 07:14:26 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2014-12-10 08:36:45 +00:00
|
|
|
|
|
|
|
it('correctly mounts express apps', function(done) {
|
2016-04-01 09:14:26 +00:00
|
|
|
var data, mountWasEmitted;
|
2014-12-10 08:36:45 +00:00
|
|
|
var subapp = express();
|
|
|
|
subapp.use(function(req, res, next) {
|
|
|
|
data = {
|
|
|
|
mountpath: req.app.mountpath,
|
2016-04-01 09:14:26 +00:00
|
|
|
parent: req.app.parent,
|
2014-12-10 08:36:45 +00:00
|
|
|
};
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
subapp.on('mount', function() { mountWasEmitted = true; });
|
|
|
|
|
|
|
|
app.middleware('routes', '/mountpath', subapp);
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, '/mountpath/test', function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(mountWasEmitted, 'mountWasEmitted').to.be.true();
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(data).to.eql({
|
|
|
|
mountpath: '/mountpath',
|
2016-04-01 09:14:26 +00:00
|
|
|
parent: app,
|
2014-12-10 08:36:45 +00:00
|
|
|
});
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('restores req & res on return from mounted express app', function(done) {
|
|
|
|
var expected = {};
|
|
|
|
var actual = {};
|
|
|
|
|
|
|
|
var subapp = express();
|
|
|
|
subapp.use(function verifyTestAssumptions(req, res, next) {
|
|
|
|
expect(req.__proto__).to.not.equal(expected.req);
|
|
|
|
expect(res.__proto__).to.not.equal(expected.res);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
app.middleware('initial', function saveOriginalValues(req, res, next) {
|
|
|
|
expected.req = req.__proto__;
|
|
|
|
expected.res = res.__proto__;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
app.middleware('routes', subapp);
|
|
|
|
app.middleware('final', function saveActualValues(req, res, next) {
|
|
|
|
actual.req = req.__proto__;
|
|
|
|
actual.res = res.__proto__;
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
expect(actual.req, 'req').to.equal(expected.req);
|
|
|
|
expect(actual.res, 'res').to.equal(expected.res);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-12-10 08:36:45 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
function namedHandler(name) {
|
|
|
|
return function(req, res, next) {
|
|
|
|
steps.push(name);
|
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
|
|
|
|
function pathSavingHandler() {
|
|
|
|
return function(req, res, next) {
|
2014-12-10 08:36:45 +00:00
|
|
|
steps.push(req.originalUrl);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
2014-12-10 08:36:45 +00:00
|
|
|
|
|
|
|
function getObjectAndPrototypeKeys(obj) {
|
|
|
|
var result = [];
|
|
|
|
for (var k in obj) {
|
|
|
|
result.push(k);
|
|
|
|
}
|
|
|
|
result.sort();
|
|
|
|
return result;
|
|
|
|
}
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe.onServer('.middlewareFromConfig', function() {
|
|
|
|
it('provides API for loading middleware from JSON config', function(done) {
|
|
|
|
var steps = [];
|
2016-11-15 21:46:23 +00:00
|
|
|
var expectedConfig = {key: 'value'};
|
2014-11-11 16:51:50 +00:00
|
|
|
|
|
|
|
var handlerFactory = function() {
|
|
|
|
var args = Array.prototype.slice.apply(arguments);
|
|
|
|
return function(req, res, next) {
|
|
|
|
steps.push(args);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
};
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
// Config as an object (single arg)
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: true,
|
|
|
|
phase: 'session',
|
2016-04-01 09:14:26 +00:00
|
|
|
params: expectedConfig,
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
});
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
// Config as a value (single arg)
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: true,
|
|
|
|
phase: 'session:before',
|
2016-04-01 09:14:26 +00:00
|
|
|
params: 'before',
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Config as a list of args
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: true,
|
|
|
|
phase: 'session:after',
|
2016-04-01 09:14:26 +00:00
|
|
|
params: ['after', 2],
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Disabled by configuration
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: false,
|
|
|
|
phase: 'initial',
|
2016-04-01 09:14:26 +00:00
|
|
|
params: null,
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
|
2015-08-05 17:30:57 +00:00
|
|
|
// This should be triggered with matching verbs
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: true,
|
|
|
|
phase: 'routes:before',
|
|
|
|
methods: ['get', 'head'],
|
2016-11-15 21:46:23 +00:00
|
|
|
params: {x: 1},
|
2015-08-05 17:30:57 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// This should be skipped as the verb doesn't match
|
|
|
|
app.middlewareFromConfig(handlerFactory, {
|
|
|
|
enabled: true,
|
|
|
|
phase: 'routes:before',
|
|
|
|
methods: ['post'],
|
2016-11-15 21:46:23 +00:00
|
|
|
params: {x: 2},
|
2015-08-05 17:30:57 +00:00
|
|
|
});
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
expect(steps).to.eql([
|
|
|
|
['before'],
|
|
|
|
[expectedConfig],
|
2015-08-05 17:30:57 +00:00
|
|
|
['after', 2],
|
2016-11-15 21:46:23 +00:00
|
|
|
[{x: 1}],
|
2014-11-11 16:51:50 +00:00
|
|
|
]);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
|
2017-12-12 08:33:15 +00:00
|
|
|
it('scopes middleware from config to a list of scopes', function(done) {
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
var steps = [];
|
|
|
|
app.middlewareFromConfig(
|
|
|
|
function factory() {
|
|
|
|
return function(req, res, next) {
|
2014-12-10 08:36:45 +00:00
|
|
|
steps.push(req.originalUrl);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
},
|
|
|
|
{
|
|
|
|
phase: 'initial',
|
2016-04-01 09:14:26 +00:00
|
|
|
paths: ['/scope', /^\/(a|b)/],
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
async.eachSeries(
|
|
|
|
['/', '/a', '/b', '/c', '/scope', '/other'],
|
|
|
|
function(url, next) { executeMiddlewareHandlers(app, url, next); },
|
|
|
|
function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
expect(steps).to.eql(['/a', '/b', '/scope']);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
Middleware phases - initial implementation
Modify the app and router implementation, so that the middleware is
executed in order defined by phases.
Predefined phases:
'initial', 'session', 'auth', 'parse', 'routes', 'files', 'final'
Methods defined via `app.use`, `app.route` and friends are executed
as the first thing in 'routes' phase.
API usage:
app.middleware('initial', compression());
app.middleware('initial:before', serveFavicon());
app.middleware('files:after', loopback.urlNotFound());
app.middleware('final:after', errorHandler());
Middleware flavours:
// regular handler
function handler(req, res, next) {
// do stuff
next();
}
// error handler
function errorHandler(err, req, res, next) {
// handle error and/or call next
next(err);
}
2014-11-05 19:07:58 +00:00
|
|
|
});
|
2013-06-06 00:11:21 +00:00
|
|
|
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
describe.onServer('.defineMiddlewarePhases(nameOrArray)', function() {
|
|
|
|
var app;
|
|
|
|
beforeEach(function() {
|
|
|
|
app = loopback();
|
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('adds the phase just before `routes` by default', function(done) {
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
app.defineMiddlewarePhases('custom');
|
|
|
|
verifyMiddlewarePhases(['custom', 'routes'], done);
|
|
|
|
});
|
|
|
|
|
Simplify `app.defineMiddlewarePhases`
Refactor the implementation to use the new method `phaseList.zipMerge`.
This is commit is changing the behaviour in the case when
the first new phase does not exist in the current list.
Before the change, all new phases were added just before the "routes"
phase.
After this change, new phases are added to the head of the list,
until an existing phase is encountered, at which point the regular
merge algorithm kicks in.
Example:
app.defineMiddlewarePhases(['first', 'routes', 'subapps']);
Before the change: code throws an error - 'routes' already exists.
After the change: phases are merged with the following result:
'first', 'initial', ..., 'routes', 'subapps', ...
2014-11-12 07:59:56 +00:00
|
|
|
it('merges phases adding to the start of the list', function(done) {
|
|
|
|
app.defineMiddlewarePhases(['first', 'routes', 'subapps']);
|
|
|
|
verifyMiddlewarePhases([
|
|
|
|
'first',
|
|
|
|
'initial', // this was the original first phase
|
|
|
|
'routes',
|
2016-04-01 09:14:26 +00:00
|
|
|
'subapps',
|
Simplify `app.defineMiddlewarePhases`
Refactor the implementation to use the new method `phaseList.zipMerge`.
This is commit is changing the behaviour in the case when
the first new phase does not exist in the current list.
Before the change, all new phases were added just before the "routes"
phase.
After this change, new phases are added to the head of the list,
until an existing phase is encountered, at which point the regular
merge algorithm kicks in.
Example:
app.defineMiddlewarePhases(['first', 'routes', 'subapps']);
Before the change: code throws an error - 'routes' already exists.
After the change: phases are merged with the following result:
'first', 'initial', ..., 'routes', 'subapps', ...
2014-11-12 07:59:56 +00:00
|
|
|
], done);
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('merges phases preserving the order', function(done) {
|
|
|
|
app.defineMiddlewarePhases([
|
|
|
|
'initial',
|
|
|
|
'postinit', 'preauth', // add
|
|
|
|
'auth', 'routes',
|
|
|
|
'subapps', // add
|
|
|
|
'final',
|
2016-04-01 09:14:26 +00:00
|
|
|
'last', // add
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
]);
|
|
|
|
verifyMiddlewarePhases([
|
|
|
|
'initial',
|
|
|
|
'postinit', 'preauth', // new
|
|
|
|
'auth', 'routes',
|
|
|
|
'subapps', // new
|
|
|
|
'files', 'final',
|
2016-04-01 09:14:26 +00:00
|
|
|
'last', // new
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
], done);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('throws helpful error on ordering conflict', function() {
|
|
|
|
app.defineMiddlewarePhases(['first', 'second']);
|
|
|
|
expect(function() { app.defineMiddlewarePhases(['second', 'first']); })
|
2014-12-10 08:36:45 +00:00
|
|
|
.to.throw(/Ordering conflict.*first.*second/);
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
function verifyMiddlewarePhases(names, done) {
|
|
|
|
var steps = [];
|
|
|
|
names.forEach(function(it) {
|
|
|
|
app.middleware(it, function(req, res, next) {
|
|
|
|
steps.push(it);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
next();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
expect(steps).to.eql(names);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
Implement `app.defineMiddlewarePhases`
Implement method for registering (new) middleware phases.
- If all names are new, then the phases are added just before
the "routes" phase.
- Otherwise the provided list of names is merged with the existing
phases in such way that the order of phases is preserved.
Example
// built-in phases:
// initial, session, auth, parse, routes, files, final
app.defineMiddlewarePhases('custom');
// new list of phases
// initial, session, auth, parse,
// custom,
// routes, files, final
app.defineMiddlewarePhases([
'initial', 'postinit', 'preauth', 'routes', 'subapps'
]);
// new list of phases
// initial,
// postinit, preauth,
// session, auth, parse, custom,
// routes,
// subapps,
// files, final
2014-11-11 16:13:44 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-06-07 19:57:51 +00:00
|
|
|
describe('app.model(Model)', function() {
|
2016-06-03 20:51:48 +00:00
|
|
|
var app, db, MyTestModel;
|
2014-01-22 10:22:23 +00:00
|
|
|
beforeEach(function() {
|
|
|
|
app = loopback();
|
2016-11-15 21:46:23 +00:00
|
|
|
app.set('remoting', {errorHandler: {debug: true, log: false}});
|
|
|
|
db = loopback.createDataSource({connector: loopback.Memory});
|
2016-06-03 20:51:48 +00:00
|
|
|
MyTestModel = app.registry.createModel('MyTestModel');
|
2014-01-22 10:22:23 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('Expose a `Model` to remote clients', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2013-06-07 19:57:51 +00:00
|
|
|
app.model(Color);
|
2014-05-03 04:19:14 +00:00
|
|
|
Color.attachTo(db);
|
2014-01-22 10:22:23 +00:00
|
|
|
|
|
|
|
expect(app.models()).to.eql([Color]);
|
|
|
|
});
|
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
it('uses singular name as app.remoteObjects() key', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2014-01-22 10:22:23 +00:00
|
|
|
app.model(Color);
|
2014-05-03 04:19:14 +00:00
|
|
|
Color.attachTo(db);
|
2016-11-15 21:46:23 +00:00
|
|
|
expect(app.remoteObjects()).to.eql({color: Color});
|
2014-01-22 10:22:23 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it('uses singular name as shared class name', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2014-01-22 10:22:23 +00:00
|
|
|
app.model(Color);
|
2014-05-03 04:19:14 +00:00
|
|
|
Color.attachTo(db);
|
2016-04-01 09:14:26 +00:00
|
|
|
var classes = app.remotes().classes().map(function(c) { return c.name; });
|
2014-05-19 22:56:26 +00:00
|
|
|
expect(classes).to.contain('color');
|
2014-01-22 10:22:23 +00:00
|
|
|
});
|
|
|
|
|
2014-06-09 23:31:33 +00:00
|
|
|
it('registers existing models to app.models', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = db.createModel('color', {name: String});
|
2014-06-09 23:31:33 +00:00
|
|
|
app.model(Color);
|
2014-06-10 06:53:01 +00:00
|
|
|
expect(Color.app).to.be.equal(app);
|
|
|
|
expect(Color.shared).to.equal(true);
|
|
|
|
expect(app.models.color).to.equal(Color);
|
|
|
|
expect(app.models.Color).to.equal(Color);
|
2014-06-09 23:31:33 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('emits a `modelRemoted` event', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2014-07-25 00:00:27 +00:00
|
|
|
Color.shared = true;
|
|
|
|
var remotedClass;
|
|
|
|
app.on('modelRemoted', function(sharedClass) {
|
|
|
|
remotedClass = sharedClass;
|
|
|
|
});
|
|
|
|
app.model(Color);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(remotedClass).to.exist();
|
2014-07-25 00:00:27 +00:00
|
|
|
expect(remotedClass).to.eql(Color.sharedClass);
|
|
|
|
});
|
2016-04-27 11:15:24 +00:00
|
|
|
|
|
|
|
it('emits a `remoteMethodDisabled` event', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2016-04-27 11:15:24 +00:00
|
|
|
Color.shared = true;
|
|
|
|
var remoteMethodDisabledClass, disabledRemoteMethod;
|
|
|
|
app.on('remoteMethodDisabled', function(sharedClass, methodName) {
|
|
|
|
remoteMethodDisabledClass = sharedClass;
|
|
|
|
disabledRemoteMethod = methodName;
|
|
|
|
});
|
|
|
|
app.model(Color);
|
2016-09-19 16:52:34 +00:00
|
|
|
app.models.Color.disableRemoteMethodByName('findOne');
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(remoteMethodDisabledClass).to.exist();
|
2016-04-27 11:15:24 +00:00
|
|
|
expect(remoteMethodDisabledClass).to.eql(Color.sharedClass);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(disabledRemoteMethod).to.exist();
|
2016-04-27 11:15:24 +00:00
|
|
|
expect(disabledRemoteMethod).to.eql('findOne');
|
|
|
|
});
|
2014-07-25 00:00:27 +00:00
|
|
|
|
2017-03-29 15:25:56 +00:00
|
|
|
it('emits a `remoteMethodAdded` event', function() {
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
|
|
|
var Book = app.registry.createModel(
|
|
|
|
'Book',
|
|
|
|
{name: 'string'},
|
|
|
|
{plural: 'books'}
|
|
|
|
);
|
|
|
|
app.model(Book, {dataSource: 'db'});
|
|
|
|
|
|
|
|
var Page = app.registry.createModel(
|
|
|
|
'Page',
|
|
|
|
{name: 'string'},
|
|
|
|
{plural: 'pages'}
|
|
|
|
);
|
|
|
|
app.model(Page, {dataSource: 'db'});
|
|
|
|
|
|
|
|
Book.hasMany(Page);
|
|
|
|
|
|
|
|
var remoteMethodAddedClass;
|
|
|
|
app.on('remoteMethodAdded', function(sharedClass) {
|
|
|
|
remoteMethodAddedClass = sharedClass;
|
|
|
|
});
|
|
|
|
Book.nestRemoting('pages');
|
|
|
|
expect(remoteMethodAddedClass).to.exist();
|
|
|
|
expect(remoteMethodAddedClass).to.eql(Book.sharedClass);
|
|
|
|
});
|
|
|
|
|
2014-06-03 08:39:54 +00:00
|
|
|
it.onServer('updates REST API when a new model is added', function(done) {
|
2014-02-19 19:40:05 +00:00
|
|
|
app.use(loopback.rest());
|
|
|
|
request(app).get('/colors').expect(404, function(err, res) {
|
|
|
|
if (err) return done(err);
|
2016-11-15 21:46:23 +00:00
|
|
|
var Color = PersistedModel.extend('color', {name: String});
|
2014-02-19 19:40:05 +00:00
|
|
|
app.model(Color);
|
2014-05-03 04:19:14 +00:00
|
|
|
Color.attachTo(db);
|
2014-02-19 19:40:05 +00:00
|
|
|
request(app).get('/colors').expect(200, done);
|
|
|
|
});
|
2014-02-18 20:40:35 +00:00
|
|
|
});
|
2014-08-20 23:05:45 +00:00
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
it('accepts null dataSource', function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(MyTestModel, {dataSource: null});
|
2016-06-03 20:51:48 +00:00
|
|
|
expect(MyTestModel.dataSource).to.eql(null);
|
|
|
|
done();
|
2014-08-26 15:58:59 +00:00
|
|
|
});
|
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
it('accepts false dataSource', function(done) {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(MyTestModel, {dataSource: false});
|
2016-06-03 20:51:48 +00:00
|
|
|
expect(MyTestModel.getDataSource()).to.eql(null);
|
|
|
|
done();
|
2015-04-24 15:32:17 +00:00
|
|
|
});
|
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
it('does not require dataSource', function(done) {
|
|
|
|
app.model(MyTestModel);
|
|
|
|
done();
|
2013-10-29 21:12:23 +00:00
|
|
|
});
|
2014-05-25 14:27:45 +00:00
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
it('throws error if model typeof string is passed', function() {
|
|
|
|
var fn = function() { app.model('MyTestModel'); };
|
|
|
|
expect(fn).to.throw(/app(\.model|\.registry)/);
|
2014-06-10 06:53:01 +00:00
|
|
|
});
|
2013-12-12 03:31:16 +00:00
|
|
|
});
|
2013-10-29 21:12:23 +00:00
|
|
|
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
describe('app.model(ModelCtor, config)', function() {
|
|
|
|
it('attaches the model to a datasource', function() {
|
2015-04-01 21:50:36 +00:00
|
|
|
var previousModel = loopback.registry.findModel('TestModel');
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('db', {connector: 'memory'});
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
|
2015-04-01 21:50:36 +00:00
|
|
|
if (previousModel) {
|
|
|
|
delete previousModel.dataSource;
|
|
|
|
}
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
|
2015-04-01 21:50:36 +00:00
|
|
|
assert(!previousModel || !previousModel.dataSource);
|
2016-06-03 20:51:48 +00:00
|
|
|
var TestModel = app.registry.createModel('TestModel');
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(TestModel, {dataSource: 'db'});
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-04-16 12:07:30 +00:00
|
|
|
describe('app.deleteModelByName()', () => {
|
|
|
|
let TestModel;
|
|
|
|
beforeEach(setupTestModel);
|
|
|
|
|
|
|
|
it('removes the model from app registries', () => {
|
|
|
|
expect(Object.keys(app.models))
|
|
|
|
.to.contain('test-model')
|
|
|
|
.and.contain('TestModel')
|
|
|
|
.and.contain('testModel');
|
|
|
|
expect(app.models().map(m => m.modelName))
|
|
|
|
.to.contain('test-model');
|
|
|
|
|
|
|
|
app.deleteModelByName('test-model');
|
|
|
|
|
|
|
|
expect(Object.keys(app.models))
|
|
|
|
.to.not.contain('test-model')
|
|
|
|
.and.not.contain('TestModel')
|
|
|
|
.and.not.contain('testModel');
|
|
|
|
expect(app.models().map(m => m.modelName))
|
|
|
|
.to.not.contain('test-model');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('removes the model from juggler registries', () => {
|
|
|
|
expect(Object.keys(app.registry.modelBuilder.models))
|
|
|
|
.to.contain('test-model');
|
|
|
|
|
|
|
|
app.deleteModelByName('test-model');
|
|
|
|
|
|
|
|
expect(Object.keys(app.registry.modelBuilder.models))
|
|
|
|
.to.not.contain('test-model');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('removes the model from remoting registries', () => {
|
|
|
|
expect(Object.keys(app.remotes()._classes))
|
|
|
|
.to.contain('test-model');
|
|
|
|
|
|
|
|
app.deleteModelByName('test-model');
|
|
|
|
|
|
|
|
expect(Object.keys(app.remotes()._classes))
|
|
|
|
.to.not.contain('test-model');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('emits "modelDeleted" event', () => {
|
|
|
|
const spy = sinon.spy();
|
|
|
|
app.on('modelDeleted', spy);
|
|
|
|
|
|
|
|
app.deleteModelByName('test-model');
|
|
|
|
|
|
|
|
sinon.assert.calledWith(spy, TestModel);
|
|
|
|
});
|
|
|
|
|
|
|
|
function setupTestModel() {
|
|
|
|
TestModel = app.registry.createModel({
|
|
|
|
name: 'test-model',
|
|
|
|
base: 'Model',
|
|
|
|
});
|
|
|
|
app.model(TestModel, {dataSource: null});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2014-02-04 19:28:19 +00:00
|
|
|
describe('app.models', function() {
|
|
|
|
it('is unique per app instance', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('db', {connector: 'memory'});
|
2016-06-03 20:51:48 +00:00
|
|
|
var Color = app.registry.createModel('Color');
|
2016-11-15 21:46:23 +00:00
|
|
|
app.model(Color, {dataSource: 'db'});
|
2014-02-04 19:28:19 +00:00
|
|
|
expect(app.models.Color).to.equal(Color);
|
|
|
|
var anotherApp = loopback();
|
|
|
|
expect(anotherApp.models.Color).to.equal(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-05-27 12:33:42 +00:00
|
|
|
describe('app.dataSources', function() {
|
|
|
|
it('is unique per app instance', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('ds', {connector: 'memory'});
|
2014-05-27 12:33:42 +00:00
|
|
|
expect(app.datasources.ds).to.not.equal(undefined);
|
|
|
|
var anotherApp = loopback();
|
|
|
|
expect(anotherApp.datasources.ds).to.equal(undefined);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-05-28 13:02:55 +00:00
|
|
|
describe('app.dataSource', function() {
|
|
|
|
it('looks up the connector in `app.connectors`', function() {
|
|
|
|
app.connector('custom', loopback.Memory);
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('custom', {connector: 'custom'});
|
2017-12-15 22:32:22 +00:00
|
|
|
expect(app.dataSources.custom.name).to.equal('custom');
|
2014-05-28 13:02:55 +00:00
|
|
|
});
|
2016-02-26 13:20:07 +00:00
|
|
|
|
|
|
|
it('adds data source name to error messages', function() {
|
|
|
|
app.connector('throwing', {
|
|
|
|
initialize: function() { throw new Error('expected test error'); },
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('bad-ds', {connector: 'throwing'});
|
2016-02-26 13:20:07 +00:00
|
|
|
}).to.throw(/bad-ds.*throwing/);
|
|
|
|
});
|
2016-08-26 10:53:38 +00:00
|
|
|
|
|
|
|
it('adds app reference to the data source object', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
app.dataSource('ds', {connector: 'memory'});
|
2016-08-26 10:53:38 +00:00
|
|
|
expect(app.datasources.ds.app).to.not.equal(undefined);
|
|
|
|
expect(app.datasources.ds.app).to.equal(app);
|
|
|
|
});
|
2014-05-28 13:02:55 +00:00
|
|
|
});
|
|
|
|
|
2014-06-03 08:39:54 +00:00
|
|
|
describe.onServer('listen()', function() {
|
2014-01-08 14:20:17 +00:00
|
|
|
it('starts http server', function(done) {
|
|
|
|
var app = loopback();
|
|
|
|
app.set('port', 0);
|
2014-07-26 21:37:13 +00:00
|
|
|
app.get('/', function(req, res) { res.status(200).send('OK'); });
|
2014-01-08 14:20:17 +00:00
|
|
|
|
|
|
|
var server = app.listen();
|
|
|
|
|
|
|
|
expect(server).to.be.an.instanceOf(require('http').Server);
|
|
|
|
|
|
|
|
request(server)
|
|
|
|
.get('/')
|
|
|
|
.expect(200, done);
|
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('updates port on `listening` event', function(done) {
|
2014-01-08 14:20:17 +00:00
|
|
|
var app = loopback();
|
|
|
|
app.set('port', 0);
|
|
|
|
|
|
|
|
app.listen(function() {
|
|
|
|
expect(app.get('port'), 'port').to.not.equal(0);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-11-21 01:46:21 +00:00
|
|
|
it('updates `url` on `listening` event', function(done) {
|
2014-07-01 12:27:02 +00:00
|
|
|
var app = loopback();
|
|
|
|
app.set('port', 0);
|
|
|
|
app.set('host', undefined);
|
|
|
|
|
|
|
|
app.listen(function() {
|
2017-06-15 20:29:37 +00:00
|
|
|
var expectedUrl = 'http://localhost:' + app.get('port') + '/';
|
2014-10-17 07:58:02 +00:00
|
|
|
expect(app.get('url'), 'url').to.equal(expectedUrl);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-07-01 12:27:02 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
it('forwards to http.Server.listen on more than one arg', function(done) {
|
|
|
|
var app = loopback();
|
|
|
|
app.set('port', 1);
|
|
|
|
app.listen(0, '127.0.0.1', function() {
|
|
|
|
expect(app.get('port'), 'port').to.not.equal(0).and.not.equal(1);
|
|
|
|
expect(this.address().address).to.equal('127.0.0.1');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('forwards to http.Server.listen when the single arg is not a function',
|
|
|
|
function(done) {
|
|
|
|
var app = loopback();
|
|
|
|
app.set('port', 1);
|
|
|
|
app.listen(0).on('listening', function() {
|
|
|
|
expect(app.get('port'), 'port') .to.not.equal(0).and.not.equal(1);
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
it('uses app config when no parameter is supplied', function(done) {
|
|
|
|
var app = loopback();
|
|
|
|
// Http listens on all interfaces by default
|
|
|
|
// Custom host serves as an indicator whether
|
|
|
|
// the value was used by app.listen
|
|
|
|
app.set('host', '127.0.0.1');
|
|
|
|
app.listen()
|
|
|
|
.on('listening', function() {
|
|
|
|
expect(this.address().address).to.equal('127.0.0.1');
|
2016-05-05 04:09:06 +00:00
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2014-06-03 08:39:54 +00:00
|
|
|
describe.onServer('enableAuth', function() {
|
2014-02-05 17:46:22 +00:00
|
|
|
it('should set app.isAuthEnabled to true', function() {
|
|
|
|
expect(app.isAuthEnabled).to.not.equal(true);
|
|
|
|
app.enableAuth();
|
|
|
|
expect(app.isAuthEnabled).to.equal(true);
|
|
|
|
});
|
2015-04-21 11:46:17 +00:00
|
|
|
|
|
|
|
it('auto-configures required models to provided dataSource', function() {
|
|
|
|
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
|
2016-11-15 21:46:23 +00:00
|
|
|
var app = loopback({localRegistry: true, loadBuiltinModels: true});
|
2015-04-21 11:46:17 +00:00
|
|
|
require('../lib/builtin-models')(app.registry);
|
2016-11-15 21:46:23 +00:00
|
|
|
var db = app.dataSource('db', {connector: 'memory'});
|
2015-04-21 11:46:17 +00:00
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
app.enableAuth({dataSource: 'db'});
|
2015-04-21 11:46:17 +00:00
|
|
|
|
|
|
|
expect(Object.keys(app.models)).to.include.members(AUTH_MODELS);
|
|
|
|
|
|
|
|
AUTH_MODELS.forEach(function(m) {
|
|
|
|
var Model = app.models[m];
|
|
|
|
expect(Model.dataSource, m + '.dataSource').to.equal(db);
|
|
|
|
expect(Model.shared, m + '.shared').to.equal(m === 'User');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('detects already configured subclass of a required model', function() {
|
2016-11-15 21:46:23 +00:00
|
|
|
var app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
var db = app.dataSource('db', {connector: 'memory'});
|
|
|
|
var Customer = app.registry.createModel('Customer', {}, {base: 'User'});
|
|
|
|
app.model(Customer, {dataSource: 'db'});
|
2015-04-21 11:46:17 +00:00
|
|
|
|
2017-02-24 13:07:41 +00:00
|
|
|
// Fix AccessToken's "belongsTo user" relation to use our new Customer model
|
|
|
|
const AccessToken = app.registry.getModel('AccessToken');
|
|
|
|
AccessToken.settings.relations.user.model = 'Customer';
|
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
app.enableAuth({dataSource: 'db'});
|
2015-04-21 11:46:17 +00:00
|
|
|
|
|
|
|
expect(Object.keys(app.models)).to.not.include('User');
|
|
|
|
});
|
2014-02-05 17:46:22 +00:00
|
|
|
});
|
|
|
|
|
2014-11-21 02:35:36 +00:00
|
|
|
describe.onServer('app.get(\'/\', loopback.status())', function() {
|
|
|
|
it('should return the status of the application', function(done) {
|
2013-11-19 20:35:29 +00:00
|
|
|
var app = loopback();
|
|
|
|
app.get('/', loopback.status());
|
|
|
|
request(app)
|
|
|
|
.get('/')
|
|
|
|
.expect(200)
|
|
|
|
.end(function(err, res) {
|
2014-11-21 02:35:36 +00:00
|
|
|
if (err) return done(err);
|
2013-11-19 20:35:29 +00:00
|
|
|
|
2016-08-04 09:00:00 +00:00
|
|
|
expect(res.body).to.be.an('object');
|
|
|
|
expect(res.body).to.have.property('started');
|
|
|
|
expect(res.body.uptime, 'uptime').to.be.gte(0);
|
2013-11-19 20:35:29 +00:00
|
|
|
|
|
|
|
var elapsed = Date.now() - Number(new Date(res.body.started));
|
|
|
|
|
2016-08-04 09:00:00 +00:00
|
|
|
// elapsed should be a small positive number...
|
|
|
|
expect(elapsed, 'elapsed').to.be.within(0, 300);
|
2013-11-19 20:35:29 +00:00
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2014-05-28 13:02:55 +00:00
|
|
|
|
|
|
|
describe('app.connectors', function() {
|
|
|
|
it('is unique per app instance', function() {
|
|
|
|
app.connectors.foo = 'bar';
|
|
|
|
var anotherApp = loopback();
|
|
|
|
expect(anotherApp.connectors.foo).to.equal(undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('includes Remote connector', function() {
|
|
|
|
expect(app.connectors.remote).to.equal(loopback.Remote);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('includes Memory connector', function() {
|
|
|
|
expect(app.connectors.memory).to.equal(loopback.Memory);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('app.connector', function() {
|
2015-03-27 15:59:11 +00:00
|
|
|
// any connector will do
|
2014-05-28 13:02:55 +00:00
|
|
|
it('adds the connector to the registry', function() {
|
|
|
|
app.connector('foo-bar', loopback.Memory);
|
|
|
|
expect(app.connectors['foo-bar']).to.equal(loopback.Memory);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('adds a classified alias', function() {
|
|
|
|
app.connector('foo-bar', loopback.Memory);
|
|
|
|
expect(app.connectors.FooBar).to.equal(loopback.Memory);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('adds a camelized alias', function() {
|
|
|
|
app.connector('FOO-BAR', loopback.Memory);
|
|
|
|
expect(app.connectors.FOOBAR).to.equal(loopback.Memory);
|
|
|
|
});
|
|
|
|
});
|
2014-06-03 08:39:54 +00:00
|
|
|
|
|
|
|
describe('app.settings', function() {
|
|
|
|
it('can be altered via `app.set(key, value)`', function() {
|
|
|
|
app.set('write-key', 'write-value');
|
|
|
|
expect(app.settings).to.have.property('write-key', 'write-value');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('can be read via `app.get(key)`', function() {
|
|
|
|
app.settings['read-key'] = 'read-value';
|
|
|
|
expect(app.get('read-key')).to.equal('read-value');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('is unique per app instance', function() {
|
|
|
|
var app1 = loopback();
|
|
|
|
var app2 = loopback();
|
|
|
|
|
|
|
|
expect(app1.settings).to.not.equal(app2.settings);
|
|
|
|
|
|
|
|
app1.set('key', 'value');
|
|
|
|
expect(app2.get('key'), 'app2 value').to.equal(undefined);
|
|
|
|
});
|
|
|
|
});
|
2014-06-14 07:40:57 +00:00
|
|
|
|
2014-06-13 08:27:23 +00:00
|
|
|
it('exposes loopback as a property', function() {
|
|
|
|
var app = loopback();
|
|
|
|
expect(app.loopback).to.equal(loopback);
|
|
|
|
});
|
2014-10-11 14:36:29 +00:00
|
|
|
|
2017-07-29 02:20:30 +00:00
|
|
|
function setupUserModels(app, options, done) {
|
|
|
|
app.dataSource('db', {connector: 'memory'});
|
|
|
|
var UserAccount = app.registry.createModel(
|
|
|
|
'UserAccount',
|
|
|
|
{name: 'string'},
|
|
|
|
options
|
|
|
|
);
|
|
|
|
var UserRole = app.registry.createModel(
|
|
|
|
'UserRole',
|
|
|
|
{name: 'string'}
|
|
|
|
);
|
|
|
|
app.model(UserAccount, {dataSource: 'db'});
|
|
|
|
app.model(UserRole, {dataSource: 'db'});
|
|
|
|
UserAccount.hasMany(UserRole);
|
|
|
|
UserAccount.create({
|
|
|
|
name: 'user',
|
|
|
|
}, function(err, user) {
|
|
|
|
if (err) return done(err);
|
|
|
|
app.use(loopback.rest());
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('Model-level normalizeHttpPath option', function() {
|
|
|
|
var app;
|
2014-10-11 14:36:29 +00:00
|
|
|
beforeEach(function() {
|
|
|
|
app = loopback();
|
|
|
|
});
|
|
|
|
|
2017-07-29 02:20:30 +00:00
|
|
|
it.onServer('honours Model-level setting of `false`', function(done) {
|
|
|
|
setupUserModels(app, {
|
|
|
|
remoting: {normalizeHttpPath: false},
|
|
|
|
}, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
2014-10-11 14:36:29 +00:00
|
|
|
});
|
2017-07-29 02:20:30 +00:00
|
|
|
});
|
|
|
|
});
|
2014-10-11 14:36:29 +00:00
|
|
|
|
2017-07-29 02:20:30 +00:00
|
|
|
it.onServer('honours Model-level setting of `true`', function(done) {
|
|
|
|
setupUserModels(app, {
|
|
|
|
remoting: {normalizeHttpPath: true},
|
|
|
|
}, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/user-accounts').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe('app-level normalizeHttpPath option', function() {
|
|
|
|
var app;
|
|
|
|
beforeEach(function() {
|
|
|
|
app = loopback();
|
|
|
|
});
|
|
|
|
|
|
|
|
it.onServer('honours app-level setting of `false`', function(done) {
|
|
|
|
app.set('remoting', {rest: {normalizeHttpPath: false}});
|
|
|
|
setupUserModels(app, null, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it.onServer('honours app-level setting of `true`', function(done) {
|
|
|
|
app.set('remoting', {rest: {normalizeHttpPath: true}});
|
|
|
|
setupUserModels(app, null, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/user-accounts').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/user-accounts/1/user-roles').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Model-level and app-level normalizeHttpPath options', function() {
|
|
|
|
var app;
|
|
|
|
beforeEach(function() {
|
|
|
|
app = loopback();
|
|
|
|
});
|
|
|
|
|
|
|
|
it.onServer('prioritizes Model-level setting over the app-level one', function(done) {
|
|
|
|
app.set('remoting', {rest: {normalizeHttpPath: true}});
|
|
|
|
setupUserModels(app, {
|
|
|
|
remoting: {normalizeHttpPath: false},
|
|
|
|
}, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
request(app).get('/UserAccounts/1/userRoles').expect(200, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2014-10-11 14:36:29 +00:00
|
|
|
});
|
|
|
|
});
|
2013-10-29 21:12:23 +00:00
|
|
|
});
|
2014-11-11 16:51:50 +00:00
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
function executeMiddlewareHandlers(app, urlPath, callback) {
|
2016-08-04 12:41:33 +00:00
|
|
|
var handlerError = undefined;
|
2014-11-11 16:51:50 +00:00
|
|
|
var server = http.createServer(function(req, res) {
|
2016-08-04 12:41:33 +00:00
|
|
|
app.handle(req, res, function(err) {
|
|
|
|
if (err) {
|
|
|
|
handlerError = err;
|
|
|
|
res.statusCode = err.status || err.statusCode || 500;
|
|
|
|
res.end(err.stack || err);
|
|
|
|
} else {
|
|
|
|
res.statusCode = 204;
|
|
|
|
res.end();
|
|
|
|
}
|
|
|
|
});
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
if (callback === undefined && typeof urlPath === 'function') {
|
|
|
|
callback = urlPath;
|
|
|
|
urlPath = '/test/url';
|
|
|
|
}
|
|
|
|
|
2014-11-11 16:51:50 +00:00
|
|
|
request(server)
|
Scope app middleware to a list of paths
Add a new argument to `app.middleware` allowing developers
to restrict the middleware to a list of paths or regular expresions.
Modify `app.middlewareFromConfig` to pass `config.paths` as the second
arg of `app.middleware`.
Examples:
// A string path (interpreted via path-to-regexp)
app.middleware('auth', '/admin', ldapAuth);
// A regular expression
app.middleware('initial', /^\/~(admin|root)/, rejectWith404);
// A list of scopes
app.middleware('routes', ['/api', /^\/assets/.*\.json$/], foo);
// From config
app.middlewareFromConfig(
handlerFactory,
{
phase: 'initial',
paths: ['/scope', /^\/(a|b)/]
});
2014-11-19 11:03:47 +00:00
|
|
|
.get(urlPath)
|
2014-11-11 16:51:50 +00:00
|
|
|
.end(function(err) {
|
2016-08-04 12:41:33 +00:00
|
|
|
callback(handlerError || err);
|
2014-11-11 16:51:50 +00:00
|
|
|
});
|
|
|
|
}
|