250 lines
7.7 KiB
JavaScript
250 lines
7.7 KiB
JavaScript
// Copyright IBM Corp. 2016,2018. All Rights Reserved.
|
|
// Node module: loopback-context
|
|
// This file is licensed under the MIT License.
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
'use strict';
|
|
|
|
var asyncV152 = require('async-1.5.2');
|
|
var whenV377 = require('when-3.7.7');
|
|
var LoopBackContext = require('..');
|
|
var Domain = require('domain');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var expect = require('./helpers/expect');
|
|
var loopback = require('loopback');
|
|
var request = require('supertest');
|
|
|
|
describe('LoopBack Context', function() {
|
|
var runInOtherDomain, runnerInterval;
|
|
|
|
before(function setupRunInOtherDomain() {
|
|
var emitterInOtherDomain = new EventEmitter();
|
|
Domain.create().add(emitterInOtherDomain);
|
|
|
|
runInOtherDomain = function(fn) {
|
|
emitterInOtherDomain.once('run', fn);
|
|
};
|
|
|
|
runnerInterval = setInterval(function() {
|
|
emitterInOtherDomain.emit('run');
|
|
}, 10);
|
|
});
|
|
|
|
after(function tearDownRunInOtherDomain() {
|
|
clearInterval(runnerInterval);
|
|
});
|
|
|
|
// See the following two items for more details:
|
|
// https://github.com/strongloop/loopback/issues/809
|
|
// https://github.com/strongloop/loopback/pull/337#issuecomment-61680577
|
|
it('preserves callback domain', function(done) {
|
|
var app = loopback({localRegistry: true, loadBuiltinModels: true});
|
|
app.set('remoting', {context: false});
|
|
app.set('legacyExplorer', false);
|
|
app.use(LoopBackContext.perRequest());
|
|
app.use(loopback.rest());
|
|
app.dataSource('db', {connector: 'memory'});
|
|
|
|
var TestModel = loopback.createModel({name: 'TestModel'});
|
|
app.model(TestModel, {dataSource: 'db', public: true});
|
|
|
|
// function for remote method
|
|
TestModel.test = function(inst, cb) {
|
|
var tmpCtx = LoopBackContext.getCurrentContext();
|
|
if (tmpCtx) tmpCtx.set('data', 'a value stored in context');
|
|
if (process.domain) cb = process.domain.bind(cb); // IMPORTANT
|
|
runInOtherDomain(cb);
|
|
};
|
|
|
|
// remote method
|
|
TestModel.remoteMethod('test', {
|
|
accepts: {arg: 'inst', type: 'TestModel'},
|
|
returns: {root: true},
|
|
http: {path: '/test', verb: 'get'},
|
|
});
|
|
|
|
// after remote hook
|
|
TestModel.afterRemote('**', function(ctxx, inst, next) {
|
|
var tmpCtx = LoopBackContext.getCurrentContext();
|
|
if (tmpCtx) {
|
|
ctxx.result.data = tmpCtx.get('data');
|
|
} else {
|
|
ctxx.result.data = 'context not available';
|
|
}
|
|
|
|
next();
|
|
});
|
|
|
|
request(app)
|
|
.get('/TestModels/test')
|
|
.end(function(err, res) {
|
|
if (err) return done(err);
|
|
|
|
expect(res.body.data).to.equal('a value stored in context');
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('works outside REST middleware', function(done) {
|
|
LoopBackContext.runInContext(function() {
|
|
var ctx = LoopBackContext.getCurrentContext();
|
|
expect(ctx).is.an('object');
|
|
ctx.set('test-key', 'test-value');
|
|
process.nextTick(function() {
|
|
var ctx = LoopBackContext.getCurrentContext();
|
|
expect(ctx).is.an('object');
|
|
expect(ctx.get('test-key')).to.equal('test-value');
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
// Credits for the original idea for this test case to @marlonkjoseph
|
|
// Original source of the POC gist of the idea:
|
|
// https://gist.github.com/marlonkjoseph/f42f3c71f746896a0d4b7279a34ea753
|
|
// Heavily edited by others
|
|
it('keeps context when using waterfall() from async 1.5.2',
|
|
function(done) {
|
|
LoopBackContext.runInContext(function() {
|
|
// Trigger async waterfall callbacks
|
|
asyncV152.waterfall([
|
|
function pushToContext(next) {
|
|
var ctx = LoopBackContext.getCurrentContext();
|
|
expect(ctx).is.an('object');
|
|
ctx.set('test-key', 'test-value');
|
|
next();
|
|
},
|
|
function pullFromContext(next) {
|
|
var ctx = LoopBackContext.getCurrentContext();
|
|
expect(ctx).is.an('object');
|
|
var testValue = ctx && ctx.get('test-key', 'test-value');
|
|
next(null, testValue);
|
|
},
|
|
function verify(testValue, next) {
|
|
expect(testValue).to.equal('test-value');
|
|
next();
|
|
},
|
|
], done);
|
|
});
|
|
});
|
|
|
|
it('handles concurrent then() calls with when v3.7.7 promises & bind option',
|
|
function() {
|
|
return Promise.all([
|
|
runWithPushedValue('test-value-1', {bind: true}),
|
|
runWithPushedValue('test-value-2', {bind: true}),
|
|
])
|
|
.then(function verify(values) {
|
|
var failureCount = getFailureCount(values);
|
|
expect(failureCount).to.equal(0);
|
|
});
|
|
});
|
|
|
|
it('fails once without bind option and when v3.7.7 promises',
|
|
function() {
|
|
return Promise.all([
|
|
runWithPushedValue('test-value-3'),
|
|
runWithPushedValue('test-value-4'),
|
|
])
|
|
.then(function verify(values) {
|
|
var failureCount = getFailureCount(values);
|
|
expect(failureCount).to.equal(1);
|
|
});
|
|
});
|
|
|
|
var timeout = 100;
|
|
|
|
function runWithPushedValue(pushedValue, options) {
|
|
return new Promise(function concurrentExecutor(outerResolve, reject) {
|
|
LoopBackContext.runInContext(function pushToContext() {
|
|
var ctx = LoopBackContext.getCurrentContext(options);
|
|
expect(ctx).is.an('object');
|
|
ctx.set('test-key', pushedValue);
|
|
var whenPromise = whenV377().delay(timeout);
|
|
whenPromise.then(function pullFromContextAndReturn() {
|
|
var pulledValue = ctx && ctx.get('test-key');
|
|
outerResolve({
|
|
pulledValue: pulledValue,
|
|
pushedValue: pushedValue,
|
|
});
|
|
}).catch(reject);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getFailureCount(values) {
|
|
var failureCount = 0;
|
|
values.forEach(function(v) {
|
|
if (v.pulledValue !== v.pushedValue) {
|
|
failureCount++;
|
|
}
|
|
});
|
|
return failureCount;
|
|
}
|
|
|
|
it('doesn\'t mix up req\'s in chains of ' +
|
|
'Express-middleware-like func\'s if next() cb is bound',
|
|
function() {
|
|
return Promise.all([
|
|
runWithRequestId('test-value-5', true),
|
|
runWithRequestId('test-value-6', true),
|
|
])
|
|
.then(function verify(values) {
|
|
var failureCount = getFailureCount(values);
|
|
expect(failureCount).to.equal(0);
|
|
});
|
|
});
|
|
|
|
it('fails & mixes up ctx among requests in mw chains if next() cb is unbound',
|
|
function() {
|
|
return Promise.all([
|
|
runWithRequestId('test-value-7'),
|
|
runWithRequestId('test-value-8'),
|
|
])
|
|
.then(function verify(values) {
|
|
var failureCount = getFailureCount(values);
|
|
expect(failureCount).to.equal(1);
|
|
});
|
|
});
|
|
|
|
function runWithRequestId(pushedValue, bindNextCb) {
|
|
return new Promise(function chainExecutor(outerResolve, reject) {
|
|
LoopBackContext.runInContext(function concurrentChain() {
|
|
function middlewareBreakingCls(req, res, next) {
|
|
var ctx = LoopBackContext.getCurrentContext({bind: true});
|
|
if (bindNextCb) {
|
|
next = ctx.bind(next);
|
|
}
|
|
ctx.set('test-key', pushedValue);
|
|
var whenPromise = whenV377().delay(timeout);
|
|
whenPromise.then(next).catch(reject);
|
|
};
|
|
|
|
function middlewareReadingContext(req, res, next) {
|
|
var ctx = LoopBackContext.getCurrentContext({bind: true});
|
|
var pulledValue = ctx && ctx.get('test-key');
|
|
next(null, pulledValue);
|
|
};
|
|
|
|
// Run the chain
|
|
var req = null;
|
|
var res = null;
|
|
middlewareBreakingCls(req, res, function(err) {
|
|
if (err) return reject(err);
|
|
|
|
middlewareReadingContext(req, res, function(err, result) {
|
|
if (err) return reject(err);
|
|
|
|
outerResolve({
|
|
pulledValue: result,
|
|
pushedValue: pushedValue,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|