四好公路
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

632 lines
19 KiB

'use strict';
var Promise = require('bluebird'),
request = require('request'),
expect = require('chai').expect,
_ = require('lodash'),
rest = require('../../lib'),
test = require('../support'),
errors = require('../../lib/Errors'),
RequestCompleted = errors.RequestCompleted,
EpilogueError = errors.EpilogueError;
describe('Milestones', function() {
before(function() {
test.models.User = test.db.define('users', {
id: { type: test.Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
username: { type: test.Sequelize.STRING, unique: true },
email: { type: test.Sequelize.STRING, unique: true, validate: { isEmail: true } }
}, {
underscored: true,
timestamps: false
});
});
beforeEach(function() {
return Promise.all([ test.initializeDatabase(), test.initializeServer() ])
.then(function() {
rest.initialize({
app: test.app,
sequelize: test.Sequelize
});
test.userResource = rest.resource({
model: test.models.User,
endpoints: ['/users', '/users/:id']
});
});
});
afterEach(function() {
return test.clearDatabase()
.then(function() { return test.closeServer(); })
.then(function() {
delete test.userResource;
});
});
// TESTS
describe('context.continue', function() {
var ContinueMiddleware;
beforeEach(function() {
ContinueMiddleware = {
results: {
beforeCalled: false,
actionCalled: false
},
create: {
write: {
action: function(req, res, context) {
ContinueMiddleware.results.actionCalled = true;
context.continue();
}
}
}
};
});
function checkContinued(done) {
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(ContinueMiddleware.results.beforeCalled).to.be.true;
expect(ContinueMiddleware.results.actionCalled).to.be.true;
done();
});
}
it('should support running as a function', function(done) {
ContinueMiddleware.create.write.before = function(req, res, context) {
ContinueMiddleware.results.beforeCalled = true;
context.continue();
};
test.userResource.use(ContinueMiddleware);
checkContinued(done);
});
it('should support a return value', function(done) {
ContinueMiddleware.create.write.before = function(req, res, context) {
ContinueMiddleware.results.beforeCalled = true;
return context.continue;
};
test.userResource.use(ContinueMiddleware);
checkContinued(done);
});
it('should support returning a promise', function(done) {
ContinueMiddleware.create.write.before = function(req, res, context) {
ContinueMiddleware.results.beforeCalled = true;
return new Promise(function(resolve) {
resolve(context.continue);
});
};
test.userResource.use(ContinueMiddleware);
checkContinued(done);
});
});
describe('context.stop', function() {
var StopMiddleware;
beforeEach(function() {
StopMiddleware = {
results: {
beforeCalled: false,
actionCalled: false
},
create: {
write: {
action: function(req, res, context) {
StopMiddleware.results.actionCalled = true;
context.continue();
}
}
}
};
});
function checkStopped(done) {
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(StopMiddleware.results.beforeCalled).to.be.true;
expect(StopMiddleware.results.actionCalled).to.be.false;
expect(response.statusCode).to.be.eql(420);
done();
});
}
function setResponse(res) {
res.status(420);
res.json({test: 'test'});
}
it('should support running as a function', function(done) {
StopMiddleware.create.write.before = function(req, res, context) {
StopMiddleware.results.beforeCalled = true;
setResponse(res);
context.stop();
};
test.userResource.use(StopMiddleware);
checkStopped(done);
});
it('should support a return value', function(done) {
StopMiddleware.create.write.before = function(req, res, context) {
StopMiddleware.results.beforeCalled = true;
setResponse(res);
return context.stop;
};
test.userResource.use(StopMiddleware);
checkStopped(done);
});
it('should support returning a promise', function(done) {
StopMiddleware.create.write.before = function(req, res, context) {
StopMiddleware.results.beforeCalled = true;
return new Promise(function(resolve) {
setResponse(res);
resolve(context.stop);
});
};
test.userResource.use(StopMiddleware);
checkStopped(done);
});
it('should support thowing RequestCompleted error', function(done) {
StopMiddleware.create.write.before = function(req, res, context) {
StopMiddleware.results.beforeCalled = true;
setResponse(res);
throw new RequestCompleted();
};
test.userResource.use(StopMiddleware);
checkStopped(done);
});
});
describe('context.skip', function() {
var SkipMiddleware;
beforeEach(function() {
SkipMiddleware = {
results: {
beforeCalled: false,
actionCalled: false,
sendCalled: false
},
create: {
write: {
action: function(req, res, context) {
SkipMiddleware.results.actionCalled = true;
context.continue();
}
},
send: {
action: function(req, res, context) {
SkipMiddleware.results.sendCalled = true;
context.continue();
}
}
}
};
});
function checkSkipped(done) {
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(SkipMiddleware.results.beforeCalled).to.be.true;
expect(SkipMiddleware.results.actionCalled).to.be.false;
expect(SkipMiddleware.results.sendCalled).to.be.true;
done();
});
}
it('should support running as a function', function(done) {
SkipMiddleware.create.write.before = function(req, res, context) {
SkipMiddleware.results.beforeCalled = true;
context.skip();
};
test.userResource.use(SkipMiddleware);
checkSkipped(done);
});
it('should support a return value', function(done) {
SkipMiddleware.create.write.before = function(req, res, context) {
SkipMiddleware.results.beforeCalled = true;
return context.skip;
};
test.userResource.use(SkipMiddleware);
checkSkipped(done);
});
it('should support returning a promise', function(done) {
SkipMiddleware.create.write.before = function(req, res, context) {
SkipMiddleware.results.beforeCalled = true;
return new Promise(function(resolve) {
resolve(context.skip);
});
};
test.userResource.use(SkipMiddleware);
checkSkipped(done);
});
});
describe('throwing errors', function() {
var ErrorMiddleware, error;
beforeEach(function () {
ErrorMiddleware = {
results: {
beforeCalled: false,
actionCalled: false
},
create: {
write: {
action: function (req, res, context) {
ErrorMiddleware.results.actionCalled = true;
context.continue();
}
}
}
};
error = new EpilogueError(420, 'test error', ['test', 'error']);
});
function checkErrored(done) {
request.post({
url: test.baseUrl + '/users',
json: {username: 'jamez', email: 'jamez@gmail.com'}
}, function (err, response, body) {
expect(ErrorMiddleware.results.beforeCalled).to.be.true;
expect(ErrorMiddleware.results.actionCalled).to.be.false;
expect(response.statusCode).to.be.eql(error.status);
expect(body.message).to.be.eql(error.message);
expect(body.errors).to.be.eql(error.errors);
done();
});
}
it('should support throw in sync', function (done) {
ErrorMiddleware.create.write.before = function (req, res, context) {
ErrorMiddleware.results.beforeCalled = true;
throw error;
};
test.userResource.use(ErrorMiddleware);
checkErrored(done);
});
it('should support throwing in a promise', function (done) {
ErrorMiddleware.create.write.before = function (req, res, context) {
ErrorMiddleware.results.beforeCalled = true;
return new Promise(function (resolve) {
throw error;
});
};
test.userResource.use(ErrorMiddleware);
checkErrored(done);
});
it('should support context.error function when in a callback', function (done) {
ErrorMiddleware.create.write.before = function (req, res, context) {
ErrorMiddleware.results.beforeCalled = true;
setTimeout(function() {
context.error(error);
}, 200);
};
test.userResource.use(ErrorMiddleware);
checkErrored(done);
});
it('should support context.error function with error constructor arguments', function (done) {
ErrorMiddleware.create.write.before = function (req, res, context) {
ErrorMiddleware.results.beforeCalled = true;
context.error(420, 'test error', ['test', 'error']);
};
test.userResource.use(ErrorMiddleware);
checkErrored(done);
});
});
describe('Errors', function() {
it('should allow error messages to be changed before send', function(done) {
var expectedBody = 'Expected Body';
test.userResource.controllers.create.error = function(req, res, err) {
res.status(400);
res.send(expectedBody);
};
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'totally gonna fail email validation' }
}, function(err, response, body) {
expect(body).to.equal(expectedBody);
expect(response.statusCode).to.equal(400);
done();
});
});
});
describe('general behavior', function() {
it('should not skip the before action of next milestone if an after resolved to skip', function(done) {
var SkipMiddleware = {
results: {
beforeCalled: false,
afterCalled: false
},
create: {
write: {
after: function(req, res, context) {
SkipMiddleware.results.afterCalled = true;
return context.skip;
}
},
send: {
before: function(req, res, context) {
SkipMiddleware.results.beforeCalled = true;
return context.continue;
}
}
}
};
test.userResource.use(SkipMiddleware);
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(SkipMiddleware.results.afterCalled).to.be.true;
expect(SkipMiddleware.results.beforeCalled).to.be.true;
done();
});
});
it('should skip the action and after hooks if skip is returned in before hook', function(done) {
var SkipMiddleware = {
results: {
beforeCalled: false,
actionCalled: false,
afterCalled: false
},
create: {
write: {
before: function(req, res, context) {
SkipMiddleware.results.beforeCalled = true;
return context.skip;
},
action: function(req, res, context) {
SkipMiddleware.results.actionCalled = true;
return context.continue;
},
after: function(req, res, context) {
SkipMiddleware.results.afterCalled = true;
return context.continue;
}
}
}
};
test.userResource.use(SkipMiddleware);
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(SkipMiddleware.results.beforeCalled).to.be.true;
expect(SkipMiddleware.results.actionCalled).to.be.false;
expect(SkipMiddleware.results.afterCalled).to.be.false;
done();
});
});
it('should throw an error on invalid milestone definition', function() {
try {
test.userResource.controllers.create.milestone('stert', function(req, res, context) {
// no-op
});
} catch (error) {
expect(error.message).to.equal('invalid milestone: stert');
}
});
});
describe('start', function() {
// Run at the beginning of the request. Defaults to passthrough.
it('should support chaining', function(done) {
var startCount;
test.userResource.read.start(function(req, res, context) {
startCount = 1;
return context.continue;
});
test.userResource.read.start(function(req, res, context) {
startCount++;
return context.continue;
});
request.get({ url: test.baseUrl + '/users/1' }, function(err, response, body) {
expect(startCount).to.equal(2);
done();
});
});
});
describe('auth', function() {
// Authorize the request. Defaults to passthrough.
});
describe('fetch', function() {
// Fetch data from the database for non-create actions according to context.criteria, writing to context.instance.
it('should support overriding data for create before fetch', function(done) {
var mockData = { username: 'mocked', email: 'mocked@gmail.com' };
test.userResource.read.fetch.before(function(req, res, context) {
context.instance = mockData;
return context.skip;
});
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(err).to.be.null;
expect(response.statusCode).to.equal(201);
var path = response.headers.location;
request.get({ url: test.baseUrl + path }, function(err, response, body) {
var record = _.isObject(body) ? body : JSON.parse(body);
delete record.id;
expect(response.statusCode).to.equal(200);
expect(record).to.eql(mockData);
done();
});
});
});
it('should support modifying data for create after fetch', function(done) {
var expected = { username: 'jamez', email: 'injected@email.com' };
test.userResource.read.fetch.after(function(req, res, context) {
context.instance.email = 'injected@email.com';
return context.skip;
});
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(err).to.be.null;
expect(response.statusCode).to.equal(201);
var path = response.headers.location;
request.get({ url: test.baseUrl + path }, function(err, response, body) {
var record = _.isObject(body) ? body : JSON.parse(body);
delete record.id;
expect(response.statusCode).to.equal(200);
expect(record).to.eql(expected);
done();
});
});
});
it('should support custom options for before fetch', function(done) {
var expected = { username: 'jamez' };
test.userResource.read.fetch.before(function(req, res, context) {
context.options.attributes = ['username'];
return context.continue;
});
request.post({
url: test.baseUrl + '/users',
json: {username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(err).to.be.null;
expect(response.statusCode).to.equal(201);
var path = response.headers.location;
request.get({ url: test.baseUrl + path }, function(err, response, body) {
var record = _.isObject(body) ? body : JSON.parse(body);
delete record.id;
expect(response.statusCode).to.equal(200);
expect(record).to.eql(expected);
done();
});
});
});
it('should respect modified criteria before fetch (aliasing)', function(done) {
test.userResource.read.fetch.before(function(req, res, context) {
if (req.params.id === 'me') {
context.criteria.id = 3;
}
return context.continue;
});
return test.models.User.bulkCreate([
{ username: 'tom', email: 'tom@gmail.com' },
{ username: 'dick', email: 'dick@gmail.com' },
{ username: 'harry', email: 'harry@gmail.com' },
])
.then(function() {
request.get({ url: test.baseUrl + '/users/me' }, function(err, response, body) {
var record = _.isObject(body) ? body : JSON.parse(body);
delete record.id;
expect(response.statusCode).to.equal(200);
expect(record).to.eql({ username: 'harry', email: 'harry@gmail.com' });
done();
});
});
});
});
describe('data', function() {
// Transform the data from the database if needed. Defaults to passthrough.
});
describe('write', function() {
// Write to the database for actions that write, reading from context.attributes.
it('should support throwing a custom error before write', function(done) {
test.userResource.create.write.before(function(req, res, context) {
throw new errors.BadRequestError("just fail please");
});
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(err).to.be.null;
expect(response.statusCode).to.equal(400);
expect(response.body.message).to.equal('just fail please');
done();
});
});
it('should support a custom error in context.error in before write', function(done) {
test.userResource.create.write.before(function(req, res, context) {
return context.error(new errors.BadRequestError("just fail please"));
});
request.post({
url: test.baseUrl + '/users',
json: { username: 'jamez', email: 'jamez@gmail.com' }
}, function(err, response, body) {
expect(err).to.be.null;
expect(response.statusCode).to.equal(400);
expect(response.body.message).to.equal('just fail please');
done();
});
});
});
describe('send', function() {
// Send the HTTP response, headers along with the data in context.instance.
});
describe('complete', function() {
// Run the specified function when the request is complete, regardless of the status of the response.
});
});