@studiolabs/strong-remoting
Version:
StrongLoop Remoting Module
416 lines (360 loc) • 14.2 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 extend = require('util')._extend;
var expect = require('chai').expect;
var SharedClass = require('../lib/shared-class');
var factory = require('./helpers/shared-objects-factory.js');
var RemoteObjects = require('../');
var RestAdapter = require('../lib/rest-adapter');
function NOOP() {};
describe('SharedClass', function() {
var SomeClass;
beforeEach(function() { SomeClass = factory.createSharedClass(); });
describe('constructor', function() {
it('fills http.path from ctor.http', function() {
SomeClass.http = {path: '/foo'};
var sc = new SharedClass('some', SomeClass);
expect(sc.http.path).to.equal('/foo');
});
it('fills http.path using the name', function() {
var sc = new SharedClass('some', SomeClass);
expect(sc.http.path).to.equal('/some');
});
it('fills http.path using a normalized path', function() {
var sc = new SharedClass('SomeClass', SomeClass, {normalizeHttpPath: true});
var remotes = RemoteObjects.create();
remotes.addClass(sc);
var classes = new RestAdapter(remotes).getClasses();
expect(classes[0]).to.have.property('routes')
.eql([{path: '/some-class', verb: 'all'}]);
});
it('does not require a sharedConstructor', function() {
var myClass = {};
myClass.remoteNamespace = 'bar';
myClass.foo = function() {};
myClass.foo.shared = true;
var sc = new SharedClass(undefined, myClass);
var fns = sc.methods().map(getName);
expect(fns).to.contain('foo');
expect(sc.http).to.eql({path: '/bar'});
});
});
describe('sharedClass.methods()', function() {
it('discovers remote methods', function() {
var sc = new SharedClass('some', SomeClass);
SomeClass.staticMethod = function() {};
SomeClass.staticMethod.shared = true;
SomeClass.prototype.instanceMethod = function() {};
SomeClass.prototype.instanceMethod.shared = true;
var fns = sc.methods().map(getFn);
expect(fns).to.contain(SomeClass.staticMethod);
expect(fns).to.contain(SomeClass.prototype.instanceMethod);
});
it('returns all methods when includeDisabled is true', function() {
var sc = new SharedClass('MySharedClass', MySharedClass);
function MySharedClass() {
// this page left intentionally blank
}
var inputNames = ['foo', 'bar'];
sc.defineMethod(inputNames[0], {shared: false, isStatic: true});
sc.defineMethod(inputNames[1], {shared: true, isStatic: true});
var outputNames = sc.methods({includeDisabled: true}).map(function(m) {
return m.name;
});
expect(outputNames).to.eql(inputNames);
});
it('only discovers a function once with aliases', function() {
function MyClass() {}
var sc = new SharedClass('some', MyClass);
var fn = function() {};
fn.shared = true;
MyClass.a = fn;
MyClass.b = fn;
MyClass.prototype.a = fn;
MyClass.prototype.b = fn;
var methods = sc.methods();
var fns = methods.map(getFn);
expect(fns.length).to.equal(1);
expect(methods[0].aliases.sort()).to.eql(['a', 'b']);
});
it('discovers multiple functions correctly', function() {
function MyClass() {}
var sc = new SharedClass('some', MyClass);
MyClass.a = createSharedFn();
MyClass.b = createSharedFn();
MyClass.prototype.a = createSharedFn();
MyClass.prototype.b = createSharedFn();
var fns = sc.methods().map(getFn);
expect(fns.length).to.equal(4);
expect(fns).to.contain(MyClass.a);
expect(fns).to.contain(MyClass.b);
expect(fns).to.contain(MyClass.prototype.a);
expect(fns).to.contain(MyClass.prototype.b);
function createSharedFn() {
var fn = function() {};
fn.shared = true;
return fn;
}
});
it('should skip properties that are model classes', function() {
var sc = new SharedClass('some', SomeClass);
function MockModel1() {}
MockModel1.modelName = 'M1';
MockModel1.shared = true;
SomeClass.staticMethod = MockModel1;
function MockModel2() {}
MockModel2.modelName = 'M2';
MockModel2.shared = true;
SomeClass.prototype.instanceMethod = MockModel2;
var fns = sc.methods().map(getFn);
expect(fns).to.not.contain(SomeClass.staticMethod);
expect(fns).to.not.contain(SomeClass.prototype.instanceMethod);
});
});
describe('sharedClass.defineMethod(name, options)', function() {
it('defines a remote method', function() {
var sc = new SharedClass('SomeClass', SomeClass);
SomeClass.prototype.myMethod = function() {};
var METHOD_NAME = 'myMethod';
sc.defineMethod(METHOD_NAME, {
prototype: true,
});
var methods = sc.methods().map(getName);
expect(methods).to.contain(METHOD_NAME);
});
it('defines a remote method with accessType', function() {
var sc = new SharedClass('SomeClass', SomeClass);
SomeClass.prototype.myMethod = function() {};
var METHOD_NAME = 'myMethod';
sc.defineMethod(METHOD_NAME, {
isStatic: true,
prototype: true,
accessType: 'READ',
});
var methods = sc.methods().map(getName);
expect(methods).to.contain(METHOD_NAME);
expect(sc.findMethodByName(METHOD_NAME).accessType).to.eql('READ');
});
it('defines a remote method with arbitrary custom metadata', function() {
var sc = new SharedClass('SomeClass', SomeClass);
SomeClass.prototype.testFn = function() {};
sc.defineMethod('testFn', {
isStatic: true,
accessScope: 'read:custom',
});
expect(sc.findMethodByName('testFn').accessScope).to.eql('read:custom');
});
it('should allow a shared class to resolve dynamically defined functions',
function(done) {
var MyClass = function() {};
var METHOD_NAME = 'dynFn';
process.nextTick(function() {
MyClass[METHOD_NAME] = function(str, cb) {
cb(null, str);
};
done();
});
var sharedClass = new SharedClass('MyClass', MyClass);
sharedClass.defineMethod(METHOD_NAME, {});
var methods = sharedClass.methods().map(getName);
expect(methods).to.contain(METHOD_NAME);
}
);
});
describe('sharedClass.resolve(resolver)', function() {
it('should allow sharedMethods to be resolved dynamically', function() {
function MyClass() {}
MyClass.obj = {
dyn: function(cb) {
cb();
},
};
var sharedClass = new SharedClass('MyClass', MyClass);
sharedClass.resolve(function(define) {
define('dyn', {}, MyClass.obj.dyn);
});
var methods = sharedClass.methods().map(getName);
expect(methods).to.contain('dyn');
});
});
describe('sharedClass.find()', function() {
ignoreDeprecationsInThisBlock();
var sc, sm;
beforeEach(function() {
sc = new SharedClass('SomeClass', SomeClass);
SomeClass.prototype.myMethod = function() {};
var METHOD_NAME = 'myMethod';
sm = sc.defineMethod(METHOD_NAME, {
prototype: true,
});
});
it('finds sharedMethod for the given function', function() {
assert(sc.find(SomeClass.prototype.myMethod) === sm);
});
it('find sharedMethod by name', function() {
assert(sc.find('myMethod') === sm);
});
});
describe('sharedClass.findMethodByName()', function() {
it('finds sharedMethod by prototype method name', function() {
var sc = new SharedClass('SomeClass', SomeClass);
var sm = sc.defineMethod('testMethod', {
isStatic: false,
});
assert(sc.findMethodByName('prototype.testMethod') === sm);
});
it('find sharedMethod by static method name', function() {
var sc = new SharedClass('SomeClass', SomeClass);
var sm = sc.defineMethod('myMethod', {
isStatic: true,
});
assert(sc.findMethodByName('myMethod') === sm);
});
});
describe('remotes.addClass(sharedClass)', function() {
it('should make the class available', function() {
var CLASS_NAME = 'SomeClass';
var remotes = RemoteObjects.create();
var sharedClass = new SharedClass(CLASS_NAME, SomeClass);
remotes.addClass(sharedClass);
var classes = remotes.classes().map(getName);
expect(classes).to.contain(CLASS_NAME);
});
});
describe('sharedClass.disableMethod(methodName, isStatic)', function() {
ignoreDeprecationsInThisBlock();
var sc, sm;
var METHOD_NAME = 'testMethod';
var INST_METHOD_NAME = 'instTestMethod';
var DYN_METHOD_NAME = 'dynMethod';
beforeEach(function() {
sc = new SharedClass('SomeClass', SomeClass);
sm = sc.defineMethod(METHOD_NAME, {isStatic: true});
sm = sc.defineMethod(INST_METHOD_NAME, {isStatic: false});
sc.resolve(function(define) {
define(DYN_METHOD_NAME, {isStatic: true});
});
});
it('excludes disabled static methods from the method list', function() {
sc.disableMethod(METHOD_NAME, true);
var methods = sc.methods().map(getName);
expect(methods).to.not.contain(METHOD_NAME);
});
it('excludes disabled prototype methods from the method list', function() {
sc.disableMethod(INST_METHOD_NAME, false);
var methods = sc.methods().map(getName);
expect(methods).to.not.contain(INST_METHOD_NAME);
});
it('excludes disabled dynamic (resolved) methods from the method list', function() {
sc.disableMethod(DYN_METHOD_NAME, true);
var methods = sc.methods().map(getName);
expect(methods).to.not.contain(DYN_METHOD_NAME);
});
});
describe('sharedClass.disableMethodByName(methodName)', function() {
var sc, sm;
beforeEach(function() {
sc = new SharedClass('SomeClass', SomeClass);
});
describe('for static methods', function() {
var STATIC_METHOD_NAME = 'testMethod';
var STATIC_METHOD_ALIAS = 'testMethodAlias';
beforeEach(function() {
sm = sc.defineMethod(STATIC_METHOD_NAME, {
isStatic: true,
aliases: [STATIC_METHOD_ALIAS],
});
});
it('excludes methods from the method list disabled by name', function() {
sc.disableMethodByName(STATIC_METHOD_NAME);
var methods = sc.methods().map(getName);
expect(methods).to.not.contain(STATIC_METHOD_NAME);
});
it('excludes methods from the method list disabled by alias',
function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(STATIC_METHOD_NAME);
sc.disableMethodByName(STATIC_METHOD_ALIAS);
methods = sc.methods().map(getName);
expect(methods).to.not.contain(STATIC_METHOD_NAME);
});
it('does not exclude methods from the method list using a prototype ' +
'method name', function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(STATIC_METHOD_NAME);
sc.disableMethodByName('prototype.'.concat(STATIC_METHOD_NAME));
methods = sc.methods().map(getName);
expect(methods).to.contain(STATIC_METHOD_NAME);
});
it('does not exclude methods from the method list using a prototype ' +
'method alias', function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(STATIC_METHOD_NAME);
sc.disableMethodByName('prototype.'.concat(STATIC_METHOD_ALIAS));
methods = sc.methods().map(getName);
expect(methods).to.contain(STATIC_METHOD_NAME);
});
});
describe('for prototype methods', function() {
var INST_METHOD_BASENAME = 'instTestMethod';
var INST_METHOD_FULLNAME = 'prototype.instTestMethod';
var INST_METHOD_BASEALIAS = 'instTestMethodAlias';
var INST_METHOD_FULLALIAS = 'prototype.instTestMethodAlias';
beforeEach(function() {
sm = sc.defineMethod(INST_METHOD_BASENAME, {
isStatic: false,
aliases: [INST_METHOD_BASEALIAS],
});
});
it('excludes methods from the method list disabled by name',
function() {
sc.disableMethodByName(INST_METHOD_FULLNAME);
var methods = sc.methods().map(getName);
expect(methods).to.not.contain(INST_METHOD_FULLNAME);
expect(methods).to.not.contain(INST_METHOD_BASENAME);
});
it('excludes methods from the method list disabled by alias',
function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(INST_METHOD_BASENAME);
sc.disableMethodByName(INST_METHOD_FULLALIAS);
methods = sc.methods().map(getName);
expect(methods).to.not.contain(INST_METHOD_BASENAME);
});
it('does not exclude methods from the method list using ' +
'static method name', function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(INST_METHOD_BASENAME);
sc.disableMethodByName(INST_METHOD_BASENAME);
methods = sc.methods().map(getName);
expect(methods).to.contain(INST_METHOD_BASENAME);
});
it('does not exclude methods from the method list using ' +
'static method alias', function() {
var methods = sc.methods().map(getName);
expect(methods).to.contain(INST_METHOD_BASENAME);
sc.disableMethodByName(INST_METHOD_BASEALIAS);
methods = sc.methods().map(getName);
expect(methods).to.contain(INST_METHOD_BASENAME);
});
});
});
});
function getName(obj) {
return obj.name;
}
function getFn(obj) {
return obj.fn;
}
function ignoreDeprecationsInThisBlock() {
before(function() {
process.on('deprecation', NOOP);
});
after(function() {
process.removeListener('deprecation', NOOP);
});
}