2020-01-21 19:19:18 +00:00
|
|
|
// Copyright IBM Corp. 2013,2019. 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';
|
2019-10-07 09:45:34 +00:00
|
|
|
const assert = require('assert');
|
|
|
|
const async = require('async');
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
const http = require('http');
|
|
|
|
const express = require('express');
|
|
|
|
const loopback = require('../');
|
|
|
|
const PersistedModel = loopback.PersistedModel;
|
|
|
|
|
|
|
|
const describe = require('./util/describe');
|
|
|
|
const expect = require('./helpers/expect');
|
|
|
|
const it = require('./util/it');
|
|
|
|
const 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let app;
|
2016-10-21 15:17:02 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const PHASES = [
|
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
|
|
|
'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);
|
|
|
|
}
|
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
let myHandler;
|
2015-02-25 17:47:18 +00:00
|
|
|
app.middleware('routes:before',
|
|
|
|
myHandler = handlerThatAddsHandler('my-handler'));
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const myHandler = namedHandler('my-handler');
|
|
|
|
const wrappedHandler = function(req, res, next) {
|
2015-02-25 17:47:18 +00:00
|
|
|
myHandler(req, res, next);
|
|
|
|
};
|
|
|
|
wrappedHandler['__NR_handler'] = myHandler;
|
|
|
|
app.middleware('routes:before', wrappedHandler);
|
2019-10-07 09:45:34 +00:00
|
|
|
const found = app._findLayerByHandler(myHandler);
|
2016-11-22 14:30:04 +00:00
|
|
|
expect(found).to.be.an('object');
|
2018-02-12 19:28:19 +00:00
|
|
|
expect(found).have.property('phase', 'routes:before');
|
|
|
|
executeMiddlewareHandlers(app, function(err) {
|
|
|
|
if (err) return done(err);
|
|
|
|
|
|
|
|
expect(steps).to.eql(['my-handler']);
|
|
|
|
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('allows handlers to be wrapped as __appdynamicsProxyInfo__ on express stack',
|
|
|
|
function(done) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const myHandler = namedHandler('my-handler');
|
|
|
|
const wrappedHandler = function(req, res, next) {
|
2018-02-12 19:28:19 +00:00
|
|
|
myHandler(req, res, next);
|
|
|
|
};
|
|
|
|
wrappedHandler['__appdynamicsProxyInfo__'] = {
|
|
|
|
orig: myHandler,
|
|
|
|
};
|
|
|
|
app.middleware('routes:before', wrappedHandler);
|
2019-10-07 09:45:34 +00:00
|
|
|
const found = app._findLayerByHandler(myHandler);
|
2018-02-12 19:28:19 +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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const myHandler = namedHandler('my-handler');
|
|
|
|
const wrappedHandler = function(req, res, next) {
|
2015-02-25 17:47:18 +00:00
|
|
|
myHandler(req, res, next);
|
|
|
|
};
|
|
|
|
wrappedHandler['__handler'] = myHandler;
|
|
|
|
app.middleware('routes:before', wrappedHandler);
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const expectedError = new Error('expected error');
|
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('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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const expectedError = new Error('expected error');
|
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('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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const expectedError = new Error('this should be handled by middleware');
|
|
|
|
let handledError;
|
2014-11-12 09:09:20 +00:00
|
|
|
|
|
|
|
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();
|
2019-11-17 19:04:57 +00:00
|
|
|
},
|
2018-08-08 15:22:20 +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
|
|
|
});
|
|
|
|
|
|
|
|
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();
|
2019-11-17 19:04:57 +00:00
|
|
|
},
|
2018-08-08 15:22:20 +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
|
|
|
});
|
|
|
|
|
|
|
|
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();
|
2019-11-17 19:04:57 +00:00
|
|
|
},
|
2018-08-08 15:22:20 +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
|
|
|
});
|
|
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
let 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
let reqProps;
|
2014-12-10 08:36:45 +00:00
|
|
|
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
|
2019-10-07 09:45:34 +00:00
|
|
|
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
2014-12-15 07:14:26 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
let data, mountWasEmitted;
|
|
|
|
const subapp = express();
|
2014-12-10 08:36:45 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const expected = {};
|
|
|
|
const actual = {};
|
2014-12-10 08:36:45 +00:00
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
const subapp = express();
|
2014-12-10 08:36:45 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const result = [];
|
|
|
|
for (const k in obj) {
|
2014-12-10 08:36:45 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const steps = [];
|
|
|
|
const expectedConfig = {key: 'value'};
|
2014-11-11 16:51:50 +00:00
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
const handlerFactory = function() {
|
|
|
|
const args = Array.prototype.slice.apply(arguments);
|
2014-11-11 16:51:50 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const steps = [];
|
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
|
|
|
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)/],
|
2019-11-17 19:04:57 +00:00
|
|
|
},
|
2018-08-08 15:22:20 +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
|
|
|
|
|
|
|
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();
|
2019-11-17 19:04:57 +00:00
|
|
|
},
|
2018-08-08 15:22:20 +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
|
|
|
});
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let app;
|
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
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const steps = [];
|
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
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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);
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const Color = PersistedModel.extend('color', {name: String});
|
2014-07-25 00:00:27 +00:00
|
|
|
Color.shared = true;
|
2019-10-07 09:45:34 +00:00
|
|
|
let remotedClass;
|
2014-07-25 00:00:27 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const Color = PersistedModel.extend('color', {name: String});
|
2016-04-27 11:15:24 +00:00
|
|
|
Color.shared = true;
|
2019-10-07 09:45:34 +00:00
|
|
|
let remoteMethodDisabledClass, disabledRemoteMethod;
|
2016-04-27 11:15:24 +00:00
|
|
|
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'});
|
2019-10-07 09:45:34 +00:00
|
|
|
const Book = app.registry.createModel(
|
2017-03-29 15:25:56 +00:00
|
|
|
'Book',
|
|
|
|
{name: 'string'},
|
2019-11-17 19:04:57 +00:00
|
|
|
{plural: 'books'},
|
2017-03-29 15:25:56 +00:00
|
|
|
);
|
|
|
|
app.model(Book, {dataSource: 'db'});
|
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
const Page = app.registry.createModel(
|
2017-03-29 15:25:56 +00:00
|
|
|
'Page',
|
|
|
|
{name: 'string'},
|
2019-11-17 19:04:57 +00:00
|
|
|
{plural: 'pages'},
|
2017-03-29 15:25:56 +00:00
|
|
|
);
|
|
|
|
app.model(Page, {dataSource: 'db'});
|
|
|
|
|
|
|
|
Book.hasMany(Page);
|
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
let remoteMethodAddedClass;
|
2017-03-29 15:25:56 +00:00
|
|
|
app.on('remoteMethodAdded', function(sharedClass) {
|
|
|
|
remoteMethodAddedClass = sharedClass;
|
|
|
|
});
|
|
|
|
Book.nestRemoting('pages');
|
|
|
|
expect(remoteMethodAddedClass).to.exist();
|
|
|
|
expect(remoteMethodAddedClass).to.eql(Book.sharedClass);
|
|
|
|
});
|
|
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const fn = function() { app.model('MyTestModel'); };
|
2016-06-03 20:51:48 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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);
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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'});
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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);
|
2019-10-07 09:45:34 +00:00
|
|
|
const anotherApp = loopback();
|
2014-02-04 19:28:19 +00:00
|
|
|
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);
|
2019-10-07 09:45:34 +00:00
|
|
|
const anotherApp = loopback();
|
2014-05-27 12:33:42 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-01-08 14:20:17 +00:00
|
|
|
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
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
const server = app.listen();
|
2014-01-08 14:20:17 +00:00
|
|
|
|
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-01-08 14:20:17 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-07-01 12:27:02 +00:00
|
|
|
app.set('port', 0);
|
|
|
|
app.set('host', undefined);
|
|
|
|
|
|
|
|
app.listen(function() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-01-08 14:20:17 +00:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-08-08 15:22:20 +00:00
|
|
|
it('forwards to http.Server.listen when the single arg is not a function', function(done) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2018-08-08 15:22:20 +00:00
|
|
|
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
|
|
|
|
2018-08-08 15:22:20 +00:00
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2014-01-08 14:20:17 +00:00
|
|
|
|
|
|
|
it('uses app config when no parameter is supplied', function(done) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-01-08 14:20:17 +00:00
|
|
|
// 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
|
|
|
|
const app = loopback({localRegistry: true, loadBuiltinModels: true});
|
2015-04-21 11:46:17 +00:00
|
|
|
require('../lib/builtin-models')(app.registry);
|
2019-10-07 09:45:34 +00:00
|
|
|
const 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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const Model = app.models[m];
|
2015-04-21 11:46:17 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
|
|
const db = app.dataSource('db', {connector: 'memory'});
|
|
|
|
const Customer = app.registry.createModel('Customer', {}, {base: 'User'});
|
2016-11-15 21:46:23 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2013-11-19 20:35:29 +00:00
|
|
|
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
|
|
|
|
2019-10-07 09:45:34 +00:00
|
|
|
const elapsed = Date.now() - Number(new Date(res.body.started));
|
2013-11-19 20:35:29 +00:00
|
|
|
|
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';
|
2019-10-07 09:45:34 +00:00
|
|
|
const anotherApp = loopback();
|
2014-05-28 13:02:55 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app1 = loopback();
|
|
|
|
const app2 = loopback();
|
2014-06-03 08:39:54 +00:00
|
|
|
|
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
const app = loopback();
|
2014-06-13 08:27:23 +00:00
|
|
|
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'});
|
2019-10-07 09:45:34 +00:00
|
|
|
const UserAccount = app.registry.createModel(
|
2017-07-29 02:20:30 +00:00
|
|
|
'UserAccount',
|
|
|
|
{name: 'string'},
|
2019-11-17 19:04:57 +00:00
|
|
|
options,
|
2017-07-29 02:20:30 +00:00
|
|
|
);
|
2019-10-07 09:45:34 +00:00
|
|
|
const UserRole = app.registry.createModel(
|
2017-07-29 02:20:30 +00:00
|
|
|
'UserRole',
|
2019-11-17 19:04:57 +00:00
|
|
|
{name: 'string'},
|
2017-07-29 02:20:30 +00:00
|
|
|
);
|
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let 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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let app;
|
2017-07-29 02:20:30 +00:00
|
|
|
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() {
|
2019-10-07 09:45:34 +00:00
|
|
|
let app;
|
2017-07-29 02:20:30 +00:00
|
|
|
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) {
|
2019-10-07 09:45:34 +00:00
|
|
|
let handlerError = undefined;
|
|
|
|
const 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
|
|
|
});
|
|
|
|
}
|