From 215b245122bf72e7f836a0f1f2c173eb33271d57 Mon Sep 17 00:00:00 2001 From: Sujesh T Date: Sat, 30 Nov 2019 15:22:47 +0530 Subject: [PATCH] feat: provide better error message The current hasone relation error message is not an appropriate one when there is no data. The 404 error code seems fine but message is not. So adds a better error message when the relation contains no data. prlint fixes --- lib/model.js | 26 +- test/relations.integration.js | 1611 ++++++++++++++++++--------------- 2 files changed, 883 insertions(+), 754 deletions(-) diff --git a/lib/model.js b/lib/model.js index 20c3aed2..2f19df3e 100644 --- a/lib/model.js +++ b/lib/model.js @@ -187,8 +187,10 @@ module.exports = function(registry) { const idDesc = ModelCtor.modelName + ' id'; ModelCtor.sharedCtor.accepts = [ - {arg: 'id', type: 'any', required: true, http: {source: 'path'}, - description: idDesc}, + { + arg: 'id', type: 'any', required: true, http: {source: 'path'}, + description: idDesc, + }, // {arg: 'instance', type: 'object', http: {source: 'body'}} {arg: 'options', type: 'object', http: createOptionsViaModelMethod}, ]; @@ -254,7 +256,7 @@ module.exports = function(registry) { )); console.warn(g.f( 'Please rework your app to use the offical solution for injecting ' + - '"options" argument from request context,\nsee %s', + '"options" argument from request context,\nsee %s', 'http://loopback.io/doc/en/lb3/Using-current-context.html', )); } @@ -385,7 +387,7 @@ module.exports = function(registry) { method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + - '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } @@ -565,7 +567,9 @@ module.exports = function(registry) { if (ctx.result !== null) return cb(); const fk = ctx.getArgByName('fk'); - const msg = g.f('Unknown "%s" id "%s".', toModelName, fk); + const msg = fk ? + g.f('Unknown "%s" id "%s".', toModelName, fk) : + g.f('No "%s" instance(s) found', toModelName); const error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; @@ -676,10 +680,12 @@ module.exports = function(registry) { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ - {arg: 'fk', type: 'any', + { + arg: 'fk', type: 'any', description: g.f('Foreign key for %s', relationName), required: true, - http: {source: 'path'}}, + http: {source: 'path'}, + }, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], @@ -704,10 +710,12 @@ module.exports = function(registry) { define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, - accepts: [{arg: 'fk', type: 'any', + accepts: [{ + arg: 'fk', type: 'any', description: g.f('Foreign key for %s', relationName), required: true, - http: {source: 'path'}}, + http: {source: 'path'}, + }, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), diff --git a/test/relations.integration.js b/test/relations.integration.js index 234e0a5f..95205481 100644 --- a/test/relations.integration.js +++ b/test/relations.integration.js @@ -20,11 +20,14 @@ describe('relations - integration', function() { lt.beforeEach.givenModel('store'); beforeEach(function(done) { this.widgetName = 'foo'; - this.store.widgets.create({ - name: this.widgetName, - }, function() { - done(); - }); + this.store.widgets.create( + { + name: this.widgetName, + }, + function() { + done(); + }, + ); }); afterEach(function(done) { this.app.models.widget.destroyAll(done); @@ -34,45 +37,51 @@ describe('relations - integration', function() { before(function defineProductAndCategoryModels() { const Team = app.registry.createModel('Team', {name: 'string'}); const Reader = app.registry.createModel('Reader', {name: 'string'}); - const Picture = app.registry.createModel('Picture', - {name: 'string', imageableId: 'number', imageableType: 'string'}); + const Picture = app.registry.createModel('Picture', { + name: 'string', + imageableId: 'number', + imageableType: 'string', + }); app.model(Team, {dataSource: 'db'}); app.model(Reader, {dataSource: 'db'}); app.model(Picture, {dataSource: 'db'}); - Reader.hasMany(Picture, {polymorphic: { // alternative syntax - as: 'imageable', // if not set, default to: reference - foreignKey: 'imageableId', // defaults to 'as + Id' - discriminator: 'imageableType', // defaults to 'as + Type' - }}); + Reader.hasMany(Picture, { + polymorphic: { + // alternative syntax + as: 'imageable', // if not set, default to: reference + foreignKey: 'imageableId', // defaults to 'as + Id' + discriminator: 'imageableType', // defaults to 'as + Type' + }, + }); - Picture.belongsTo('imageable', {polymorphic: { - foreignKey: 'imageableId', - discriminator: 'imageableType', - }}); + Picture.belongsTo('imageable', { + polymorphic: { + foreignKey: 'imageableId', + discriminator: 'imageableType', + }, + }); Reader.belongsTo(Team); }); before(function createEvent(done) { const test = this; - app.models.Team.create({name: 'Team 1'}, - function(err, team) { + app.models.Team.create({name: 'Team 1'}, function(err, team) { + if (err) return done(err); + + test.team = team; + app.models.Reader.create({name: 'Reader 1'}, function(err, reader) { if (err) return done(err); - test.team = team; - app.models.Reader.create({name: 'Reader 1'}, - function(err, reader) { - if (err) return done(err); - - test.reader = reader; - reader.pictures.create({name: 'Picture 1'}); - reader.pictures.create({name: 'Picture 2'}); - reader.team(test.team); - reader.save(done); - }); + test.reader = reader; + reader.pictures.create({name: 'Picture 1'}); + reader.pictures.create({name: 'Picture 2'}); + reader.team(test.team); + reader.save(done); }); + }); }); after(function(done) { @@ -82,14 +91,24 @@ describe('relations - integration', function() { it('includes the related child model', function(done) { const url = '/api/readers/' + this.reader.id; this.get(url) - .query({'filter': {'include': 'pictures'}}) + .query({filter: {include: 'pictures'}}) .expect(200, function(err, res) { if (err) return done(err); expect(res.body.name).to.be.equal('Reader 1'); expect(res.body.pictures).to.be.eql([ - {name: 'Picture 1', id: 1, imageableId: 1, imageableType: 'Reader'}, - {name: 'Picture 2', id: 2, imageableId: 1, imageableType: 'Reader'}, + { + name: 'Picture 1', + id: 1, + imageableId: 1, + imageableType: 'Reader', + }, + { + name: 'Picture 2', + id: 2, + imageableId: 1, + imageableType: 'Reader', + }, ]); done(); @@ -99,13 +118,17 @@ describe('relations - integration', function() { it('includes the related parent model', function(done) { const url = '/api/pictures'; this.get(url) - .query({'filter': {'include': 'imageable'}}) + .query({filter: {include: 'imageable'}}) .expect(200, function(err, res) { if (err) return done(err); expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); - expect(res.body[0].imageable).to.be.eql({name: 'Reader 1', id: 1, teamId: 1}); + expect(res.body[0].imageable).to.be.eql({ + name: 'Reader 1', + id: 1, + teamId: 1, + }); done(); }); @@ -114,17 +137,24 @@ describe('relations - integration', function() { it('includes related models scoped to the related parent model', function(done) { const url = '/api/pictures'; this.get(url) - .query({'filter': {'include': { - 'relation': 'imageable', - 'scope': {'include': 'team'}, - }}}) + .query({ + filter: { + include: { + relation: 'imageable', + scope: {include: 'team'}, + }, + }, + }) .expect(200, function(err, res) { if (err) return done(err); expect(res.body[0].name).to.be.equal('Picture 1'); expect(res.body[1].name).to.be.equal('Picture 2'); expect(res.body[0].imageable.name).to.be.eql('Reader 1'); - expect(res.body[0].imageable.team).to.be.eql({name: 'Team 1', id: 1}); + expect(res.body[0].imageable.team).to.be.eql({ + name: 'Team 1', + id: 1, + }); done(); }); @@ -133,14 +163,13 @@ describe('relations - integration', function() { describe('/store/superStores', function() { it('should invoke scoped methods remotely', function(done) { - this.get('/api/stores/superStores') - .expect(200, function(err, res) { - if (err) return done(err); + this.get('/api/stores/superStores').expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.an('array'); + expect(res.body).to.be.an('array'); - done(); - }); + done(); + }); }); }); @@ -148,34 +177,40 @@ describe('relations - integration', function() { beforeEach(function() { this.url = '/api/stores/' + this.store.id + '/widgets'; }); - lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - }); - describe('widgets (response.body)', function() { - beforeEach(function() { - debug('GET /api/stores/:id/widgets response: %s' + - '\nheaders: %j\nbody string: %s', - this.res.statusCode, - this.res.headers, - this.res.text); - this.widgets = this.res.body; - this.widget = this.res.body && this.res.body[0]; + lt.describe.whenCalledRemotely( + 'GET', + '/api/stores/:id/widgets', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); }); - it('should be an array', function() { - assert(Array.isArray(this.widgets)); + describe('widgets (response.body)', function() { + beforeEach(function() { + debug( + 'GET /api/stores/:id/widgets response: %s' + + '\nheaders: %j\nbody string: %s', + this.res.statusCode, + this.res.headers, + this.res.text, + ); + this.widgets = this.res.body; + this.widget = this.res.body && this.res.body[0]; + }); + it('should be an array', function() { + assert(Array.isArray(this.widgets)); + }); + it('should include a single widget', function() { + assert(this.widgets.length === 1); + assert(this.widget); + }); + it('should be a valid widget', function() { + assert(this.widget.id); + assert.equal(this.widget.storeId, this.store.id); + assert.equal(this.widget.name, this.widgetName); + }); }); - it('should include a single widget', function() { - assert(this.widgets.length === 1); - assert(this.widget); - }); - it('should be a valid widget', function() { - assert(this.widget.id); - assert.equal(this.widget.storeId, this.store.id); - assert.equal(this.widget.name, this.widgetName); - }); - }); - }); + }, + ); describe('POST /api/store/:id/widgets', function() { beforeEach(function() { this.newWidgetName = 'baz'; @@ -186,14 +221,16 @@ describe('relations - integration', function() { beforeEach(function(done) { this.http = this.post(this.url, this.newWidget); this.http.send(this.newWidget); - this.http.end(function(err) { - if (err) return done(err); + this.http.end( + function(err) { + if (err) return done(err); - this.req = this.http.req; - this.res = this.http.response; + this.req = this.http.req; + this.res = this.http.response; - done(); - }.bind(this)); + done(); + }.bind(this), + ); }); it('should succeed with statusCode 200', function() { assert.equal(this.res.statusCode, 200); @@ -213,32 +250,39 @@ describe('relations - integration', function() { }); }); it('should have a single widget with storeId', function(done) { - this.app.models.widget.count({ - storeId: this.store.id, - }, function(err, count) { - if (err) return done(err); + this.app.models.widget.count( + { + storeId: this.store.id, + }, + function(err, count) { + if (err) return done(err); - assert.equal(count, 2); + assert.equal(count, 2); - done(); - }); + done(); + }, + ); }); }); describe('PUT /api/store/:id/widgets/:fk', function() { beforeEach(function(done) { const self = this; - this.store.widgets.create({ - name: this.widgetName, - }, function(err, widget) { - self.widget = widget; - self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; - done(); - }); + this.store.widgets.create( + { + name: this.widgetName, + }, + function(err, widget) { + self.widget = widget; + self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; + done(); + }, + ); }); it('does not add default properties to request body', function(done) { const self = this; - self.request.put(self.url) + self.request + .put(self.url) .send({active: true}) .end(function(err) { if (err) return done(err); @@ -255,88 +299,116 @@ describe('relations - integration', function() { describe('/stores/:id/widgets/:fk - 200', function() { beforeEach(function(done) { const self = this; - this.store.widgets.create({ - name: this.widgetName, - }, function(err, widget) { - self.widget = widget; - self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; + this.store.widgets.create( + { + name: this.widgetName, + }, + function(err, widget) { + self.widget = widget; + self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id; - done(); - }); - }); - lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - assert.equal(this.res.body.id, this.widget.id); - }); + done(); + }, + ); }); + lt.describe.whenCalledRemotely( + 'GET', + '/stores/:id/widgets/:fk', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + assert.equal(this.res.body.id, this.widget.id); + }); + }, + ); }); describe('/stores/:id/widgets/:fk - 404', function() { beforeEach(function() { this.url = '/api/stores/' + this.store.id + '/widgets/123456'; }); - lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function() { - it('should fail with statusCode 404', function() { - assert.equal(this.res.statusCode, 404); - assert.equal(this.res.body.error.statusCode, 404); - }); - }); + lt.describe.whenCalledRemotely( + 'GET', + '/stores/:id/widgets/:fk', + function() { + it('should fail with statusCode 404', function() { + assert.equal(this.res.statusCode, 404); + assert.equal(this.res.body.error.statusCode, 404); + }); + }, + ); }); describe('/store/:id/widgets/count', function() { beforeEach(function() { this.url = '/api/stores/' + this.store.id + '/widgets/count'; }); - lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - }); - it('should return the count', function() { - assert.equal(this.res.body.count, 1); - }); - }); + lt.describe.whenCalledRemotely( + 'GET', + '/api/stores/:id/widgets/count', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + }); + it('should return the count', function() { + assert.equal(this.res.body.count, 1); + }); + }, + ); }); describe('/store/:id/widgets/count - filtered (matches)', function() { beforeEach(function() { - this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=foo'; - }); - lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=foo', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - }); - it('should return the count', function() { - assert.equal(this.res.body.count, 1); - }); + this.url = + '/api/stores/' + this.store.id + '/widgets/count?where[name]=foo'; }); + lt.describe.whenCalledRemotely( + 'GET', + '/api/stores/:id/widgets/count?where[name]=foo', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + }); + it('should return the count', function() { + assert.equal(this.res.body.count, 1); + }); + }, + ); }); describe('/store/:id/widgets/count - filtered (no matches)', function() { beforeEach(function() { - this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=bar'; - }); - lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=bar', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - }); - it('should return the count', function() { - assert.equal(this.res.body.count, 0); - }); + this.url = + '/api/stores/' + this.store.id + '/widgets/count?where[name]=bar'; }); + lt.describe.whenCalledRemotely( + 'GET', + '/api/stores/:id/widgets/count?where[name]=bar', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + }); + it('should return the count', function() { + assert.equal(this.res.body.count, 0); + }); + }, + ); }); describe('/widgets/:id/store', function() { beforeEach(function(done) { const self = this; - this.store.widgets.create({ - name: this.widgetName, - }, function(err, widget) { - self.widget = widget; - self.url = '/api/widgets/' + self.widget.id + '/store'; + this.store.widgets.create( + { + name: this.widgetName, + }, + function(err, widget) { + self.widget = widget; + self.url = '/api/widgets/' + self.widget.id + '/store'; - done(); - }); + done(); + }, + ); }); lt.describe.whenCalledRemotely('GET', '/api/widgets/:id/store', function() { it('should succeed with statusCode 200', function() { @@ -350,53 +422,74 @@ describe('relations - integration', function() { function setup(connecting, cb) { const root = {}; - async.series([ - // Clean up models - function(done) { - app.models.physician.destroyAll(function(err) { - app.models.patient.destroyAll(function(err) { - app.models.appointment.destroyAll(function(err) { - done(); + async.series( + [ + // Clean up models + function(done) { + app.models.physician.destroyAll(function(err) { + app.models.patient.destroyAll(function(err) { + app.models.appointment.destroyAll(function(err) { + done(); + }); }); }); - }); + }, + + // Create a physician + function(done) { + app.models.physician.create( + { + name: 'ph1', + }, + function(err, physician) { + root.physician = physician; + + done(); + }, + ); + }, + + // Create a patient + connecting ? + function(done) { + root.physician.patients.create( + { + name: 'pa1', + }, + function(err, patient) { + root.patient = patient; + root.relUrl = + '/api/physicians/' + + root.physician.id + + '/patients/rel/' + + root.patient.id; + + done(); + }, + ); + } : + function(done) { + app.models.patient.create( + { + name: 'pa1', + }, + function(err, patient) { + root.patient = patient; + root.relUrl = + '/api/physicians/' + + root.physician.id + + '/patients/rel/' + + root.patient.id; + + done(); + }, + ); + }, + ], + function(err, done) { + cb(err, root); }, - - // Create a physician - function(done) { - app.models.physician.create({ - name: 'ph1', - }, function(err, physician) { - root.physician = physician; - - done(); - }); - }, - - // Create a patient - connecting ? function(done) { - root.physician.patients.create({ - name: 'pa1', - }, function(err, patient) { - root.patient = patient; - root.relUrl = '/api/physicians/' + root.physician.id + - '/patients/rel/' + root.patient.id; - - done(); - }); - } : function(done) { - app.models.patient.create({ - name: 'pa1', - }, function(err, patient) { - root.patient = patient; - root.relUrl = '/api/physicians/' + root.physician.id + - '/patients/rel/' + root.patient.id; - - done(); - }); - }], function(err, done) { - cb(err, root); - }); + ); } describe('PUT /physicians/:id/patients/rel/:fk', function() { @@ -411,33 +504,37 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - assert.equal(this.res.body.patientId, this.patient.id); - assert.equal(this.res.body.physicianId, this.physician.id); - }); - - it('should create a record in appointment', function(done) { - const self = this; - app.models.appointment.find(function(err, apps) { - assert.equal(apps.length, 1); - assert.equal(apps[0].patientId, self.patient.id); - - done(); + lt.describe.whenCalledRemotely( + 'PUT', + '/api/physicians/:id/patients/rel/:fk', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + assert.equal(this.res.body.patientId, this.patient.id); + assert.equal(this.res.body.physicianId, this.physician.id); }); - }); - it('should connect physician to patient', function(done) { - const self = this; - self.physician.patients(function(err, patients) { - assert.equal(patients.length, 1); - assert.equal(patients[0].id, self.patient.id); + it('should create a record in appointment', function(done) { + const self = this; + app.models.appointment.find(function(err, apps) { + assert.equal(apps.length, 1); + assert.equal(apps[0].patientId, self.patient.id); - done(); + done(); + }); }); - }); - }); + + it('should connect physician to patient', function(done) { + const self = this; + self.physician.patients(function(err, patients) { + assert.equal(patients.length, 1); + assert.equal(patients[0].id, self.patient.id); + + done(); + }); + }); + }, + ); }); describe('PUT /physicians/:id/patients/rel/:fk with data', function() { @@ -455,36 +552,41 @@ describe('relations - integration', function() { const NOW = Date.now(); const data = {date: new Date(NOW)}; - lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', data, function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - assert.equal(this.res.body.patientId, this.patient.id); - assert.equal(this.res.body.physicianId, this.physician.id); - assert.equal(new Date(this.res.body.date).getTime(), NOW); - }); - - it('should create a record in appointment', function(done) { - const self = this; - app.models.appointment.find(function(err, apps) { - assert.equal(apps.length, 1); - assert.equal(apps[0].patientId, self.patient.id); - assert.equal(apps[0].physicianId, self.physician.id); - assert.equal(apps[0].date.getTime(), NOW); - - done(); + lt.describe.whenCalledRemotely( + 'PUT', + '/api/physicians/:id/patients/rel/:fk', + data, + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + assert.equal(this.res.body.patientId, this.patient.id); + assert.equal(this.res.body.physicianId, this.physician.id); + assert.equal(new Date(this.res.body.date).getTime(), NOW); }); - }); - it('should connect physician to patient', function(done) { - const self = this; - self.physician.patients(function(err, patients) { - assert.equal(patients.length, 1); - assert.equal(patients[0].id, self.patient.id); + it('should create a record in appointment', function(done) { + const self = this; + app.models.appointment.find(function(err, apps) { + assert.equal(apps.length, 1); + assert.equal(apps[0].patientId, self.patient.id); + assert.equal(apps[0].physicianId, self.physician.id); + assert.equal(apps[0].date.getTime(), NOW); - done(); + done(); + }); }); - }); - }); + + it('should connect physician to patient', function(done) { + const self = this; + self.physician.patients(function(err, patients) { + assert.equal(patients.length, 1); + assert.equal(patients[0].id, self.patient.id); + + done(); + }); + }); + }, + ); }); describe('HEAD /physicians/:id/patients/rel/:fk', function() { @@ -499,19 +601,23 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('HEAD', '/api/physicians/:id/patients/rel/:fk', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - }); - }); + lt.describe.whenCalledRemotely( + 'HEAD', + '/api/physicians/:id/patients/rel/:fk', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + }); + }, + ); }); describe('HEAD /physicians/:id/patients/rel/:fk that does not exist', function() { before(function(done) { const self = this; setup(true, function(err, root) { - self.url = '/api/physicians/' + root.physician.id + - '/patients/rel/' + '999'; + self.url = + '/api/physicians/' + root.physician.id + '/patients/rel/' + '999'; self.patient = root.patient; self.physician = root.physician; @@ -519,11 +625,15 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('HEAD', '/api/physicians/:id/patients/rel/:fk', function() { - it('should succeed with statusCode 404', function() { - assert.equal(this.res.statusCode, 404); - }); - }); + lt.describe.whenCalledRemotely( + 'HEAD', + '/api/physicians/:id/patients/rel/:fk', + function() { + it('should succeed with statusCode 404', function() { + assert.equal(this.res.statusCode, 404); + }); + }, + ); }); describe('DELETE /physicians/:id/patients/rel/:fk', function() { @@ -558,38 +668,45 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/rel/:fk', function() { - it('should succeed with statusCode 204', function() { - assert.equal(this.res.statusCode, 204); - }); - - it('should remove the record in appointment', function(done) { - const self = this; - app.models.appointment.find(function(err, apps) { - assert.equal(apps.length, 0); - - done(); + lt.describe.whenCalledRemotely( + 'DELETE', + '/api/physicians/:id/patients/rel/:fk', + function() { + it('should succeed with statusCode 204', function() { + assert.equal(this.res.statusCode, 204); }); - }); - it('should remove the connection between physician and patient', function(done) { - const self = this; - // Need to refresh the cache - self.physician.patients(true, function(err, patients) { - assert.equal(patients.length, 0); + it('should remove the record in appointment', function(done) { + const self = this; + app.models.appointment.find(function(err, apps) { + assert.equal(apps.length, 0); - done(); + done(); + }); }); - }); - }); + + it('should remove the connection between physician and patient', function(done) { + const self = this; + // Need to refresh the cache + self.physician.patients(true, function(err, patients) { + assert.equal(patients.length, 0); + + done(); + }); + }); + }, + ); }); describe('GET /physicians/:id/patients/:fk', function() { before(function(done) { const self = this; setup(true, function(err, root) { - self.url = '/api/physicians/' + root.physician.id + - '/patients/' + root.patient.id; + self.url = + '/api/physicians/' + + root.physician.id + + '/patients/' + + root.patient.id; self.patient = root.patient; self.physician = root.physician; @@ -597,20 +714,27 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('GET', '/api/physicians/:id/patients/:fk', function() { - it('should succeed with statusCode 200', function() { - assert.equal(this.res.statusCode, 200); - assert.equal(this.res.body.id, this.physician.id); - }); - }); + lt.describe.whenCalledRemotely( + 'GET', + '/api/physicians/:id/patients/:fk', + function() { + it('should succeed with statusCode 200', function() { + assert.equal(this.res.statusCode, 200); + assert.equal(this.res.body.id, this.physician.id); + }); + }, + ); }); describe('DELETE /physicians/:id/patients/:fk', function() { before(function(done) { const self = this; setup(true, function(err, root) { - self.url = '/api/physicians/' + root.physician.id + - '/patients/' + root.patient.id; + self.url = + '/api/physicians/' + + root.physician.id + + '/patients/' + + root.patient.id; self.patient = root.patient; self.physician = root.physician; @@ -618,39 +742,43 @@ describe('relations - integration', function() { }); }); - lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/:fk', function() { - it('should succeed with statusCode 204', function() { - assert.equal(this.res.statusCode, 204); - }); - - it('should remove the record in appointment', function(done) { - const self = this; - app.models.appointment.find(function(err, apps) { - assert.equal(apps.length, 0); - - done(); + lt.describe.whenCalledRemotely( + 'DELETE', + '/api/physicians/:id/patients/:fk', + function() { + it('should succeed with statusCode 204', function() { + assert.equal(this.res.statusCode, 204); }); - }); - it('should remove the connection between physician and patient', function(done) { - const self = this; - // Need to refresh the cache - self.physician.patients(true, function(err, patients) { - assert.equal(patients.length, 0); + it('should remove the record in appointment', function(done) { + const self = this; + app.models.appointment.find(function(err, apps) { + assert.equal(apps.length, 0); - done(); + done(); + }); }); - }); - it('should remove the record in patient', function(done) { - const self = this; - app.models.patient.find(function(err, patients) { - assert.equal(patients.length, 0); + it('should remove the connection between physician and patient', function(done) { + const self = this; + // Need to refresh the cache + self.physician.patients(true, function(err, patients) { + assert.equal(patients.length, 0); - done(); + done(); + }); }); - }); - }); + + it('should remove the record in patient', function(done) { + const self = this; + app.models.patient.find(function(err, patients) { + assert.equal(patients.length, 0); + + done(); + }); + }); + }, + ); }); }); @@ -659,14 +787,14 @@ describe('relations - integration', function() { // Disable "Warning: overriding remoting type product" this.app.remotes()._typeRegistry._options.warnWhenOverridingType = false; - const product = app.registry.createModel( - 'product', - {id: 'string', name: 'string'}, - ); - const category = app.registry.createModel( - 'category', - {id: 'string', name: 'string'}, - ); + const product = app.registry.createModel('product', { + id: 'string', + name: 'string', + }); + const category = app.registry.createModel('category', { + id: 'string', + name: 'string', + }); app.model(product, {dataSource: 'db'}); app.model(category, {dataSource: 'db'}); @@ -678,24 +806,29 @@ describe('relations - integration', function() { beforeEach(function createProductsInCategory(done) { const test = this; - this.category.products.create({ - name: 'a-product', - }, function(err, product) { - if (err) return done(err); + this.category.products.create( + { + name: 'a-product', + }, + function(err, product) { + if (err) return done(err); - test.product = product; + test.product = product; - done(); - }); + done(); + }, + ); }); beforeEach(function createAnotherCategoryAndProduct(done) { - app.models.category.create({name: 'another-category'}, - function(err, cat) { - if (err) return done(err); + app.models.category.create({name: 'another-category'}, function( + err, + cat, + ) { + if (err) return done(err); - cat.products.create({name: 'another-product'}, done); - }); + cat.products.create({name: 'another-product'}, done); + }); }); afterEach(function(done) { @@ -705,25 +838,27 @@ describe('relations - integration', function() { it.skip('allows to find related objects via where filter', function(done) { // TODO https://github.com/strongloop/loopback-datasource-juggler/issues/94 const expectedProduct = this.product; - this.get('/api/products?filter[where][categoryId]=' + this.category.id) - .expect(200, function(err, res) { - if (err) return done(err); + this.get( + '/api/products?filter[where][categoryId]=' + this.category.id, + ).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.eql([ - { - id: expectedProduct.id, - name: expectedProduct.name, - }, - ]); + expect(res.body).to.eql([ + { + id: expectedProduct.id, + name: expectedProduct.name, + }, + ]); - done(); - }); + done(); + }); }); it('allows to find related object via URL scope', function(done) { const expectedProduct = this.product; - this.get('/api/categories/' + this.category.id + '/products') - .expect(200, function(err, res) { + this.get('/api/categories/' + this.category.id + '/products').expect( + 200, + function(err, res) { if (err) return done(err); expect(res.body).to.eql([ @@ -734,28 +869,30 @@ describe('relations - integration', function() { ]); done(); - }); + }, + ); }); it('includes requested related models in `find`', function(done) { const expectedProduct = this.product; - const url = '/api/categories/findOne?filter[where][id]=' + - this.category.id + '&filter[include]=products'; + const url = + '/api/categories/findOne?filter[where][id]=' + + this.category.id + + '&filter[include]=products'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.have.property('products'); - expect(res.body.products).to.eql([ - { - id: expectedProduct.id, - name: expectedProduct.name, - }, - ]); + expect(res.body).to.have.property('products'); + expect(res.body.products).to.eql([ + { + id: expectedProduct.id, + name: expectedProduct.name, + }, + ]); - done(); - }); + done(); + }); }); it.skip('includes requested related models in `findById`', function(done) { @@ -764,20 +901,19 @@ describe('relations - integration', function() { // Note: the URL format is not final const url = '/api/categories/' + this.category.id + '?include=products'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.have.property('products'); - expect(res.body.products).to.eql([ - { - id: expectedProduct.id, - name: expectedProduct.name, - }, - ]); + expect(res.body).to.have.property('products'); + expect(res.body.products).to.eql([ + { + id: expectedProduct.id, + name: expectedProduct.name, + }, + ]); - done(); - }); + done(); + }); }); }); @@ -790,10 +926,7 @@ describe('relations - integration', function() { ); app.model(group, {dataSource: 'db'}); - const poster = app.registry.createModel( - 'poster', - {url: 'string'}, - ); + const poster = app.registry.createModel('poster', {url: 'string'}); app.model(poster, {dataSource: 'db'}); group.embedsOne(poster, {as: 'cover'}); @@ -801,14 +934,13 @@ describe('relations - integration', function() { before(function createImage(done) { const test = this; - app.models.group.create({name: 'Group 1'}, - function(err, group) { - if (err) return done(err); + app.models.group.create({name: 'Group 1'}, function(err, group) { + if (err) return done(err); - test.group = group; + test.group = group; - done(); - }); + done(); + }); }); after(function(done) { @@ -821,9 +953,7 @@ describe('relations - integration', function() { this.post(url) .send({url: 'http://image.url'}) .expect(200, function(err, res) { - expect(res.body).to.be.eql( - {url: 'http://image.url'}, - ); + expect(res.body).to.be.eql({url: 'http://image.url'}); done(); }); @@ -832,32 +962,26 @@ describe('relations - integration', function() { it('includes the embedded models', function(done) { const url = '/api/groups/' + this.group.id; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.name).to.be.equal('Group 1'); - expect(res.body.poster).to.be.eql( - {url: 'http://image.url'}, - ); + expect(res.body.name).to.be.equal('Group 1'); + expect(res.body.poster).to.be.eql({url: 'http://image.url'}); - done(); - }); + done(); + }); }); it('returns the embedded model', function(done) { const url = '/api/groups/' + this.group.id + '/cover'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql( - {url: 'http://image.url'}, - ); + expect(res.body).to.be.eql({url: 'http://image.url'}); - done(); - }); + done(); + }); }); it('updates an embedded model', function(done) { @@ -875,16 +999,13 @@ describe('relations - integration', function() { it('returns the updated embedded model', function(done) { const url = '/api/groups/' + this.group.id + '/cover'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql( - {url: 'http://changed.url'}, - ); + expect(res.body).to.be.eql({url: 'http://changed.url'}); - done(); - }); + done(); + }); }); it('deletes an embedded model', function(done) { @@ -909,7 +1030,8 @@ describe('relations - integration', function() { const todoItem = app.registry.createModel( 'todoItem', - {content: 'string'}, {forceId: false}, + {content: 'string'}, + {forceId: false}, ); app.model(todoItem, {dataSource: 'db'}); @@ -918,15 +1040,14 @@ describe('relations - integration', function() { before(function createTodoList(done) { const test = this; - app.models.todoList.create({name: 'List A'}, - function(err, list) { - if (err) return done(err); + app.models.todoList.create({name: 'List A'}, function(err, list) { + if (err) return done(err); - test.todoList = list; - list.items.build({content: 'Todo 1'}); - list.items.build({content: 'Todo 2'}); - list.save(done); - }); + test.todoList = list; + list.items.build({content: 'Todo 1'}); + list.items.build({content: 'Todo 2'}); + list.save(done); + }); }); after(function(done) { @@ -936,50 +1057,45 @@ describe('relations - integration', function() { it('includes the embedded models', function(done) { const url = '/api/todo-lists/' + this.todoList.id; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.name).to.be.equal('List A'); - expect(res.body.todoItems).to.be.eql([ - {content: 'Todo 1', id: 1}, - {content: 'Todo 2', id: 2}, - ]); + expect(res.body.name).to.be.equal('List A'); + expect(res.body.todoItems).to.be.eql([ + {content: 'Todo 1', id: 1}, + {content: 'Todo 2', id: 2}, + ]); - done(); - }); + done(); + }); }); it('returns the embedded models', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {content: 'Todo 1', id: 1}, - {content: 'Todo 2', id: 2}, - ]); + expect(res.body).to.be.eql([ + {content: 'Todo 1', id: 1}, + {content: 'Todo 2', id: 2}, + ]); - done(); - }); + done(); + }); }); it('filters the embedded models', function(done) { let url = '/api/todo-lists/' + this.todoList.id + '/items'; url += '?filter[where][id]=2'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {content: 'Todo 2', id: 2}, - ]); + expect(res.body).to.be.eql([{content: 'Todo 2', id: 2}]); - done(); - }); + done(); + }); }); it('creates embedded models', function(done) { @@ -999,59 +1115,53 @@ describe('relations - integration', function() { it('includes the created embedded model', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {content: 'Todo 1', id: 1}, - {content: 'Todo 2', id: 2}, - {content: 'Todo 3', id: 3}, - ]); + expect(res.body).to.be.eql([ + {content: 'Todo 1', id: 1}, + {content: 'Todo 2', id: 2}, + {content: 'Todo 3', id: 3}, + ]); - done(); - }); + done(); + }); }); it('returns an embedded model by (internal) id', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items/3'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql( - {content: 'Todo 3', id: 3}, - ); + expect(res.body).to.be.eql({content: 'Todo 3', id: 3}); - done(); - }); + done(); + }); }); it('removes an embedded model', function(done) { const expectedProduct = this.product; const url = '/api/todo-lists/' + this.todoList.id + '/items/2'; - this.del(url) - .expect(200, function(err, res) { - done(); - }); + this.del(url).expect(200, function(err, res) { + done(); + }); }); it('returns the embedded models - verify', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {content: 'Todo 1', id: 1}, - {content: 'Todo 3', id: 3}, - ]); + expect(res.body).to.be.eql([ + {content: 'Todo 1', id: 1}, + {content: 'Todo 3', id: 3}, + ]); - done(); - }); + done(); + }); }); it('returns a 404 response when embedded model is not found', function(done) { @@ -1060,7 +1170,9 @@ describe('relations - integration', function() { if (err) return done(err); expect(res.body.error.status).to.be.equal(404); - expect(res.body.error.message).to.be.equal('Unknown "todoItem" id "2".'); + expect(res.body.error.message).to.be.equal( + 'Unknown "todoItem" id "2".', + ); expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); done(); @@ -1070,63 +1182,59 @@ describe('relations - integration', function() { it.skip('checks if an embedded model exists - ok', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items/3'; - this.head(url) - .expect(200, function(err, res) { - done(); - }); + this.head(url).expect(200, function(err, res) { + done(); + }); }); it.skip('checks if an embedded model exists - fail', function(done) { const url = '/api/todo-lists/' + this.todoList.id + '/items/2'; - this.head(url) - .expect(404, function(err, res) { - done(); - }); + this.head(url).expect(404, function(err, res) { + done(); + }); }); }); describe('referencesMany', function() { before(function defineProductAndCategoryModels() { - const recipe = app.registry.createModel( - 'recipe', - {name: 'string'}, - ); + const recipe = app.registry.createModel('recipe', {name: 'string'}); app.model(recipe, {dataSource: 'db'}); - const ingredient = app.registry.createModel( - 'ingredient', - {name: 'string'}, - ); + const ingredient = app.registry.createModel('ingredient', { + name: 'string', + }); app.model(ingredient, {dataSource: 'db'}); - const photo = app.registry.createModel( - 'photo', - {name: 'string'}, - ); + const photo = app.registry.createModel('photo', {name: 'string'}); app.model(photo, {dataSource: 'db'}); recipe.referencesMany(ingredient); // contrived example for test: - recipe.hasOne(photo, {as: 'picture', options: { - http: {path: 'image'}, - }}); + recipe.hasOne(photo, { + as: 'picture', + options: { + http: {path: 'image'}, + }, + }); }); before(function createRecipe(done) { const test = this; - app.models.recipe.create({name: 'Recipe'}, - function(err, recipe) { - if (err) return done(err); + app.models.recipe.create({name: 'Recipe'}, function(err, recipe) { + if (err) return done(err); - test.recipe = recipe; - recipe.ingredients.create({ - name: 'Chocolate'}, + test.recipe = recipe; + recipe.ingredients.create( + { + name: 'Chocolate', + }, function(err, ing) { test.ingredient1 = ing.id; recipe.picture.create({name: 'Photo 1'}, done); - }); - }); + }, + ); + }); }); before(function createIngredient(done) { @@ -1151,15 +1259,14 @@ describe('relations - integration', function() { const url = '/api/recipes/' + this.recipe.id; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.ingredientIds).to.eql([test.ingredient1]); - expect(res.body).to.not.have.property('ingredients'); + expect(res.body.ingredientIds).to.eql([test.ingredient1]); + expect(res.body).to.not.have.property('ingredients'); - done(); - }); + done(); + }); }); it('creates referenced models', function(done) { @@ -1180,35 +1287,33 @@ describe('relations - integration', function() { const url = '/api/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Sugar', id: test.ingredient2}, - {name: 'Butter', id: test.ingredient3}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Sugar', id: test.ingredient2}, + {name: 'Butter', id: test.ingredient3}, + ]); - done(); - }); + done(); + }); }); it('returns the referenced models', function(done) { const url = '/api/recipes/' + this.recipe.id + '/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Butter', id: test.ingredient3}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Butter', id: test.ingredient3}, + ]); - done(); - }); + done(); + }); }); it('filters the referenced models', function(done) { @@ -1216,16 +1321,13 @@ describe('relations - integration', function() { url += '?filter[where][name]=Butter'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Butter', id: test.ingredient3}, - ]); + expect(res.body).to.be.eql([{name: 'Butter', id: test.ingredient3}]); - done(); - }); + done(); + }); }); it('includes the referenced models', function(done) { @@ -1233,20 +1335,20 @@ describe('relations - integration', function() { url += '&filter[include]=ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.ingredientIds).to.eql([ - test.ingredient1, test.ingredient3, - ]); - expect(res.body.ingredients).to.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Butter', id: test.ingredient3}, - ]); + expect(res.body.ingredientIds).to.eql([ + test.ingredient1, + test.ingredient3, + ]); + expect(res.body.ingredients).to.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Butter', id: test.ingredient3}, + ]); - done(); - }); + done(); + }); }); it('returns a referenced model by id', function(done) { @@ -1254,16 +1356,13 @@ describe('relations - integration', function() { url += this.ingredient3; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql( - {name: 'Butter', id: test.ingredient3}, - ); + expect(res.body).to.be.eql({name: 'Butter', id: test.ingredient3}); - done(); - }); + done(); + }); }); it('keeps an array of ids - verify', function(done) { @@ -1272,15 +1371,14 @@ describe('relations - integration', function() { const expected = [test.ingredient1, test.ingredient3]; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.ingredientIds).to.eql(expected); - expect(res.body).to.not.have.property('ingredients'); + expect(res.body.ingredientIds).to.eql(expected); + expect(res.body).to.not.have.property('ingredients'); - done(); - }); + done(); + }); }); it('destroys a referenced model', function(done) { @@ -1288,43 +1386,40 @@ describe('relations - integration', function() { let url = '/api/recipes/' + this.recipe.id + '/ingredients/'; url += this.ingredient3; - this.del(url) - .expect(200, function(err, res) { - done(); - }); + this.del(url).expect(200, function(err, res) { + done(); + }); }); it('has destroyed a referenced model', function(done) { const url = '/api/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Sugar', id: test.ingredient2}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Sugar', id: test.ingredient2}, + ]); - done(); - }); + done(); + }); }); it('returns the referenced models without the deleted one', function(done) { const url = '/api/recipes/' + this.recipe.id + '/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + ]); - done(); - }); + done(); + }); }); it('creates/links a reference by id', function(done) { @@ -1332,31 +1427,27 @@ describe('relations - integration', function() { url += '/rel/' + this.ingredient2; const test = this; - this.put(url) - .expect(200, function(err, res) { - expect(res.body).to.be.eql( - {name: 'Sugar', id: test.ingredient2}, - ); + this.put(url).expect(200, function(err, res) { + expect(res.body).to.be.eql({name: 'Sugar', id: test.ingredient2}); - done(); - }); + done(); + }); }); it('returns the referenced models - verify', function(done) { const url = '/api/recipes/' + this.recipe.id + '/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Sugar', id: test.ingredient2}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Sugar', id: test.ingredient2}, + ]); - done(); - }); + done(); + }); }); it('removes/unlinks a reference by id', function(done) { @@ -1364,77 +1455,69 @@ describe('relations - integration', function() { url += '/rel/' + this.ingredient1; const test = this; - this.del(url) - .expect(200, function(err, res) { - done(); - }); + this.del(url).expect(200, function(err, res) { + done(); + }); }); it('returns the referenced models without the unlinked one', function(done) { const url = '/api/recipes/' + this.recipe.id + '/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Sugar', id: test.ingredient2}, - ]); + expect(res.body).to.be.eql([{name: 'Sugar', id: test.ingredient2}]); - done(); - }); + done(); + }); }); it('has not destroyed an unlinked model', function(done) { const url = '/api/ingredients'; const test = this; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.eql([ - {name: 'Chocolate', id: test.ingredient1}, - {name: 'Sugar', id: test.ingredient2}, - ]); + expect(res.body).to.be.eql([ + {name: 'Chocolate', id: test.ingredient1}, + {name: 'Sugar', id: test.ingredient2}, + ]); - done(); - }); + done(); + }); }); it('uses a custom relation path', function(done) { const url = '/api/recipes/' + this.recipe.id + '/image'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(err).to.not.exist(); - expect(res.body.name).to.equal('Photo 1'); + expect(err).to.not.exist(); + expect(res.body.name).to.equal('Photo 1'); - done(); - }); + done(); + }); }); it.skip('checks if a referenced model exists - ok', function(done) { let url = '/api/recipes/' + this.recipe.id + '/ingredients/'; url += this.ingredient1; - this.head(url) - .expect(200, function(err, res) { - done(); - }); + this.head(url).expect(200, function(err, res) { + done(); + }); }); it.skip('checks if an referenced model exists - fail', function(done) { let url = '/api/recipes/' + this.recipe.id + '/ingredients/'; url += this.ingredient3; - this.head(url) - .expect(404, function(err, res) { - done(); - }); + this.head(url).expect(404, function(err, res) { + done(); + }); }); }); @@ -1489,8 +1572,13 @@ describe('relations - integration', function() { throw new Error('This should not crash the app'); }; - Page.remoteMethod('__throw__errors', {isStatic: false, http: {path: '/throws', verb: 'get'}, - accepts: [{arg: 'options', type: 'object', http: 'optionsFromRequest'}]}); + Page.remoteMethod('__throw__errors', { + isStatic: false, + http: {path: '/throws', verb: 'get'}, + accepts: [ + {arg: 'options', type: 'object', http: 'optionsFromRequest'}, + ], + }); // Now `pages` has nestRemoting set to true and no need to call nestRemoting() // Book.nestRemoting('pages'); @@ -1500,13 +1588,21 @@ describe('relations - integration', function() { expect(Book.prototype['__findById__pages']).to.be.a('function'); expect(Image.prototype['__get__book']).to.be.a('function'); - Page.beforeRemote('prototype.__findById__notes', function(ctx, result, next) { + Page.beforeRemote('prototype.__findById__notes', function( + ctx, + result, + next, + ) { ctx.res.set('x-before', 'before'); next(); }); - Page.afterRemote('prototype.__findById__notes', function(ctx, result, next) { + Page.afterRemote('prototype.__findById__notes', function( + ctx, + result, + next, + ) { ctx.res.set('x-after', 'after'); next(); @@ -1524,71 +1620,72 @@ describe('relations - integration', function() { before(function createBook(done) { const test = this; - app.models.Book.create({name: 'Book 1'}, - function(err, book) { + app.models.Book.create({name: 'Book 1'}, function(err, book) { + if (err) return done(err); + + test.book = book; + book.pages.create({name: 'Page 1'}, function(err, page) { if (err) return done(err); - test.book = book; - book.pages.create({name: 'Page 1'}, - function(err, page) { - if (err) return done(err); - - test.page = page; - page.notes.create({text: 'Page Note 1'}, - function(err, note) { - test.note = note; - - done(); - }); - }); - }); - }); - - before(function createChapters(done) { - const test = this; - test.book.chapters.create({name: 'Chapter 1'}, - function(err, chapter) { - if (err) return done(err); - - test.chapter = chapter; - chapter.notes.create({text: 'Chapter Note 1'}, function(err, note) { - test.cnote = note; + test.page = page; + page.notes.create({text: 'Page Note 1'}, function(err, note) { + test.note = note; done(); }); }); + }); + }); + + before(function createChapters(done) { + const test = this; + test.book.chapters.create({name: 'Chapter 1'}, function(err, chapter) { + if (err) return done(err); + + test.chapter = chapter; + chapter.notes.create({text: 'Chapter Note 1'}, function(err, note) { + test.cnote = note; + + done(); + }); + }); }); before(function createCover(done) { const test = this; - app.models.Image.create({name: 'Cover 1', book: test.book}, - function(err, image) { - if (err) return done(err); + app.models.Image.create({name: 'Cover 1', book: test.book}, function( + err, + image, + ) { + if (err) return done(err); - test.image = image; + test.image = image; - done(); - }); + done(); + }); }); it('has regular relationship routes - pages', function(done) { const test = this; - this.get('/api/books/' + test.book.id + '/pages') - .expect(200, function(err, res) { - if (err) return done(err); + this.get('/api/books/' + test.book.id + '/pages').expect(200, function( + err, + res, + ) { + if (err) return done(err); - expect(res.body).to.be.an('array'); - expect(res.body).to.have.length(1); - expect(res.body[0].name).to.equal('Page 1'); + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(1); + expect(res.body[0].name).to.equal('Page 1'); - done(); - }); + done(); + }); }); it('has regular relationship routes - notes', function(done) { const test = this; - this.get('/api/pages/' + test.page.id + '/notes/' + test.note.id) - .expect(200, function(err, res) { + this.get('/api/pages/' + test.page.id + '/notes/' + test.note.id).expect( + 200, + function(err, res) { if (err) return done(err); expect(res.headers['x-before']).to.equal('before'); @@ -1597,13 +1694,15 @@ describe('relations - integration', function() { expect(res.body.text).to.equal('Page Note 1'); done(); - }); + }, + ); }); it('has a basic error handler', function(done) { const test = this; - this.get('/api/books/unknown/pages/' + test.page.id + '/notes') - .expect(404, function(err, res) { + this.get('/api/books/unknown/pages/' + test.page.id + '/notes').expect( + 404, + function(err, res) { if (err) return done(err); expect(res.body.error).to.be.an('object'); @@ -1612,21 +1711,24 @@ describe('relations - integration', function() { expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); done(); - }); + }, + ); }); it('enables nested relationship routes - belongsTo find', function(done) { const test = this; - this.get('/api/images/' + test.image.id + '/book/pages') - .end(function(err, res) { - if (err) return done(err); + this.get('/api/images/' + test.image.id + '/book/pages').end(function( + err, + res, + ) { + if (err) return done(err); - expect(res.body).to.be.an('array'); - expect(res.body).to.have.length(1); - expect(res.body[0].name).to.equal('Page 1'); + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(1); + expect(res.body[0].name).to.equal('Page 1'); - done(); - }); + done(); + }); }); it('enables nested relationship routes - belongsTo findById', function(done) { @@ -1645,35 +1747,44 @@ describe('relations - integration', function() { it('enables nested relationship routes - hasMany find', function(done) { const test = this; - this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes') - .expect(200, function(err, res) { - if (err) return done(err); + this.get( + '/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes', + ).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body).to.be.an('array'); - expect(res.body).to.have.length(1); - expect(res.body[0].text).to.equal('Page Note 1'); + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(1); + expect(res.body[0].text).to.equal('Page Note 1'); - done(); - }); + done(); + }); }); it('enables nested relationship routes - hasMany findById', function(done) { const test = this; - this.get('/api/books/' + test.book.id + '/pages/' + test.page.id + '/notes/' + test.note.id) - .expect(200, function(err, res) { - if (err) return done(err); + this.get( + '/api/books/' + + test.book.id + + '/pages/' + + test.page.id + + '/notes/' + + test.note.id, + ).expect(200, function(err, res) { + if (err) return done(err); - expect(res.headers['x-before']).to.equal('before'); - expect(res.headers['x-after']).to.equal('after'); - expect(res.body).to.be.an('object'); - expect(res.body.text).to.equal('Page Note 1'); + expect(res.headers['x-before']).to.equal('before'); + expect(res.headers['x-after']).to.equal('after'); + expect(res.body).to.be.an('object'); + expect(res.body.text).to.equal('Page Note 1'); - done(); - }); + done(); + }); }); it('passes options to nested relationship routes', function() { - return this.get(`/api/books/${this.book.id}/pages/${this.page.id}/notes/${this.note.id}`) + return this.get( + `/api/books/${this.book.id}/pages/${this.page.id}/notes/${this.note.id}`, + ) .expect(200) .then(res => { expect(accessOptions).to.have.property('accessToken'); @@ -1682,15 +1793,21 @@ describe('relations - integration', function() { it('should nest remote hooks of ModelTo - hasMany findById', function(done) { const test = this; - this.get('/api/books/' + test.book.id + '/chapters/' + test.chapter.id + '/notes/' + test.cnote.id) - .expect(200, function(err, res) { - if (err) return done(err); + this.get( + '/api/books/' + + test.book.id + + '/chapters/' + + test.chapter.id + + '/notes/' + + test.cnote.id, + ).expect(200, function(err, res) { + if (err) return done(err); - expect(res.headers['x-before']).to.be.undefined(); - expect(res.headers['x-after']).to.be.undefined(); + expect(res.headers['x-before']).to.be.undefined(); + expect(res.headers['x-after']).to.be.undefined(); - done(); - }); + done(); + }); }); it('should have proper http.path for remoting', function() { @@ -1709,18 +1826,21 @@ describe('relations - integration', function() { it('should catch error if nested function throws', function(done) { const test = this; - this.get('/api/books/' + test.book.id + '/pages/' + this.page.id + '/throws') - .end(function(err, res) { - if (err) return done(err); + this.get( + '/api/books/' + test.book.id + '/pages/' + this.page.id + '/throws', + ).end(function(err, res) { + if (err) return done(err); - expect(res.body).to.be.an('object'); - expect(res.body.error).to.be.an('object'); - expect(res.body.error.name).to.equal('Error'); - expect(res.body.error.statusCode).to.equal(500); - expect(res.body.error.message).to.equal('This should not crash the app'); + expect(res.body).to.be.an('object'); + expect(res.body.error).to.be.an('object'); + expect(res.body.error.name).to.equal('Error'); + expect(res.body.error.statusCode).to.equal(500); + expect(res.body.error.message).to.equal( + 'This should not crash the app', + ); - done(); - }); + done(); + }); }); }); @@ -1764,15 +1884,14 @@ describe('relations - integration', function() { it('should find the referenced model', function(done) { const url = '/api/customers/' + cust.id + '/profile'; - this.get(url) - .expect(200, function(err, res) { - if (err) return done(err); + this.get(url).expect(200, function(err, res) { + if (err) return done(err); - expect(res.body.points).to.be.eql(10); - expect(res.body.customerId).to.be.eql(cust.id); + expect(res.body.points).to.be.eql(10); + expect(res.body.customerId).to.be.eql(cust.id); - done(); - }); + done(); + }); }); it('should not create the referenced model twice', function(done) { @@ -1800,18 +1919,20 @@ describe('relations - integration', function() { it('should delete the referenced model', function(done) { const url = '/api/customers/' + cust.id + '/profile'; - this.del(url) - .expect(204, function(err, res) { - done(err); - }); + this.del(url).expect(204, function(err, res) { + done(err); + }); }); it('should not find the referenced model', function(done) { const url = '/api/customers/' + cust.id + '/profile'; - this.get(url) - .expect(404, function(err, res) { - done(err); - }); + this.get(url).expect(404, function(err, res) { + expect(res.body.error.message).to.be.equal( + 'No "profile" instance(s) found', + ); + expect(res.body.error.code).to.be.equal('MODEL_NOT_FOUND'); + done(err); + }); }); }); });