vremoting
Version:
StrongLoop Remoting Module
1,492 lines (1,306 loc) • 42.8 kB
JavaScript
var assert = require('assert');
var extend = require('util')._extend;
var inherits = require('util').inherits;
var RemoteObjects = require('../');
var express = require('express');
var request = require('supertest');
var expect = require('chai').expect;
var factory = require('./helpers/shared-objects-factory.js');
describe('strong-remoting-rest', function(){
var app;
var server;
var objects;
var remotes;
var adapterName = 'rest';
before(function(done) {
app = express();
app.disable('x-powered-by');
app.use(function (req, res, next) {
// create the handler for each request
objects.handler(adapterName).apply(objects, arguments);
});
server = app.listen(done);
});
// setup
beforeEach(function(){
if (process.env.NODE_ENV === 'production') {
process.env.NODE_ENV = 'test';
}
objects = RemoteObjects.create({json: {limit: '1kb'},
errorHandler: {disableStackTrace: false}});
remotes = objects.exports;
// connect to the app
objects.connect('http://localhost:' + server.address().port, adapterName);
});
function json(method, url) {
if (url === undefined) {
url = method;
method = 'get';
}
return request(app)[method](url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.expect('Content-Type', /json/);
}
describe('remoting options', function(){
// The 1kb limit is set by RemoteObjects.create({json: {limit: '1kb'}});
it('should reject json payload larger than 1kb', function(done) {
var method = givenSharedStaticMethod(
function greet(msg, cb) {
cb(null, msg);
},
{
accepts: { arg: 'person', type: 'string', http: {source: 'body'} },
returns: { arg: 'msg', type: 'string' }
}
);
// Build an object that is larger than 1kb
var name = "";
for (var i = 0; i < 2048; i++) {
name += "11111111111";
}
request(app)['post'](method.url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send(name)
.expect(413, done);
});
it('should disable stack trace', function(done) {
objects.options.errorHandler.disableStackTrace = true;
var method = givenSharedStaticMethod(
function(cb) {
cb(new Error('test-error'));
}
);
// Send a plain, non-json request to make sure the error handler
// always returns a json response.
request(app).get(method.url)
.expect('Content-Type', /json/)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, ['stack'], done));
});
it('should disable stack trace', function(done) {
process.env.NODE_ENV = 'production';
var method = givenSharedStaticMethod(
function(cb) {
cb(new Error('test-error'));
}
);
// Send a plain, non-json request to make sure the error handler
// always returns a json response.
request(app).get(method.url)
.expect('Content-Type', /json/)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, ['stack'], done));
});
});
describe('cors', function() {
var method;
beforeEach(function() {
method = givenSharedStaticMethod(
function greet(person, cb) {
if (person === 'error') {
var err = new Error('error');
err.statusCode = 400;
cb(err);
} else {
cb(null, 'hello');
}
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
});
it('should support cors', function(done) {
request(app)['post'](method.url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Origin', 'http://localhost:3001')
.send({person: 'ABC'})
.expect('Access-Control-Allow-Origin', 'http://localhost:3001')
.expect('Access-Control-Allow-Credentials', 'true')
.expect(200, done);
});
it('should skip cors if origin is the same as the request url', function(done) {
var server = request(app)['post'](method.url);
var url = server.url.replace('/testClass/testMethod', '');
server
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Origin', url)
.send({person: 'ABC'})
.end(function(err, res) {
assert(res.headers['Access-Control-Allow-Origin'] === undefined);
assert(res.headers['Access-Control-Allow-Credentials'] === undefined);
done();
});
});
it('should support cors preflight', function(done) {
request(app)['options'](method.url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Origin', 'http://localhost:3001')
.send()
.expect('Access-Control-Allow-Origin', 'http://localhost:3001')
.expect('Access-Control-Allow-Credentials', 'true')
.expect(204, done);
});
it('should support cors when errors happen', function(done) {
request(app)['post'](method.url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Origin', 'http://localhost:3001')
.send({person: 'error'})
.expect('Access-Control-Allow-Origin', 'http://localhost:3001')
.expect('Access-Control-Allow-Credentials', 'true')
.expect(400, done);
});
it('should support cors when parsing errors happen', function(done) {
request(app)['post'](method.url)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('Origin', 'http://localhost:3001')
.send('ABC') // invalid json
.expect('Access-Control-Allow-Origin', 'http://localhost:3001')
.expect('Access-Control-Allow-Credentials', 'true')
.expect(400, done);
});
});
describe('call of constructor method', function(){
it('should work', function(done) {
var method = givenSharedStaticMethod(
function greet(msg, cb) {
cb(null, msg);
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
json(method.url + '?person=hello')
.expect(200, { msg: 'hello' }, done);
});
it('should allow arguments in the path', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'path' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/:a' }
}
);
json(method.classUrl +'/1?b=2')
.expect({ n: 3 }, done);
});
it('should allow arguments in the query', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'query' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
json(method.classUrl +'/?a=1&b=2')
.expect({ n: 3 }, done);
});
it('should allow string[] arg in the query', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, b.join('') + a);
},
{
accepts: [
{ arg: 'a', type: 'string' },
{ arg: 'b', type: ['string'], http: {source: 'query' } }
],
returns: { arg: 'n', type: 'string' },
http: { path: '/' }
}
);
json(method.classUrl +'/?a=z&b[0]=x&b[1]=y')
.expect({ n: 'xyz' }, done);
});
it('should allow string[] arg in the query with stringified value',
function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, b.join('') + a);
},
{
accepts: [
{ arg: 'a', type: 'string' },
{ arg: 'b', type: ['string'], http: {source: 'query' } }
],
returns: { arg: 'n', type: 'string' },
http: { path: '/' }
}
);
json(method.classUrl +'/?a=z&b=["x", "y"]')
.expect({ n: 'xyz' }, done);
});
it('should allow custom argument functions', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: function(ctx) {
return ctx.req.query.a;
} }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
json(method.classUrl +'/?a=1&b=2')
.expect({ n: 3 }, done);
});
it('should pass undefined if the argument is not supplied', function (done) {
var called = false;
var method = givenSharedStaticMethod(
function bar(a, cb) {
called = true;
assert(a === undefined, 'a should be undefined');
cb();
},
{
accepts: [
{ arg: 'b', type: 'number' }
]
}
);
json(method.url).end(function() {
assert(called);
done();
});
});
it('should allow arguments in the body', function(done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
request(app)['post'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send('{"x": 1, "y": "Y"}')
.expect('Content-Type', /json/)
.expect(200, function(err, res){
expect(res.body).to.deep.equal({"x": 1, "y": "Y"});
done(err, res);
});
});
it('should allow arguments in the body with date', function(done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
var data = {date: {$type: 'date', $data: new Date()}};
request(app)['post'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send(data)
.expect('Content-Type', /json/)
.expect(200, function(err, res){
expect(res.body).to.deep.equal({date: data.date.$data.toISOString()});
done(err, res);
});
});
it('should allow arguments in the form', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number', http: {source: 'form' } },
{ arg: 'a', type: 'number', http: {source: 'form' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
request(app)['post'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send('a=1&b=2')
.expect('Content-Type', /json/)
.expect({ n: 3 }, done);
});
it('should allow arguments in the header', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number', http: {source: 'header' } },
{ arg: 'a', type: 'number', http: {source: 'header' } }
],
returns: { arg: 'n', type: 'number' },
http: { verb: 'get', path: '/' }
}
);
request(app)['get'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('a', 1)
.set('b', 2)
.send()
.expect('Content-Type', /json/)
.expect({ n: 3 }, done);
});
it('should allow arguments in the header without http source',
function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number' }
],
returns: { arg: 'n', type: 'number' },
http: { verb: 'get', path: '/' }
}
);
request(app)['get'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.set('a', 1)
.set('b', 2)
.send()
.expect('Content-Type', /json/)
.expect({ n: 3 }, done);
});
it('should allow arguments from http req and res', function(done) {
var method = givenSharedStaticMethod(
function bar(req, res, cb) {
res.status(200).send(req.body);
},
{
accepts: [
{ arg: 'req', type: 'object', http: {source: 'req' } },
{ arg: 'res', type: 'object', http: {source: 'res' } }
],
http: { path: '/' }
}
);
request(app)['post'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send('{"x": 1, "y": "Y"}')
.expect('Content-Type', /json/)
.expect(200, function(err, res){
expect(res.body).to.deep.equal({"x": 1, "y": "Y"});
done(err, res);
});
});
it('should allow arguments from http context', function(done) {
var method = givenSharedStaticMethod(
function bar(ctx, cb) {
ctx.res.status(200).send(ctx.req.body);
},
{
accepts: [
{ arg: 'ctx', type: 'object', http: {source: 'context' } }
],
http: { path: '/' }
}
);
request(app)['post'](method.classUrl)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.send('{"x": 1, "y": "Y"}')
.expect('Content-Type', /json/)
.expect(200, function(err, res){
expect(res.body).to.deep.equal({"x": 1, "y": "Y"});
done(err, res);
});
});
it('should respond with 204 if returns is not defined', function(done) {
var method = givenSharedStaticMethod(
function(cb) { cb(null, 'value-to-ignore'); }
);
json(method.url)
.expect(204, done);
});
it('should accept custom content-type header if respond with 204', function(done) {
var method = givenSharedStaticMethod();
objects.before(method.name, function(ctx, next) {
ctx.res.set('Content-Type', 'application/json; charset=utf-8; profile=http://example.org/');
next();
});
request(app).get(method.url)
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json; charset=utf-8; profile=http://example.org/')
.expect(204, done);
});
it('should respond with named results if returns has multiple args', function(done) {
var method = givenSharedStaticMethod(
function(a, b, cb) {
cb(null, a, b);
},
{
accepts: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
],
returns: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
]
}
);
json(method.url + '?a=1&b=2')
.expect({a: 1, b: 2}, done);
});
it('should remove any X-Powered-By header to LoopBack', function(done) {
var method = givenSharedStaticMethod(
function(cb) { cb(null, 'value-to-ignore'); }
);
json(method.url)
.expect(204)
.end(function(err,result){
expect(result.headers).not.to.have.keys(['x-powered-by']);
done();
});
});
it('should report error for mismatched arg type', function(done) {
remotes.foo = {
bar: function (a, fn) {
fn(null, a);
}
};
var fn = remotes.foo.bar;
fn.shared = true;
fn.accepts = [
{arg: 'a', type: 'object'}
];
fn.returns = {root: true};
json('get', '/foo/bar?a=foo')
.expect(500, done);
});
it('should coerce boolean strings - true', function(done) {
remotes.foo = {
bar: function (a, fn) {
fn(null, a);
}
};
var fn = remotes.foo.bar;
fn.shared = true;
fn.accepts = [
{arg: 'a', type: 'object'}
];
fn.returns = {root: true};
json('get', '/foo/bar?a[foo]=true')
.expect({foo: true}, done);
});
it('should coerce boolean strings - false', function(done) {
remotes.foo = {
bar: function (a, fn) {
fn(null, a);
}
};
var fn = remotes.foo.bar;
fn.shared = true;
fn.accepts = [
{arg: 'a', type: 'object'},
];
fn.returns = {root: true};
json('get', '/foo/bar?a[foo]=false')
.expect({foo: false}, done);
});
it('should coerce number strings', function(done) {
remotes.foo = {
bar: function (a, b, fn) {
fn(null, a + b);
}
};
var fn = remotes.foo.bar;
fn.shared = true;
fn.accepts = [
{arg: 'a', type: 'number'},
{arg: 'b', type: 'number'}
];
fn.returns = {root: true};
json('get', '/foo/bar?a=42&b=0.42')
.expect(200, function (err, res) {
assert.equal(res.body, 42.42);
done();
});
});
it('should allow empty body for json request', function(done) {
remotes.foo = {
bar: function (a, b, fn) {
fn(null, a, b);
}
};
var fn = remotes.foo.bar;
fn.shared = true;
fn.accepts = [
{arg: 'a', type: 'number'},
{arg: 'b', type: 'number'}
];
fn.returns = [
{arg: 'a', type: 'number'},
{arg: 'b', type: 'number'}
];
json('post', '/foo/bar?a=1&b=2').set('Content-Length', 0)
.expect({a: 1, b: 2}, done);
});
it('should call rest hooks', function(done) {
var hooksCalled = [];
var method = givenSharedStaticMethod({
rest: {
before: createHook('beforeRest'),
after: createHook('afterRest')
}
});
objects.before(method.name, createHook('beforeRemote'));
objects.after(method.name, createHook('afterRemote'));
json(method.url)
.end(function(err) {
if (err) done(err);
assert.deepEqual(
hooksCalled,
['beforeRest', 'beforeRemote', 'afterRemote', 'afterRest']
);
done();
});
function createHook(name) {
return function(ctx, next) {
hooksCalled.push(name);
next();
};
}
});
describe('uncaught errors', function () {
it('should return 500 if an error object is thrown', function (done) {
remotes.shouldThrow = {
bar: function (fn) {
throw new Error('an error');
fn(null);
}
};
var fn = remotes.shouldThrow.bar;
fn.shared = true;
json('get', '/shouldThrow/bar?a=1&b=2')
.expect(500)
.end(expectErrorResponseContaining({message: 'an error'}, done));
});
it('should return 500 if an error string is thrown', function (done) {
remotes.shouldThrow = {
bar: function (fn) {
throw 'an error';
fn(null);
}
};
var fn = remotes.shouldThrow.bar;
fn.shared = true;
json('get', '/shouldThrow/bar?a=1&b=2')
.expect(500)
.end(expectErrorResponseContaining({message: 'an error'}, done));
});
});
it('should return 500 when method returns an error', function(done) {
var method = givenSharedStaticMethod(
function(cb) {
cb(new Error('test-error'));
}
);
// Send a plain, non-json request to make sure the error handler
// always returns a json response.
request(app).get(method.url)
.expect('Content-Type', /json/)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
it('should return 500 when "before" returns an error', function(done) {
var method = givenSharedStaticMethod();
objects.before(method.name, function(ctx, next) {
next(new Error('test-error'));
});
json(method.url)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
it('should return 500 when "after" returns an error', function(done) {
var method = givenSharedStaticMethod();
objects.after(method.name, function(ctx, next) {
next(new Error('test-error'));
});
json(method.url)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
it('should return 400 when a required arg is missing', function (done) {
var method = givenSharedPrototypeMethod(
function(a, cb) {
cb();
},
{
accepts: [
{ arg: 'a', type: 'number', required: true }
]
}
);
json(method.url)
.expect(400, done);
});
});
describe('call of prototype method', function(){
it('should work', function(done) {
var method = givenSharedPrototypeMethod(
function greet(msg, cb) {
cb(null, this.id + ':' + msg);
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
json(method.getUrlForId('world') + '?person=hello')
.expect(200, { msg: 'world:hello' }, done);
});
it('should have the correct scope', function(done) {
var method = givenSharedPrototypeMethod(
function greet(msg, cb) {
assert.equal(this.constructor, method.ctor);
cb(null, this.id + ':' + msg);
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
json(method.getUrlForId('world') + '?person=hello')
.expect(200, { msg: 'world:hello' }, done);
});
it('should allow arguments in the path', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, b, cb) {
cb(null, this.id + ':' + (a + b));
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'path' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/:a' }
}
);
json(method.getClassUrlForId('sum') +'/1?b=2')
.expect({ n: 'sum:3' }, done);
});
it('should allow jsonp requests', function (done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'number', http: {source: 'path'} }
],
returns: { arg: 'n', type: 'number', root: true},
http: { path: '/:a' }
}
);
request(app)['get'](method.classUrl + '/1?callback=boo')
.set('Accept', 'application/javascript')
.expect('Content-Type', /javascript/)
.expect('/**/ typeof boo === \'function\' && boo(1);', done);
});
it('should allow jsonp requests with null response', function (done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, null);
},
{
accepts: [
{ arg: 'a', type: 'number', http: {source: 'path'} }
],
returns: { arg: 'n', type: 'number', root: true},
http: { path: '/:a' }
}
);
request(app)['get'](method.classUrl + '/1?callback=boo')
.set('Accept', 'application/javascript')
.expect('Content-Type', /javascript/)
.expect('/**/ typeof boo === \'function\' && boo(null);', done);
});
it('should allow arguments in the query', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, b, cb) {
cb(null, this.id + ':' + (a + b));
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'query' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
json(method.getClassUrlForId('sum') +'/?b=2&a=1')
.expect({ n: 'sum:3' }, done);
});
it('should support methods on `/` path', function(done) {
var method = givenSharedPrototypeMethod({
http: { path: '/', verb: 'get'}
});
json('get', method.getClassUrlForId(0))
.expect(204) // 204 No Content
.end(done);
});
it('should respond with 204 if returns is not defined', function(done) {
var method = givenSharedPrototypeMethod(
function(cb) { cb(null, 'value-to-ignore'); }
);
json(method.getUrlForId('an-id'))
.expect(204, done);
});
it('should respond with named results if returns has multiple args', function(done) {
var method = givenSharedPrototypeMethod(
function(a, b, cb) {
cb(null, this.id, a, b);
},
{
accepts: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
],
returns: [
{ arg: 'id', type: 'any' },
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
]
}
);
json(method.getUrlForId('an-id') + '?a=1&b=2')
.expect({ id: 'an-id', a: 1, b: 2 }, done);
});
it('should return 500 when method returns an error', function(done) {
var method = givenSharedPrototypeMethod(
function(cb) {
cb(new Error('test-error'));
}
);
json(method.url)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
it('should return 500 when "before" returns an error', function(done) {
var method = givenSharedPrototypeMethod();
objects.before(method.name, function(ctx, next) {
next(new Error('test-error'));
});
json(method.url)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
it('should return 500 when "after" returns an error', function(done) {
var method = givenSharedPrototypeMethod();
objects.after(method.name, function(ctx, next) {
next(new Error('test-error'));
});
json(method.url)
.expect(500)
.end(expectErrorResponseContaining({message: 'test-error'}, done));
});
});
it('returns 404 for unknown method of a shared class', function(done) {
var classUrl = givenSharedStaticMethod().classUrl;
json(classUrl + '/unknown-method')
.expect(404, done);
});
it('returns 404 with standard JSON body for uknown URL', function(done) {
json('/unknown-url')
.expect(404)
.end(expectErrorResponseContaining({status: 404}, done));
});
it('returns correct error response body', function(done) {
function TestError() {
Error.captureStackTrace(this, TestError);
this.name = 'TestError';
this.message = 'a test error';
this.status = 444;
this.aCustomProperty = 'a-custom-value';
}
inherits(TestError, Error);
var method = givenSharedStaticMethod(function(cb) { cb(new TestError()); });
json(method.url)
.expect(444)
.end(function(err, result) {
if (err) done(err);
expect(result.body).to.have.keys(['error']);
var expected = {
name: 'TestError',
status: 444,
message: 'a test error',
aCustomProperty: 'a-custom-value'
};
for (var prop in expected) {
expect(result.body.error[prop], prop).to.equal(expected[prop]);
}
expect(result.body.error.stack, 'stack').to.contain(__filename);
done();
});
});
describe('client', function() {
describe('call of constructor method', function(){
it('should work', function(done) {
var method = givenSharedStaticMethod(
function greet(msg, cb) {
cb(null, msg);
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
var msg = 'hello';
objects.invoke(method.name, [msg], function(err, resMsg) {
assert.equal(resMsg, msg);
done();
});
});
it('should allow arguments in the path', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'path' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/:a' }
}
);
objects.invoke(method.name, [1, 2], function(err, n) {
assert.equal(n, 3);
done();
});
});
it('should allow arguments in the query', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'query' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
objects.invoke(method.name, [1, 2], function(err, n) {
assert.equal(n, 3);
done();
});
});
it('should pass undefined if the argument is not supplied', function (done) {
var called = false;
var method = givenSharedStaticMethod(
function bar(a, cb) {
called = true;
assert(a === undefined, 'a should be undefined');
cb();
},
{
accepts: [
{ arg: 'b', type: 'number' }
]
}
);
objects.invoke(method.name, [], function(err) {
assert(called);
done();
});
});
it('should allow arguments in the body', function(done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
var obj = {
foo: 'bar'
};
objects.invoke(method.name, [obj], function(err, data) {
expect(obj).to.deep.equal(data);
done();
});
});
it('should allow arguments in the body with date', function(done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
var data = {date: {$type: 'date', $data: new Date()}};
objects.invoke(method.name, [data], function(err, resData) {
expect(resData).to.deep.equal({date: data.date.$data.toISOString()});
done();
});
});
it('should allow arguments in the form', function(done) {
var method = givenSharedStaticMethod(
function bar(a, b, cb) {
cb(null, a + b);
},
{
accepts: [
{ arg: 'b', type: 'number', http: {source: 'form' } },
{ arg: 'a', type: 'number', http: {source: 'form' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
objects.invoke(method.name, [1, 2], function(err, n) {
assert.equal(n, 3);
done();
});
});
it('should respond with correct args if returns has multiple args', function(done) {
var method = givenSharedStaticMethod(
function(a, b, cb) {
cb(null, a, b);
},
{
accepts: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
],
returns: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
]
}
);
objects.invoke(method.name, [1, 2], function(err, a, b) {
assert.equal(a, 1);
assert.equal(b, 2);
done();
});
});
describe('uncaught errors', function () {
it('should return 500 if an error object is thrown', function (done) {
var errMsg = 'an error';
var method = givenSharedStaticMethod(
function(a, b, cb) {
throw new Error(errMsg);
}
);
objects.invoke(method.name, function(err) {
assert(err instanceof Error);
assert.equal(err.message, errMsg);
done();
});
});
});
});
describe('call of prototype method', function(){
it('should work', function(done) {
var method = givenSharedPrototypeMethod(
function greet(msg, cb) {
cb(null, this.id + ':' + msg);
},
{
accepts: { arg: 'person', type: 'string' },
returns: { arg: 'msg', type: 'string' }
}
);
var msg = 'hello';
objects.invoke(method.name, ['anId'], [msg], function(err, resMsg) {
assert.equal(resMsg, 'anId:' + msg);
done();
});
});
it('should allow arguments in the path', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, b, cb) {
cb(null, Number(this.id) + a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'path' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/:a' }
}
);
objects.invoke(method.name, [39], [1, 2], function(err, n) {
assert.equal(n, 42);
done();
});
});
it('should allow arguments in the query', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, b, cb) {
cb(null, Number(this.id) + a + b);
},
{
accepts: [
{ arg: 'b', type: 'number' },
{ arg: 'a', type: 'number', http: {source: 'query' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
objects.invoke(method.name, [39], [1, 2], function(err, n) {
assert.equal(n, 42);
done();
});
});
it('should pass undefined if the argument is not supplied', function (done) {
var called = false;
var method = givenSharedPrototypeMethod(
function bar(a, cb) {
called = true;
assert(a === undefined, 'a should be undefined');
cb();
},
{
accepts: [
{ arg: 'b', type: 'number' }
]
}
);
objects.invoke(method.name, [39], [], function(err) {
assert(called);
done();
});
});
it('should allow arguments in the body', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
var obj = {
foo: 'bar'
};
objects.invoke(method.name, [39], [obj], function(err, data) {
expect(obj).to.deep.equal(data);
done();
});
});
it('should allow arguments in the body with date', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, cb) {
cb(null, a);
},
{
accepts: [
{ arg: 'a', type: 'object', http: {source: 'body' } }
],
returns: { arg: 'data', type: 'object', root: true },
http: { path: '/' }
}
);
var data = {date: {$type: 'date', $data: new Date()}};
objects.invoke(method.name, [39], [data], function(err, resData) {
expect(resData).to.deep.equal({date: data.date.$data.toISOString()});
done();
});
});
it('should allow arguments in the form', function(done) {
var method = givenSharedPrototypeMethod(
function bar(a, b, cb) {
cb(null, Number(this.id) + a + b);
},
{
accepts: [
{ arg: 'b', type: 'number', http: {source: 'form' } },
{ arg: 'a', type: 'number', http: {source: 'form' } }
],
returns: { arg: 'n', type: 'number' },
http: { path: '/' }
}
);
objects.invoke(method.name, [39], [1, 2], function(err, n) {
assert.equal(n, 42);
done();
});
});
it('should respond with correct args if returns has multiple args', function(done) {
var method = givenSharedPrototypeMethod(
function(a, b, cb) {
cb(null, this.id, a, b);
},
{
accepts: [
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
],
returns: [
{ arg: 'id', type: 'string' },
{ arg: 'a', type: 'number' },
{ arg: 'b', type: 'number' }
]
}
);
objects.invoke(method.name, ['39'], [1, 2], function(err, id, a, b) {
assert.equal(id, '39');
assert.equal(a, 1);
assert.equal(b, 2);
done();
});
});
describe('uncaught errors', function () {
it('should return 500 if an error object is thrown', function (done) {
var errMsg = 'an error';
var method = givenSharedPrototypeMethod(
function(a, b, cb) {
throw new Error(errMsg);
}
);
objects.invoke(method.name, ['39'], function(err) {
assert(err instanceof Error);
assert.equal(err.message, errMsg);
done();
});
});
});
});
});
function givenSharedStaticMethod(fn, config) {
if (typeof fn === 'object' && config === undefined) {
config = fn;
fn = null;
}
fn = fn || function(cb) { cb(); };
remotes.testClass = { testMethod: fn };
config = extend({ shared: true }, config);
extend(remotes.testClass.testMethod, config);
return {
name: 'testClass.testMethod',
url: '/testClass/testMethod',
classUrl: '/testClass'
};
}
function givenSharedPrototypeMethod(fn, config) {
if (typeof fn === 'object' && config === undefined) {
config = fn;
fn = undefined;
}
fn = fn || function(cb) { cb(); };
remotes.testClass = factory.createSharedClass();
remotes.testClass.prototype.testMethod = fn;
config = extend({ shared: true }, config);
extend(remotes.testClass.prototype.testMethod, config);
return {
name: 'testClass.prototype.testMethod',
getClassUrlForId: function(id) {
return '/testClass/' + id;
},
getUrlForId: function(id) {
return this.getClassUrlForId(id) + '/testMethod';
},
url: '/testClass/an-id/testMethod',
ctor: remotes.testClass
};
}
function expectErrorResponseContaining(keyValues, excludedKeyValues, done) {
if(done === undefined && typeof excludedKeyValues === 'function') {
done = excludedKeyValues;
excludedKeyValues = {};
}
return function(err, resp) {
if (err) return done(err);
for (var prop in keyValues) {
expect(resp.body.error).to.have.property(prop, keyValues[prop]);
}
for (var i = 0, n = excludedKeyValues.length; i < n; i++) {
expect(resp.body.error).to.not.have.property(excludedKeyValues[i]);
}
done();
};
}
it('should skip the super class and only expose user defined remote methods',
function (done) {
function base() {
}
function foo() {
}
foo.bar = function() {
};
foo.bar.shared = true;
inherits(foo, base);
base.shared = true;
foo.shared = true;
foo.sharedCtor = function() {};
remotes.foo = foo;
var methodNames = [];
var methods = objects.methods();
for (var i = 0; i < methods.length; i++) {
methodNames.push(methods[i].stringName);
}
expect(methodNames).not.to.contain('super_');
expect(methodNames).to.contain('foo.bar');
expect(methodNames.length).to.equal(1);
done();
});
});