strong-remotingnext
Version:
StrongLoop Remoting Module
493 lines (401 loc) • 15.3 kB
JavaScript
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: strong-remoting
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
var assert = require('assert');
var HttpInvocation = require('../lib/http-invocation');
var extend = require('util')._extend;
var inherits = require('util').inherits;
var RemoteObjects = require('../');
var RestAdapter = require('../lib/rest-adapter');
var SharedClass = require('../lib/shared-class');
var SharedMethod = require('../lib/shared-method');
var expect = require('chai').expect;
var factory = require('./helpers/shared-objects-factory.js');
function NOOP() {}
describe('RestAdapter', function() {
var remotes;
beforeEach(function() {
remotes = RemoteObjects.create();
});
describe('getClasses()', function() {
it('fills `name`', function() {
remotes.exports.testClass = factory.createSharedClass();
var classes = getRestClasses();
expect(classes[0]).to.have.property('name', 'testClass');
});
it('fills `routes`', function() {
remotes.exports.testClass = factory.createSharedClass();
remotes.exports.testClass.http = { path: '/test-class', verb: 'any' };
var classes = getRestClasses();
expect(classes[0]).to.have.property('routes')
.eql([{ path: '/test-class', verb: 'any' }]);
});
it('fills `sharedClass`', function() {
remotes.exports.testClass = factory.createSharedClass();
var classes = getRestClasses();
expect(classes[0]).to.have.property('sharedClass');
expect(classes[0].sharedClass).to.be.an.instanceOf(SharedClass);
});
it('fills `ctor`', function() {
var testClass = remotes.exports.testClass = factory.createSharedClass();
testClass.sharedCtor.http = { path: '/shared-ctor', verb: 'all' };
var classes = getRestClasses();
expect(classes[0].ctor).to.have.property('routes')
.eql([{ path: '/shared-ctor', verb: 'all' }]);
});
it('fills static methods', function() {
var testClass = remotes.exports.testClass = factory.createSharedClass();
testClass.staticMethod = extend(someFunc, { shared: true });
var methods = getRestClasses()[0].methods;
expect(methods).to.have.length(1);
expect(methods[0]).to.have.property('name', 'staticMethod');
expect(methods[0]).to.have.property('fullName', 'testClass.staticMethod');
expect(methods[0])
.to.have.deep.property('routes[0].path', '/staticMethod');
});
it('fills prototype methods', function() {
var testClass = remotes.exports.testClass = factory.createSharedClass();
testClass.prototype.instanceMethod = extend(someFunc, { shared: true });
var methods = getRestClasses()[0].methods;
expect(methods).to.have.length(1);
expect(methods[0])
.to.have.property('fullName', 'testClass.prototype.instanceMethod');
expect(methods[0])
// Note: the `/id:` part is coming from testClass.sharedCtor
.to.have.deep.property('routes[0].path', '/:id/instanceMethod');
});
function getRestClasses() {
return new RestAdapter(remotes).getClasses();
}
});
describe('path normalization', function() {
it('fills `routes`', function() {
remotes.exports.testClass = factory.createSharedClass();
remotes.exports.testClass.http = { path: '/testClass', verb: 'any' };
var classes = getRestClasses();
expect(classes[0]).to.have.property('routes')
.eql([{ path: '/test-class', verb: 'any' }]);
});
function getRestClasses() {
return new RestAdapter(remotes, { normalizeHttpPath: true }).getClasses();
}
});
describe('RestClass', function() {
describe('getPath', function() {
it('returns the path of the first route', function() {
var restClass = givenRestClass({ http: [
{ path: '/a-path' },
{ path: '/another-path' }
]});
expect(restClass.getPath()).to.equal('/a-path');
});
});
function givenRestClass(config) {
var ctor = factory.createSharedClass(config);
remotes.testClass = ctor;
var sharedClass = new SharedClass('testClass', ctor);
return new RestAdapter.RestClass(sharedClass);
}
});
describe('RestMethod', function() {
var anArg = { arg: 'an-arg-name', type: String };
it('has `accepts`', function() {
var method = givenRestStaticMethod({ accepts: anArg });
expect(method.accepts).to.eql([anArg]);
});
it('has `returns`', function() {
var method = givenRestStaticMethod({ returns: anArg });
expect(method.returns).to.eql([anArg]);
});
it('has `errors`', function() {
var method = givenRestStaticMethod({ errors: anArg });
expect(method.errors).to.eql([anArg]);
});
it('has `description`', function() {
var method = givenRestStaticMethod({ description: 'a-desc' });
expect(method.description).to.equal('a-desc');
});
it('has `notes`', function() {
var method = givenRestStaticMethod({ notes: 'some-notes' });
expect(method.notes).to.equal('some-notes');
});
it('has `documented`', function() {
var method = givenRestStaticMethod({ documented: false });
expect(method.documented).to.equal(false);
});
it('has `documented:true` by default', function() {
var method = givenRestStaticMethod();
expect(method.documented).to.equal(true);
});
describe('isReturningArray()', function() {
it('returns true when there is single root Array arg', function() {
var method = givenRestStaticMethod({
returns: { root: true, type: Array }
});
expect(method.isReturningArray()).to.equal(true);
});
it('returns true when there is single root "array" arg', function() {
var method = givenRestStaticMethod({
returns: { root: true, type: Array }
});
expect(method.isReturningArray()).to.equal(true);
});
it('returns true when there is single root [Model] arg', function() {
var method = givenRestStaticMethod({
returns: { root: true, type: ['string'] }
});
expect(method.isReturningArray()).to.equal(true);
});
it('returns false otherwise', function() {
var method = givenRestStaticMethod({
returns: { arg: 'result', type: Array }
});
expect(method.isReturningArray()).to.equal(false);
});
it('handles invalid type', function() {
var method = givenRestStaticMethod({
returns: { root: true }
});
expect(method.isReturningArray()).to.equal(false);
});
});
describe('acceptsSingleBodyArgument()', function() {
it('returns true when the arg is a single Object from body', function() {
var method = givenRestStaticMethod({
accepts: {
arg: 'data',
type: Object,
http: { source: 'body' }
}
});
expect(method.acceptsSingleBodyArgument()).to.equal(true);
});
it('returns false otherwise', function() {
var method = givenRestStaticMethod({
accepts: { arg: 'data', type: Object }
});
expect(method.acceptsSingleBodyArgument()).to.equal(false);
});
});
describe('getHttpMethod', function() {
ignoreDeprecationsInThisBlock();
it('returns POST for `all`', function() {
var method = givenRestStaticMethod({ http: { verb: 'all'} });
expect(method.getHttpMethod()).to.equal('POST');
});
it('returns DELETE for `del`', function() {
var method = givenRestStaticMethod({ http: { verb: 'del'} });
expect(method.getHttpMethod()).to.equal('DELETE');
});
it('returns upper-case value otherwise', function() {
var method = givenRestStaticMethod({ http: { verb: 'get'} });
expect(method.getHttpMethod()).to.equal('GET');
});
});
describe('getPath', function() {
it('returns the path of the first route', function() {
var method = givenRestStaticMethod({ http: [
{ path: '/a-path' },
{ path: '/another-path' }
]});
expect(method.getPath()).to.equal('/a-path');
});
});
describe('getFullPath', function() {
ignoreDeprecationsInThisBlock();
it('returns class path + method path', function() {
var method = givenRestStaticMethod(
{ http: { path: '/a-method' } },
{ http: { path: '/a-class' } }
);
expect(method.getFullPath()).to.equal('/a-class/a-method');
});
});
describe('getEndpoints', function() {
it('should return verb and fullPath for multiple paths', function() {
var method = givenRestStaticMethod({ http: [
{ verb: 'DEL', path: '/testMethod1' },
{ verb: 'PUT', path: '/testMethod2' },
] });
var expectedEndpoints = [
{
fullPath: '/testClass/testMethod1',
verb: 'DELETE',
}, {
fullPath: '/testClass/testMethod2',
verb: 'PUT',
},
];
expect(method.getEndpoints()).to.eql(expectedEndpoints);
});
it('should return verb and fullPath for single path', function() {
var method = givenRestStaticMethod({ http: { verb: 'all' }});
expect(method.getEndpoints()).to.eql([
{
verb: 'POST',
fullPath: '/testClass/testMethod',
},
]);
});
});
function givenRestStaticMethod(methodConfig, classConfig) {
var name = 'testMethod';
methodConfig = extend({ shared: true }, methodConfig);
classConfig = extend({ shared: true}, classConfig);
remotes.testClass = extend({}, classConfig);
var fn = remotes.testClass[name] = extend(function() {}, methodConfig);
var sharedClass = new SharedClass('testClass', remotes.testClass, true);
var restClass = new RestAdapter.RestClass(sharedClass);
var sharedMethod = new SharedMethod(fn, name, sharedClass, methodConfig);
return new RestAdapter.RestMethod(restClass, sharedMethod);
}
});
describe('sortRoutes', function() {
it('should sort routes based on verb & path', function() {
var routes = [
{route: {verb: 'get', path: '/'}},
{route: {verb: 'get', path: '/:id'}},
{route: {verb: 'get', path: '/findOne'}},
{route: {verb: 'delete', path: '/'}},
{route: {verb: 'del', path: '/:id'}}
];
routes.sort(RestAdapter.sortRoutes);
expect(routes).to.eql([
{route: {verb: 'get', path: '/findOne'}},
{route: {verb: 'get', path: '/:id'}},
{route: {verb: 'get', path: '/'}},
{route: {verb: 'del', path: '/:id'}},
{route: {verb: 'delete', path: '/'}}
]);
});
it('should sort routes based on path accuracy', function() {
var routes = [
{route: {verb: 'get', path: '/'}},
{route: {verb: 'get', path: '/:id/docs'}},
{route: {verb: 'get', path: '/:id'}},
{route: {verb: 'get', path: '/findOne'}}
];
routes.sort(RestAdapter.sortRoutes);
expect(routes).to.eql([
{route: {verb: 'get', path: '/findOne'}},
{route: {verb: 'get', path: '/:id/docs'}},
{route: {verb: 'get', path: '/:id'}},
{route: {verb: 'get', path: '/'}}
]);
});
it('should sort routes with common parts', function() {
var routes = [
{route: {verb: 'get', path: '/sum'}},
{route: {verb: 'get', path: '/sum/1'}}
];
routes.sort(RestAdapter.sortRoutes);
expect(routes).to.eql([
{route: {verb: 'get', path: '/sum/1'}},
{route: {verb: 'get', path: '/sum'}}
]);
});
it('should sort routes with trailing /', function() {
var routes = [
{route: {verb: 'get', path: '/sum/'}},
{route: {verb: 'get', path: '/sum/1'}}
];
routes.sort(RestAdapter.sortRoutes);
expect(routes).to.eql([
{route: {verb: 'get', path: '/sum/1'}},
{route: {verb: 'get', path: '/sum/'}}
]);
});
});
describe('invoke()', function() {
var oldInvoke = HttpInvocation.prototype.invoke;
var remotes, req, res;
beforeEach(function() {
remotes = RemoteObjects.create();
req = false;
res = false;
HttpInvocation.prototype.invoke = function(callback) {
if (!this.req) {
this.createRequest();
}
req = this.req;
res = this.res = { foo: 'bar' };
this.transformResponse(res, null, callback);
};
});
afterEach(function() {
HttpInvocation.prototype.invoke = oldInvoke;
});
it('should call remote hooks', function(done) {
var beforeCalled = false;
var afterCalled = false;
var name = 'testClass.testMethod';
remotes.before(name, function(ctx, next) {
beforeCalled = true;
next();
});
remotes.after(name, function(ctx, next) {
afterCalled = true;
next();
});
var restAdapter = givenRestStaticMethod({ isStatic: true });
restAdapter.connect('foo');
restAdapter.invoke(name, [], [], function() {
assert(beforeCalled);
assert(afterCalled);
done();
});
});
it('should call beforeRemote hook with request object', function(done) {
var name = 'testClass.testMethod';
var _req;
remotes.before(name, function(ctx, next) {
_req = ctx.req;
next();
});
var restAdapter = givenRestStaticMethod({ isStatic: true });
restAdapter.connect('foo');
restAdapter.invoke(name, [], [], function() {
expect(_req).to.equal(req);
done();
});
});
it('should call afterRemote hook with response object', function(done) {
var name = 'testClass.testMethod';
var _res;
remotes.after(name, function(ctx, next) {
_res = ctx.res;
next();
});
var restAdapter = givenRestStaticMethod({ isStatic: true });
restAdapter.connect('foo');
restAdapter.invoke(name, [], [], function() {
expect(_res).to.equal(res);
done();
});
});
function givenRestStaticMethod(methodConfig, classConfig) {
var name = 'testMethod';
methodConfig = extend({ shared: true }, methodConfig);
classConfig = extend({ shared: true }, classConfig);
var testClass = extend({}, classConfig);
var fn = testClass[name] = extend(function() {}, methodConfig);
var sharedClass = new SharedClass('testClass', testClass, true);
var restClass = new RestAdapter.RestClass(sharedClass);
remotes.addClass(sharedClass);
var sharedMethod = new SharedMethod(fn, name, sharedClass, methodConfig);
var restMethod = new RestAdapter.RestMethod(restClass, sharedMethod);
return new RestAdapter(remotes);
}
});
});
function someFunc() {
}
function ignoreDeprecationsInThisBlock() {
before(function() {
process.on('deprecation', NOOP);
});
after(function() {
process.removeListener('deprecation', NOOP);
});
}