UNPKG

rested

Version:

{REST}ed; {REST}ful Enterprise Data-as-a-Service (DaaS)

441 lines (392 loc) 17.5 kB
const supertest = require('supertest'); const mongoose = require('mongoose'); const rested = require('..'); const fixture = require('./fixtures/controller'); describe('Controllers', () => { beforeAll(fixture.init); afterAll(fixture.deinit); beforeEach(fixture.create); const request = () => supertest(fixture.app()); it('should allow passing string name to create', () => { let makeController = () => rested.Controller('unmade'); expect(makeController).not.toThrow(); }); it('should allow passing a model to create', () => { let makeController = () => rested.Controller(mongoose.model('unmade')); expect(makeController).not.toThrow(); }); it('should not allow leaving off arguments to create', () => { let makeController = () => rested.Controller(); expect(makeController).toThrow(/You must pass in a model or model name/); }); it('should not allow weird arguments to create', () => { let makeController = () => rested.Controller({}); expect(makeController).toThrow(/You must pass in a model or model name/); }); it('should have methods set by default', () => { let controller; let makeController = () => controller = rested.Controller('unmade'); expect(makeController).not.toThrow(); expect(controller.methods()).toEqual(['head', 'get', 'put', 'post', 'delete']); }); it('should support select options for GET requests', () => request().get('/api/cheeses') .query({ sort: 'name' }) .expect('Content-Type', /json/) .expect(200) .then(({ body }) => { expect(body).toHaveLength(3); expect(body[1]).toEqual({ name: 'Cheddar', color: 'Yellow' }); })); it('should allow deselecting', () => request().get('/api/liens') .expect('Content-Type', /json/) .expect(200) .then(({ body }) => { expect(body[0]).toHaveProperty('_id'); expect(body[0]).toHaveProperty('__v'); expect(body[0]).not.toHaveProperty('title'); })); it('should allow deselecting hyphenated field names', () => request().get('/api/stores') .expect('Content-Type', /json/) .expect(200) .then(({ body }) => { expect(body[0]).toHaveProperty('_id'); expect(body[0]).toHaveProperty('__v'); expect(body[0]).not.toHaveProperty('hpyhenated-field-name'); expect(body[0]).not.toHaveProperty('voltaic'); })); it('should support select options for POST requests', () => request().post('/api/cheeses') .send({ name: 'Gorgonzola', color: 'Green' }) .expect('Content-Type', /json/) .expect(201) .then(({ body }) => { expect(body).toHaveProperty('color', 'Green'); expect(body).toHaveProperty('name', 'Gorgonzola'); expect(body).not.toHaveProperty('_id'); expect(body).not.toHaveProperty('cave'); })); it('should support select options for PUT requests', () => request().put('/api/cheeses/Cheddar') .send({ color: 'White' }) .expect('Content-Type', /json/) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('color', 'White'); expect(body).toHaveProperty('name', 'Cheddar'); expect(body).not.toHaveProperty('_id'); expect(body).not.toHaveProperty('cave'); })); it('should allow POSTing when fields are deselected (issue #67)', () => request().post('/api/stores') .send({ name: "Lou's" }) .expect(201) .then(({ body }) => { expect(body).toHaveProperty('_id'); expect(body).toHaveProperty('__v'); expect(body).toHaveProperty('name', "Lou's"); })); it('should support finding documents with custom findBy field', () => request().get('/api/cheeses/Camembert') .expect(200) .then(({ body }) => expect(body).toHaveProperty('color', 'White') )); xit('should disallow adding a non-unique findBy field', () => { let makeController = () => rested.Controller('cheese').findBy('color'); expect(makeController).toThrow(/^`findBy` path for model "cheese" must be unique$/); }); it('should allow adding a uniqe findBy field 1', () => { let makeController = () => { let rab = new mongoose.Schema({ 'arb': { type: String, unique: true } }); mongoose.model('rab', rab); rested.Controller('rab').findBy('arb'); }; expect(makeController).not.toThrow(); }); it('should allow adding a unique findBy field 2', () => { let makeController = () => { let barb = new mongoose.Schema({ 'arb': { type: String, index: { unique: true } } }); mongoose.model('barb', barb); rested.Controller('barb').findBy('arb'); }; expect(makeController).not.toThrow(); }); it('should allow adding arbitrary routes', () => request().get('/api/stores/info') .expect(200, '"OK!"')); it('should allow adding arbitrary routes with params', () => request().get('/api/stores/XYZ/arbitrary') .expect(200) .then(({ body }) => expect(body).toEqual('XYZ') )); it('should still allow using routes when adding arbitrary routes', () => request().get('/api/stores') .query({ select: '-_id -__v', sort: 'name' }) .expect(200) .then(({ body }) => expect(body).toEqual([{ name: 'Corner' }, { name: 'Westlake' }]) )); it('should allow using middleware', () => request().del('/api/stores') .expect(200) .then(({ headers }) => expect(headers['x-poncho']).toEqual('Poncho!') )); it('should allow using middleware mounted at a path', () => request().post('/api/stores/binfo') .expect(200) .then(({ body }) => expect(body).toEqual('Poncho!') )); it('should disallow unrecognized verbs', () => { let controller = rested.Controller('store'); let register = () => controller.request('get dude', () => { }); expect(register).toThrow(/^Unrecognized HTTP method: "dude"$/); }); it('should disallow unrecognized howManys', () => { let controller = rested.Controller('store'); let register = () => controller.request('gargoyle', 'get put', () => { }); expect(register).toThrow(/^End-point type must be either "instance" or "collection," not "gargoyle"$/); }); it('should allow specifying instance or collection middleware', () => { let controller = rested.Controller('store'); let register = () => { controller.request('collection', 'get put head delete post', () => { }); controller.request('instance', 'get put head delete post', () => { }); }; expect(register).not.toThrow(); }); it('should allow registering query middleware for other verbs', () => { let controller = rested.Controller('store'); let register = () => controller.query('get put head delete', () => { }); expect(register).not.toThrow(); }); it('should allow registering POST middleware for other stages', () => { let controller = rested.Controller('store'); let register = () => { controller.request('post', () => { }); controller.query('post', () => { }); }; expect(register).not.toThrow(); }); it('should correctly set the deselected paths property', () => { let doozle = new mongoose.Schema({ a: { type: String, select: false }, b: String, c: String, d: String }); mongoose.model('doozle', doozle); let controller = rested.Controller('doozle').select('-d c -a b'); expect(controller.deselected()).toEqual(['a', 'd']); }); it('should disallow push mode by default', () => request().put('/api/stores/Westlake') .send({ molds: 'penicillium roqueforti', __v: 0 }) .set('Update-Operator', '$push') .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'The requested update operator "$push" is not enabled for this resource') )); it('should disallow pushing to non-whitelisted paths', () => request().put('/api/cheeses/Huntsman') .set('Update-Operator', '$push') .send({ 'favorite nes game': 'bubble bobble' }) .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'This update path is forbidden for the requested update operator "$push"') )); it("should allow pushing to an instance document's whitelisted arrays when $push mode is enabled", () => request().put('/api/cheeses/Huntsman?select=molds') .set('Update-Operator', '$push') .send({ molds: 'penicillium roqueforti' }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('molds'); expect(body.molds).toHaveProperty('length', 1); expect(body.molds).toEqual(['penicillium roqueforti']); })); it('should disallow $pull mode by default', () => request().put('/api/stores/Westlake') .set('Update-Operator', '$pull') .send({ molds: 'penicillium roqueforti', __v: 0 }) .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'The requested update operator "$pull" is not enabled for this resource') )); it('should disallow pulling non-whitelisted paths', () => request().put('/api/cheeses/Huntsman') .set('Update-Operator', '$pull') .send({ 'favorite nes game': 'bubble bobble' }) .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'This update path is forbidden for the requested update operator "$pull"') )); it("should allow pulling from an instance document's whitelisted arrays when $pull mode is enabled", () => request().put('/api/cheeses/Huntsman?select=molds') .set('Update-Operator', '$push') .send({ molds: 'penicillium roqueforti' }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('molds'); expect(body.molds).toHaveProperty('length', 1); expect(body.molds).toEqual(['penicillium roqueforti']); }) .then(() => request().put('/api/cheeses/Huntsman?select=molds') .set('Update-Operator', '$pull') .send({ molds: 'penicillium roqueforti' }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('molds'); expect(body.molds).toHaveProperty('length', 0); }))); it('should disallow push mode by default', () => request().put('/api/stores/Westlake') .set('Update-Operator', '$set') .send({ molds: 'penicillium roqueforti', __v: 0 }) .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'The requested update operator "$set" is not enabled for this resource') )); it('should disallow setting non-whitelisted paths', () => request().put('/api/cheeses/Huntsman') .set('Update-Operator', '$set') .send({ 'favorite nes game': 'bubble bobble' }) .expect(403) .then(({ body }) => expect(body).toHaveProperty('message', 'This update path is forbidden for the requested update operator "$set"') )); it("should allow setting an instance document's whitelisted paths when $set mode is enabled", () => request().put('/api/cheeses/Huntsman?select=molds') .set('Update-Operator', '$set') .send({ molds: ['penicillium roqueforti'] }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('molds'); expect(body.molds).toHaveProperty('length', 1); expect(body.molds).toEqual(['penicillium roqueforti']); })); it("should allow pushing to embedded arrays using positional $", () => request().put('/api/cheeses/Camembert?select=arbitrary') .set('Update-Operator', '$push') .query({ conditions: JSON.stringify({ 'arbitrary.goat': true }) }) .send({ 'arbitrary.$.llama': 5 }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('arbitrary'); expect(body.arbitrary).toHaveProperty('length', 2); expect(body.arbitrary[0]).toHaveProperty('llama'); expect(body.arbitrary[0].llama).toHaveProperty('length', 3); /* .expect(3).then(({ body }) => { .expect(4).then(({ body }) => { .expect(5).then(({ body }) => { expect(body.arbitrary[1].llama).toHaveProperty('length', 2); .expect(1).then(({ body }) => { .expect(2).then(({ body }) => { */ })); it("should allow setting embedded fields using positional $", () => request().put('/api/cheeses/Camembert?select=arbitrary') .set('Update-Operator', '$set') .query({ conditions: JSON.stringify({ 'arbitrary.goat': false }) }) .send({ 'arbitrary.$.champagne': 'extra dry' }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('arbitrary'); expect(body.arbitrary).toHaveProperty('length', 2); expect(body.arbitrary[0]).not.toHaveProperty('champagne'); expect(body.arbitrary[1]).toHaveProperty('champagne', 'extra dry'); })); it("should allow pulling from embedded fields using positional $", () => request().put('/api/cheeses/Camembert?select=arbitrary') .set('Update-Operator', '$pull') .query({ conditions: JSON.stringify({ 'arbitrary.goat': true }) }) .send({ 'arbitrary.$.llama': 3 }) .expect(200) .then(({ body }) => { expect(body).toHaveProperty('arbitrary'); expect(body.arbitrary).toHaveProperty('length', 2); expect(body.arbitrary[0]).toHaveProperty('llama'); expect(body.arbitrary[0].llama).toHaveProperty('length', 1); /* .expect(4).then(({ body }) => { expect(body.arbitrary[1].llama).toHaveProperty('length', 2); .expect(1).then(({ body }) => { .expect(2).then(({ body }) => { */ })); it('should send 405 when a verb is disabled (GET)', () => request().get('/api/beans') .expect(405) .then(({ headers, body }) => { expect(headers).toHaveProperty('allow', 'HEAD,POST,PUT,DELETE'); expect(body).toHaveProperty('message', 'The requested method has been disabled for this resource'); })); it('should send 405 when a verb is disabled (DELETE)', () => request().del('/api/liens') .expect(405) .then(({ headers, body }) => { expect(headers).toHaveProperty('allow', 'HEAD,GET,POST,PUT'); expect(body).toHaveProperty('message', 'The requested method has been disabled for this resource'); })); it('should return a 400 when ID malformed (not ObjectID)', () => request().get('/api/beans/bad') .expect(400) .then(({ body }) => expect(body).toHaveProperty('message', 'The requested document ID "bad" is not a valid document ID') )); it('should return a 400 when ID malformed (not Number)', () => request().get('/api/deans/0booze') .expect(400) .then(({ body }) => expect(body).toHaveProperty('message', 'The requested document ID "0booze" is not a valid document ID') )); it('should allow setting path different from model name', () => request().get('/api/baloo/?sort=name') .expect(200) .then(({ body }) => expect(body).toHaveLength(2) )); it('should allow setting model independently of name', () => request().get('/api/timeentries/Camembert') .expect(200) .then(({ body }) => expect(body).toHaveProperty('color', 'White') )); it('should handle unique key error as a validation error', () => request().post('/api/cheeses') .send({ name: 'Gorgonzola', color: 'Green' }) .expect(201) .then(() => request().post('/api/cheeses') .send({ name: 'Gorgonzola', color: 'Green' }) .expect(422) .then(({ body }) => { expect(body).toHaveProperty('type', 'unique'); expect(body).toHaveProperty('name', 'MongoError'); expect(body).toHaveProperty('value', 'Gorgonzola'); expect(body).toHaveProperty('path', 'name', '???'); expect(body).toHaveProperty('message', 'Path `name` (Gorgonzola) must be unique.', 'Path `???` (Gorgonzola) must be unique.'); expect(body).toHaveProperty('originalMessage'); expect(body.originalMessage).toMatch(/dup key/); expect(body.originalMessage).toMatch(/"Gorgonzola"/); expect(body.originalMessage).toMatch(/E11000 duplicate key/); }) )); it('should not handle errors if disabled', () => request().post('/api-no-error-handler/geese') .send({ name: 'Gorgonzola', color: 'Green' }) .expect(201) .then(() => request().post('/api-no-error-handler/geese') .send({ name: 'Gorgonzola', color: 'Green' }) .expect(422, /Unprocessable Entity/) )); it('should allow setting path apart from plural', () => request().get('/api/linseed.oil') .expect(200) .then(({ body }) => expect(body).toHaveProperty('length', 2) )); });