From c4edd8638488bda85bc678ddf3a5eb10cb7634ae Mon Sep 17 00:00:00 2001 From: Marshall Thompson Date: Tue, 17 Feb 2015 11:34:44 -0700 Subject: [PATCH 1/5] Fix duplicate events. --- lib/application.js | 2 +- lib/providers/socket/commons.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/application.js b/lib/application.js index 5441d90a22..9a3c92f93b 100644 --- a/lib/application.js +++ b/lib/application.js @@ -47,11 +47,11 @@ module.exports = { // If already _setup, just add this single service. if (this._setup) { + protoService.setup(this, location); // If we're using a socket provider, register the service on it. if (this.addService) { this.addService(protoService, location); } - protoService.setup(this, location); } this.services[location] = protoService; diff --git a/lib/providers/socket/commons.js b/lib/providers/socket/commons.js index 1447dc836e..7de8dc32d6 100644 --- a/lib/providers/socket/commons.js +++ b/lib/providers/socket/commons.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var eventsMixin = require('../../mixins/event').Mixin; // The position of the params parameters for a service method so that we can extend them // default is 1 @@ -18,7 +17,6 @@ exports.addService = function addService(service, path){ }, this); // Setup events for the service. - eventsMixin.applyEvents.call(service); exports.setupEventHandlers.call(this, service, path); }; From 08f347affba97969e7de874a68c69f52554f5724 Mon Sep 17 00:00:00 2001 From: Marshall Thompson Date: Tue, 17 Feb 2015 11:35:19 -0700 Subject: [PATCH 2/5] Fix event filtering callbacks in tests. --- test/providers/primus.test.js | 11 +++-------- test/providers/socketio.test.js | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/test/providers/primus.test.js b/test/providers/primus.test.js index abf2401912..6c9631d17a 100644 --- a/test/providers/primus.test.js +++ b/test/providers/primus.test.js @@ -255,7 +255,7 @@ describe('Primus provider', function () { return callback(null, data); } - callback(); + callback(null, false); }; socket.send('todo::remove', 1, {}, function() {}); @@ -432,21 +432,16 @@ describe('Primus provider', function () { return callback(null, data); } - callback(); + callback(null, false); }; socket.send('tasks::remove', 1, {}, function() {}); socket.send('tasks::remove', 23, {}, function() {}); - var ready = false; - socket.on('tasks removed', function (data) { service.removed = oldRemoved; assert.equal(data.id, 23); - if (ready) { - done(); - } - ready = true; + done(); }); }); }); diff --git a/test/providers/socketio.test.js b/test/providers/socketio.test.js index f60085f262..6c7812d87f 100644 --- a/test/providers/socketio.test.js +++ b/test/providers/socketio.test.js @@ -254,7 +254,7 @@ describe('SocketIO provider', function () { return callback(null, data); } - callback(); + callback(null, false); }; socket.emit('todo::remove', 1, {}, function() {}); @@ -431,21 +431,16 @@ describe('SocketIO provider', function () { return callback(null, data); } - callback(); + callback(null, false); }; socket.emit('tasks::remove', 1, {}, function() {}); socket.emit('tasks::remove', 23, {}, function() {}); - var ready = false; - socket.on('tasks removed', function (data) { service.removed = oldRemoved; assert.equal(data.id, 23); - if (ready) { - done(); - } - ready = true; + done(); }); }); }); From 09b8edd502b27a2fd91205426040488493707ba5 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 20 Feb 2015 17:17:38 -0700 Subject: [PATCH 3/5] Reorgnize dynamic service tests into nested describes. --- test/providers/primus.test.js | 485 ++++++++++++++++--------------- test/providers/rest.test.js | 282 +++++++++--------- test/providers/socketio.test.js | 486 ++++++++++++++++---------------- 3 files changed, 624 insertions(+), 629 deletions(-) diff --git a/test/providers/primus.test.js b/test/providers/primus.test.js index 6c9631d17a..fdb834f14e 100644 --- a/test/providers/primus.test.js +++ b/test/providers/primus.test.js @@ -92,356 +92,355 @@ describe('Primus provider', function () { }); }); + describe('Services', function() { - /* * * * * * * * * Services * * * * * * * * */ + describe('CRUD', function () { + it('::find', function (done) { + socket.send('todo::find', {}, function (error, data) { + verify.find(data); - describe('CRUD', function () { - it('::find', function (done) { - socket.send('todo::find', {}, function (error, data) { - verify.find(data); - - done(error); + done(error); + }); }); - }); - it('::get', function (done) { - socket.send('todo::get', 'laundry', {}, function (error, data) { - verify.get('laundry', data); + it('::get', function (done) { + socket.send('todo::get', 'laundry', {}, function (error, data) { + verify.get('laundry', data); - done(error); + done(error); + }); }); - }); - it('::create', function (done) { - var original = { - name: 'creating' - }; + it('::create', function (done) { + var original = { + name: 'creating' + }; - socket.send('todo::create', original, {}, function (error, data) { - verify.create(original, data); + socket.send('todo::create', original, {}, function (error, data) { + verify.create(original, data); - done(error); + done(error); + }); }); - }); - it('::update', function (done) { - var original = { - name: 'updating' - }; + it('::update', function (done) { + var original = { + name: 'updating' + }; - socket.send('todo::update', 23, original, {}, function (error, data) { - verify.update(23, original, data); + socket.send('todo::update', 23, original, {}, function (error, data) { + verify.update(23, original, data); - done(error); + done(error); + }); }); - }); - it('::patch', function (done) { - var original = { - name: 'patching' - }; + it('::patch', function (done) { + var original = { + name: 'patching' + }; - socket.send('todo::patch', 25, original, {}, function (error, data) { - verify.patch(25, original, data); + socket.send('todo::patch', 25, original, {}, function (error, data) { + verify.patch(25, original, data); - done(error); + done(error); + }); }); - }); - it('::remove', function (done) { - socket.send('todo::remove', 11, {}, function (error, data) { - verify.remove(11, data); + it('::remove', function (done) { + socket.send('todo::remove', 11, {}, function (error, data) { + verify.remove(11, data); - done(error); + done(error); + }); }); }); - }); - describe('Events', function () { - it('created', function (done) { - var original = { - name: 'created event' - }; + describe('Events', function () { + it('created', function (done) { + var original = { + name: 'created event' + }; - socket.once('todo created', function (data) { - verify.create(original, data); - done(); + socket.once('todo created', function (data) { + verify.create(original, data); + done(); + }); + + socket.send('todo::create', original, {}, function () {}); }); - socket.send('todo::create', original, {}, function () {}); - }); + it('updated', function (done) { + var original = { + name: 'updated event' + }; - it('updated', function (done) { - var original = { - name: 'updated event' - }; + socket.once('todo updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.once('todo updated', function (data) { - verify.update(10, original, data); - done(); + socket.send('todo::update', 10, original, {}, function () {}); }); - socket.send('todo::update', 10, original, {}, function () {}); - }); + it('patched', function(done) { + var original = { + name: 'patched event' + }; - it('patched', function(done) { - var original = { - name: 'patched event' - }; + socket.once('todo patched', function (data) { + verify.patch(12, original, data); + done(); + }); - socket.once('todo patched', function (data) { - verify.patch(12, original, data); - done(); + socket.send('todo::patch', 12, original, {}, function () {}); }); - socket.send('todo::patch', 12, original, {}, function () {}); - }); + it('removed', function (done) { + socket.once('todo removed', function (data) { + verify.remove(333, data); + done(); + }); - it('removed', function (done) { - socket.once('todo removed', function (data) { - verify.remove(333, data); - done(); + socket.send('todo::remove', 333, {}, function () {}); }); - - socket.send('todo::remove', 333, {}, function () {}); }); - }); - describe('Event filtering', function() { - it('.created', function (done) { - var service = app.service('todo'); - var original = { description: 'created event test' }; - var oldCreated = service.created; + describe('Event filtering', function() { + it('.created', function (done) { + var service = app.service('todo'); + var original = { description: 'created event test' }; + var oldCreated = service.created; - service.created = function(data, params, callback) { - assert.deepEqual(params, socketParams); - verify.create(original, data); + service.created = function(data, params, callback) { + assert.deepEqual(params, socketParams); + verify.create(original, data); - callback(null, _.extend({ processed: true }, data)); - }; + callback(null, _.extend({ processed: true }, data)); + }; - socket.send('todo::create', original, {}, function() {}); + socket.send('todo::create', original, {}, function() {}); - socket.once('todo created', function (data) { - service.created = oldCreated; - // Make sure Todo got processed - verify.create(_.extend({ processed: true }, original), data); - done(); + socket.once('todo created', function (data) { + service.created = oldCreated; + // Make sure Todo got processed + verify.create(_.extend({ processed: true }, original), data); + done(); + }); }); - }); - it('.updated', function (done) { - var original = { - name: 'updated event' - }; + it('.updated', function (done) { + var original = { + name: 'updated event' + }; - socket.once('todo updated', function (data) { - verify.update(10, original, data); - done(); + socket.once('todo updated', function (data) { + verify.update(10, original, data); + done(); + }); + + socket.send('todo::update', 10, original, {}, function () {}); }); - socket.send('todo::update', 10, original, {}, function () {}); - }); + it('.removed', function (done) { + var service = app.service('todo'); + var oldRemoved = service.removed; - it('.removed', function (done) { - var service = app.service('todo'); - var oldRemoved = service.removed; + service.removed = function(data, params, callback) { + assert.deepEqual(params, socketParams); - service.removed = function(data, params, callback) { - assert.deepEqual(params, socketParams); + if(data.id === 23) { + // Only dispatch with given id + return callback(null, data); + } - if(data.id === 23) { - // Only dispatch with given id - return callback(null, data); - } + callback(null, false); + }; - callback(null, false); - }; + socket.send('todo::remove', 1, {}, function() {}); + socket.send('todo::remove', 23, {}, function() {}); - socket.send('todo::remove', 1, {}, function() {}); - socket.send('todo::remove', 23, {}, function() {}); - - socket.on('todo removed', function (data) { - service.removed = oldRemoved; - assert.equal(data.id, 23); - done(); + socket.on('todo removed', function (data) { + service.removed = oldRemoved; + assert.equal(data.id, 23); + done(); + }); }); }); }); + describe('Dynamic services', function() { + describe('CRUD', function () { + it('::find', function (done) { + socket.send('tasks::find', {}, function (error, data) { + verify.find(data); - /* * * * * * * * * Dynamically-Added Services * * * * * * * * */ - - describe('Dynamic Service CRUD', function () { - it('::find', function (done) { - socket.send('tasks::find', {}, function (error, data) { - verify.find(data); - - done(error); + done(error); + }); }); - }); - it('::get', function (done) { - socket.send('tasks::get', 'laundry', {}, function (error, data) { - verify.get('laundry', data); + it('::get', function (done) { + socket.send('tasks::get', 'laundry', {}, function (error, data) { + verify.get('laundry', data); - done(error); + done(error); + }); }); - }); - it('::create', function (done) { - var original = { - name: 'creating' - }; + it('::create', function (done) { + var original = { + name: 'creating' + }; - socket.send('tasks::create', original, {}, function (error, data) { - verify.create(original, data); + socket.send('tasks::create', original, {}, function (error, data) { + verify.create(original, data); - done(error); + done(error); + }); }); - }); - it('::update', function (done) { - var original = { - name: 'updating' - }; + it('::update', function (done) { + var original = { + name: 'updating' + }; - socket.send('tasks::update', 23, original, {}, function (error, data) { - verify.update(23, original, data); + socket.send('tasks::update', 23, original, {}, function (error, data) { + verify.update(23, original, data); - done(error); + done(error); + }); }); - }); - it('::patch', function (done) { - var original = { - name: 'patching' - }; + it('::patch', function (done) { + var original = { + name: 'patching' + }; - socket.send('tasks::patch', 25, original, {}, function (error, data) { - verify.patch(25, original, data); + socket.send('tasks::patch', 25, original, {}, function (error, data) { + verify.patch(25, original, data); - done(error); + done(error); + }); }); - }); - it('::remove', function (done) { - socket.send('tasks::remove', 11, {}, function (error, data) { - verify.remove(11, data); + it('::remove', function (done) { + socket.send('tasks::remove', 11, {}, function (error, data) { + verify.remove(11, data); - done(error); + done(error); + }); }); }); - }); - describe('Dynamic Service Events', function () { - it('created', function (done) { - var original = { - name: 'created event' - }; + describe('Events', function () { + it('created', function (done) { + var original = { + name: 'created event' + }; - socket.once('tasks created', function (data) { - verify.create(original, data); - done(); + socket.once('tasks created', function (data) { + verify.create(original, data); + done(); + }); + + socket.send('tasks::create', original, {}, function () {}); }); - socket.send('tasks::create', original, {}, function () {}); - }); + it('updated', function (done) { + var original = { + name: 'updated event' + }; - it('updated', function (done) { - var original = { - name: 'updated event' - }; + socket.once('tasks updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.once('tasks updated', function (data) { - verify.update(10, original, data); - done(); + socket.send('tasks::update', 10, original, {}, function () {}); }); - socket.send('tasks::update', 10, original, {}, function () {}); - }); + it('patched', function(done) { + var original = { + name: 'patched event' + }; - it('patched', function(done) { - var original = { - name: 'patched event' - }; + socket.once('tasks patched', function (data) { + verify.patch(12, original, data); + done(); + }); - socket.once('tasks patched', function (data) { - verify.patch(12, original, data); - done(); + socket.send('tasks::patch', 12, original, {}, function () {}); }); - socket.send('tasks::patch', 12, original, {}, function () {}); - }); + it('removed', function (done) { + socket.once('tasks removed', function (data) { + verify.remove(333, data); + done(); + }); - it('removed', function (done) { - socket.once('tasks removed', function (data) { - verify.remove(333, data); - done(); + socket.send('tasks::remove', 333, {}, function () {}); }); - - socket.send('tasks::remove', 333, {}, function () {}); }); - }); - describe('Dynamic Service Event filtering', function() { - it('.created', function (done) { - var service = app.service('tasks'); - var original = { description: 'created event test' }; - var oldCreated = service.created; + describe('Event filtering', function() { + it('.created', function (done) { + var service = app.service('tasks'); + var original = { description: 'created event test' }; + var oldCreated = service.created; - service.created = function(data, params, callback) { - assert.deepEqual(params, socketParams); - verify.create(original, data); + service.created = function(data, params, callback) { + assert.deepEqual(params, socketParams); + verify.create(original, data); - callback(null, _.extend({ processed: true }, data)); - }; + callback(null, _.extend({ processed: true }, data)); + }; - socket.send('tasks::create', original, {}, function() {}); + socket.send('tasks::create', original, {}, function() {}); - socket.once('tasks created', function (data) { - service.created = oldCreated; - // Make sure Todo got processed - verify.create(_.extend({ processed: true }, original), data); - done(); + socket.once('tasks created', function (data) { + service.created = oldCreated; + // Make sure Todo got processed + verify.create(_.extend({ processed: true }, original), data); + done(); + }); }); - }); - it('.updated', function (done) { - var original = { - name: 'updated event' - }; + it('.updated', function (done) { + var original = { + name: 'updated event' + }; - socket.once('tasks updated', function (data) { - verify.update(10, original, data); - done(); - }); + socket.once('tasks updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.send('tasks::update', 10, original, {}, function () {}); - }); + socket.send('tasks::update', 10, original, {}, function () {}); + }); - it('.removed', function (done) { - var service = app.service('tasks'); - var oldRemoved = service.removed; + it('.removed', function (done) { + var service = app.service('tasks'); + var oldRemoved = service.removed; - service.removed = function(data, params, callback) { - assert.deepEqual(params, socketParams); + service.removed = function(data, params, callback) { + assert.deepEqual(params, socketParams); - if(data.id === 23) { - // Only dispatch with given id - return callback(null, data); - } + if(data.id === 23) { + // Only dispatch with given id + return callback(null, data); + } - callback(null, false); - }; + callback(null, false); + }; - socket.send('tasks::remove', 1, {}, function() {}); - socket.send('tasks::remove', 23, {}, function() {}); + socket.send('tasks::remove', 1, {}, function() {}); + socket.send('tasks::remove', 23, {}, function() {}); - socket.on('tasks removed', function (data) { - service.removed = oldRemoved; - assert.equal(data.id, 23); - done(); + socket.on('tasks removed', function (data) { + service.removed = oldRemoved; + assert.equal(data.id, 23); + done(); + }); }); }); }); diff --git a/test/providers/rest.test.js b/test/providers/rest.test.js index 8b20d44466..b0f5bcaf43 100644 --- a/test/providers/rest.test.js +++ b/test/providers/rest.test.js @@ -36,188 +36,186 @@ describe('REST provider', function () { server.close(done); }); + describe('Services', function() { - /* * * * * * * * * Services * * * * * * * * */ - - it('GET .find', function (done) { - request('http://localhost:4777/todo', function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.find(JSON.parse(body)); - done(error); + it('GET .find', function (done) { + request('http://localhost:4777/todo', function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.find(JSON.parse(body)); + done(error); + }); }); - }); - it('GET .get', function (done) { - request('http://localhost:4777/todo/dishes', function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.get('dishes', JSON.parse(body)); - done(error); + it('GET .get', function (done) { + request('http://localhost:4777/todo/dishes', function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.get('dishes', JSON.parse(body)); + done(error); + }); }); - }); - it('POST .create', function (done) { - var original = { - description: 'POST .create' - }; + it('POST .create', function (done) { + var original = { + description: 'POST .create' + }; - request({ - url: 'http://localhost:4777/todo', - method: 'post', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 201, 'Got CREATED status code'); - verify.create(original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/todo', + method: 'post', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 201, 'Got CREATED status code'); + verify.create(original, JSON.parse(body)); - done(error); + done(error); + }); }); - }); - it('PUT .update', function (done) { - var original = { - description: 'PUT .update' - }; + it('PUT .update', function (done) { + var original = { + description: 'PUT .update' + }; - request({ - url: 'http://localhost:4777/todo/544', - method: 'put', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.update(544, original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/todo/544', + method: 'put', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.update(544, original, JSON.parse(body)); - done(error); + done(error); + }); }); - }); - it('PATCH .patch', function (done) { - var original = { - description: 'PATCH .patch' - }; + it('PATCH .patch', function (done) { + var original = { + description: 'PATCH .patch' + }; - request({ - url: 'http://localhost:4777/todo/544', - method: 'patch', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.patch(544, original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/todo/544', + method: 'patch', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.patch(544, original, JSON.parse(body)); - done(error); + done(error); + }); }); }); + describe('Dynamic Services', function() { + it('.remove', function (done) { + request({ + url: 'http://localhost:4777/todo/233', + method: 'delete' + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.remove(233, JSON.parse(body)); - /* * * * * * * * * Dynamically-Added Services * * * * * * * * */ - - it('DELETE .remove', function (done) { - request({ - url: 'http://localhost:4777/todo/233', - method: 'delete' - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.remove(233, JSON.parse(body)); - - done(error); + done(error); + }); }); - }); - it('Dynamic GET .find', function (done) { - request('http://localhost:4777/tasks', function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.find(JSON.parse(body)); - done(error); + it('GET .find', function (done) { + request('http://localhost:4777/tasks', function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.find(JSON.parse(body)); + done(error); + }); }); - }); - it('Dynamic GET .get', function (done) { - request('http://localhost:4777/tasks/dishes', function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.get('dishes', JSON.parse(body)); - done(error); + it('GET .get', function (done) { + request('http://localhost:4777/tasks/dishes', function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.get('dishes', JSON.parse(body)); + done(error); + }); }); - }); - it('Dynamic POST .create', function (done) { - var original = { - description: 'Dynamic POST .create' - }; + it('POST .create', function (done) { + var original = { + description: 'Dynamic POST .create' + }; - request({ - url: 'http://localhost:4777/tasks', - method: 'post', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 201, 'Got CREATED status code'); - verify.create(original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/tasks', + method: 'post', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 201, 'Got CREATED status code'); + verify.create(original, JSON.parse(body)); - done(error); + done(error); + }); }); - }); - it('Dynamic PUT .update', function (done) { - var original = { - description: 'Dynamic PUT .update' - }; + it('PUT .update', function (done) { + var original = { + description: 'Dynamic PUT .update' + }; - request({ - url: 'http://localhost:4777/tasks/544', - method: 'put', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.update(544, original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/tasks/544', + method: 'put', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.update(544, original, JSON.parse(body)); - done(error); + done(error); + }); }); - }); - it('Dynamic PATCH .patch', function (done) { - var original = { - description: 'Dynamic PATCH .patch' - }; + it('PATCH .patch', function (done) { + var original = { + description: 'Dynamic PATCH .patch' + }; - request({ - url: 'http://localhost:4777/tasks/544', - method: 'patch', - body: JSON.stringify(original), - headers: { - 'Content-Type': 'application/json' - } - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.patch(544, original, JSON.parse(body)); + request({ + url: 'http://localhost:4777/tasks/544', + method: 'patch', + body: JSON.stringify(original), + headers: { + 'Content-Type': 'application/json' + } + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.patch(544, original, JSON.parse(body)); - done(error); + done(error); + }); }); - }); - it('Dynamic DELETE .remove', function (done) { - request({ - url: 'http://localhost:4777/tasks/233', - method: 'delete' - }, function (error, response, body) { - assert.ok(response.statusCode === 200, 'Got OK status code'); - verify.remove(233, JSON.parse(body)); + it('DELETE .remove', function (done) { + request({ + url: 'http://localhost:4777/tasks/233', + method: 'delete' + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + verify.remove(233, JSON.parse(body)); - done(error); + done(error); + }); }); }); - /* * * End of Dynamically-Added Tests * * */ }); describe('HTTP status codes', function() { diff --git a/test/providers/socketio.test.js b/test/providers/socketio.test.js index 6c7812d87f..a6a3fb2148 100644 --- a/test/providers/socketio.test.js +++ b/test/providers/socketio.test.js @@ -91,356 +91,354 @@ describe('SocketIO provider', function () { }); }); + describe('Services', function() { + describe('CRUD', function () { + it('::find', function (done) { + socket.emit('todo::find', {}, function (error, data) { + verify.find(data); -/* * * * * * * * * Services * * * * * * * * */ - - describe('CRUD', function () { - it('::find', function (done) { - socket.emit('todo::find', {}, function (error, data) { - verify.find(data); - - done(error); + done(error); + }); }); - }); - it('::get', function (done) { - socket.emit('todo::get', 'laundry', {}, function (error, data) { - verify.get('laundry', data); + it('::get', function (done) { + socket.emit('todo::get', 'laundry', {}, function (error, data) { + verify.get('laundry', data); - done(error); + done(error); + }); }); - }); - it('::create', function (done) { - var original = { - name: 'creating' - }; + it('::create', function (done) { + var original = { + name: 'creating' + }; - socket.emit('todo::create', original, {}, function (error, data) { - verify.create(original, data); + socket.emit('todo::create', original, {}, function (error, data) { + verify.create(original, data); - done(error); + done(error); + }); }); - }); - it('::update', function (done) { - var original = { - name: 'updating' - }; + it('::update', function (done) { + var original = { + name: 'updating' + }; - socket.emit('todo::update', 23, original, {}, function (error, data) { - verify.update(23, original, data); + socket.emit('todo::update', 23, original, {}, function (error, data) { + verify.update(23, original, data); - done(error); + done(error); + }); }); - }); - it('::patch', function (done) { - var original = { - name: 'patching' - }; + it('::patch', function (done) { + var original = { + name: 'patching' + }; - socket.emit('todo::patch', 25, original, {}, function (error, data) { - verify.patch(25, original, data); + socket.emit('todo::patch', 25, original, {}, function (error, data) { + verify.patch(25, original, data); - done(error); + done(error); + }); }); - }); - it('::remove', function (done) { - socket.emit('todo::remove', 11, {}, function (error, data) { - verify.remove(11, data); + it('::remove', function (done) { + socket.emit('todo::remove', 11, {}, function (error, data) { + verify.remove(11, data); - done(error); + done(error); + }); }); }); - }); - describe('Events', function () { - it('created', function (done) { - var original = { - name: 'created event' - }; + describe('Events', function () { + it('created', function (done) { + var original = { + name: 'created event' + }; - socket.once('todo created', function (data) { - verify.create(original, data); - done(); + socket.once('todo created', function (data) { + verify.create(original, data); + done(); + }); + + socket.emit('todo::create', original, {}, function () {}); }); - socket.emit('todo::create', original, {}, function () {}); - }); + it('updated', function (done) { + var original = { + name: 'updated event' + }; - it('updated', function (done) { - var original = { - name: 'updated event' - }; + socket.once('todo updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.once('todo updated', function (data) { - verify.update(10, original, data); - done(); + socket.emit('todo::update', 10, original, {}, function () {}); }); - socket.emit('todo::update', 10, original, {}, function () {}); - }); + it('patched', function(done) { + var original = { + name: 'patched event' + }; - it('patched', function(done) { - var original = { - name: 'patched event' - }; + socket.once('todo patched', function (data) { + verify.patch(12, original, data); + done(); + }); - socket.once('todo patched', function (data) { - verify.patch(12, original, data); - done(); + socket.emit('todo::patch', 12, original, {}, function () {}); }); - socket.emit('todo::patch', 12, original, {}, function () {}); - }); + it('removed', function (done) { + socket.once('todo removed', function (data) { + verify.remove(333, data); + done(); + }); - it('removed', function (done) { - socket.once('todo removed', function (data) { - verify.remove(333, data); - done(); + socket.emit('todo::remove', 333, {}, function () {}); }); - - socket.emit('todo::remove', 333, {}, function () {}); }); - }); - describe('Event filtering', function() { - it('.created', function (done) { - var service = app.service('todo'); - var original = { description: 'created event test' }; - var oldCreated = service.created; + describe('Event filtering', function() { + it('.created', function (done) { + var service = app.service('todo'); + var original = { description: 'created event test' }; + var oldCreated = service.created; - service.created = function(data, params, callback) { - assert.deepEqual(params, socketParams); - verify.create(original, data); + service.created = function(data, params, callback) { + assert.deepEqual(params, socketParams); + verify.create(original, data); - callback(null, _.extend({ processed: true }, data)); - }; + callback(null, _.extend({ processed: true }, data)); + }; - socket.emit('todo::create', original, {}, function() {}); + socket.emit('todo::create', original, {}, function() {}); - socket.once('todo created', function (data) { - service.created = oldCreated; - // Make sure Todo got processed - verify.create(_.extend({ processed: true }, original), data); - done(); + socket.once('todo created', function (data) { + service.created = oldCreated; + // Make sure Todo got processed + verify.create(_.extend({ processed: true }, original), data); + done(); + }); }); - }); - it('.updated', function (done) { - var original = { - name: 'updated event' - }; + it('.updated', function (done) { + var original = { + name: 'updated event' + }; - socket.once('todo updated', function (data) { - verify.update(10, original, data); - done(); - }); + socket.once('todo updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.emit('todo::update', 10, original, {}, function () {}); - }); + socket.emit('todo::update', 10, original, {}, function () {}); + }); - it('.removed', function (done) { - var service = app.service('todo'); - var oldRemoved = service.removed; + it('.removed', function (done) { + var service = app.service('todo'); + var oldRemoved = service.removed; - service.removed = function(data, params, callback) { - assert.deepEqual(params, socketParams); + service.removed = function(data, params, callback) { + assert.deepEqual(params, socketParams); - if(data.id === 23) { - // Only dispatch with given id - return callback(null, data); - } + if(data.id === 23) { + // Only dispatch with given id + return callback(null, data); + } - callback(null, false); - }; + callback(null, false); + }; - socket.emit('todo::remove', 1, {}, function() {}); - socket.emit('todo::remove', 23, {}, function() {}); + socket.emit('todo::remove', 1, {}, function() {}); + socket.emit('todo::remove', 23, {}, function() {}); - socket.on('todo removed', function (data) { - service.removed = oldRemoved; - assert.equal(data.id, 23); - done(); + socket.on('todo removed', function (data) { + service.removed = oldRemoved; + assert.equal(data.id, 23); + done(); + }); }); }); }); + describe('Dynamic Services', function() { + describe('CRUD', function () { + it('::find', function (done) { + socket.emit('tasks::find', {}, function (error, data) { + verify.find(data); -/* * * * * * * * * Dynamically-Added Services * * * * * * * * */ - - describe('Dynamic Service CRUD', function () { - it('::find', function (done) { - socket.emit('tasks::find', {}, function (error, data) { - verify.find(data); - - done(error); + done(error); + }); }); - }); - it('::get', function (done) { - socket.emit('tasks::get', 'laundry', {}, function (error, data) { - verify.get('laundry', data); + it('::get', function (done) { + socket.emit('tasks::get', 'laundry', {}, function (error, data) { + verify.get('laundry', data); - done(error); + done(error); + }); }); - }); - it('::create', function (done) { - var original = { - name: 'creating' - }; + it('::create', function (done) { + var original = { + name: 'creating' + }; - socket.emit('tasks::create', original, {}, function (error, data) { - verify.create(original, data); + socket.emit('tasks::create', original, {}, function (error, data) { + verify.create(original, data); - done(error); + done(error); + }); }); - }); - it('::update', function (done) { - var original = { - name: 'updating' - }; + it('::update', function (done) { + var original = { + name: 'updating' + }; - socket.emit('tasks::update', 23, original, {}, function (error, data) { - verify.update(23, original, data); + socket.emit('tasks::update', 23, original, {}, function (error, data) { + verify.update(23, original, data); - done(error); + done(error); + }); }); - }); - it('::patch', function (done) { - var original = { - name: 'patching' - }; + it('::patch', function (done) { + var original = { + name: 'patching' + }; - socket.emit('tasks::patch', 25, original, {}, function (error, data) { - verify.patch(25, original, data); + socket.emit('tasks::patch', 25, original, {}, function (error, data) { + verify.patch(25, original, data); - done(error); + done(error); + }); }); - }); - it('::remove', function (done) { - socket.emit('tasks::remove', 11, {}, function (error, data) { - verify.remove(11, data); + it('::remove', function (done) { + socket.emit('tasks::remove', 11, {}, function (error, data) { + verify.remove(11, data); - done(error); + done(error); + }); }); }); - }); - describe('Dynamic Service Events', function () { - it('created', function (done) { - var original = { - name: 'created event' - }; + describe('Events', function () { + it('created', function (done) { + var original = { + name: 'created event' + }; - socket.once('tasks created', function (data) { - verify.create(original, data); - done(); + socket.once('tasks created', function (data) { + verify.create(original, data); + done(); + }); + + socket.emit('tasks::create', original, {}, function () {}); }); - socket.emit('tasks::create', original, {}, function () {}); - }); + it('updated', function (done) { + var original = { + name: 'updated event' + }; - it('updated', function (done) { - var original = { - name: 'updated event' - }; + socket.once('tasks updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.once('tasks updated', function (data) { - verify.update(10, original, data); - done(); + socket.emit('tasks::update', 10, original, {}, function () {}); }); - socket.emit('tasks::update', 10, original, {}, function () {}); - }); + it('patched', function(done) { + var original = { + name: 'patched event' + }; - it('patched', function(done) { - var original = { - name: 'patched event' - }; + socket.once('tasks patched', function (data) { + verify.patch(12, original, data); + done(); + }); - socket.once('tasks patched', function (data) { - verify.patch(12, original, data); - done(); + socket.emit('tasks::patch', 12, original, {}, function () {}); }); - socket.emit('tasks::patch', 12, original, {}, function () {}); - }); + it('removed', function (done) { + socket.once('tasks removed', function (data) { + verify.remove(333, data); + done(); + }); - it('removed', function (done) { - socket.once('tasks removed', function (data) { - verify.remove(333, data); - done(); + socket.emit('tasks::remove', 333, {}, function () {}); }); - - socket.emit('tasks::remove', 333, {}, function () {}); }); - }); - describe('Dynamic Service Event Filtering', function() { - it('.created', function (done) { - var service = app.service('tasks'); - var original = { description: 'created event test' }; - var oldCreated = service.created; + describe('Event Filtering', function() { + it('.created', function (done) { + var service = app.service('tasks'); + var original = { description: 'created event test' }; + var oldCreated = service.created; - service.created = function(data, params, callback) { - assert.deepEqual(params, socketParams); - verify.create(original, data); + service.created = function(data, params, callback) { + assert.deepEqual(params, socketParams); + verify.create(original, data); - callback(null, _.extend({ processed: true }, data)); - }; + callback(null, _.extend({ processed: true }, data)); + }; - socket.emit('tasks::create', original, {}, function() {}); + socket.emit('tasks::create', original, {}, function() {}); - socket.once('tasks created', function (data) { - service.created = oldCreated; - // Make sure Todo got processed - verify.create(_.extend({ processed: true }, original), data); - done(); + socket.once('tasks created', function (data) { + service.created = oldCreated; + // Make sure Todo got processed + verify.create(_.extend({ processed: true }, original), data); + done(); + }); }); - }); - it('.updated', function (done) { - var original = { - name: 'updated event' - }; + it('.updated', function (done) { + var original = { + name: 'updated event' + }; - socket.once('tasks updated', function (data) { - verify.update(10, original, data); - done(); - }); + socket.once('tasks updated', function (data) { + verify.update(10, original, data); + done(); + }); - socket.emit('tasks::update', 10, original, {}, function () {}); - }); + socket.emit('tasks::update', 10, original, {}, function () {}); + }); - it('.removed', function (done) { - var service = app.service('tasks'); - var oldRemoved = service.removed; + it('.removed', function (done) { + var service = app.service('tasks'); + var oldRemoved = service.removed; - service.removed = function(data, params, callback) { - assert.deepEqual(params, socketParams); + service.removed = function(data, params, callback) { + assert.deepEqual(params, socketParams); - if(data.id === 23) { - // Only dispatch with given id - return callback(null, data); - } + if(data.id === 23) { + // Only dispatch with given id + return callback(null, data); + } - callback(null, false); - }; + callback(null, false); + }; - socket.emit('tasks::remove', 1, {}, function() {}); - socket.emit('tasks::remove', 23, {}, function() {}); + socket.emit('tasks::remove', 1, {}, function() {}); + socket.emit('tasks::remove', 23, {}, function() {}); - socket.on('tasks removed', function (data) { - service.removed = oldRemoved; - assert.equal(data.id, 23); - done(); + socket.on('tasks removed', function (data) { + service.removed = oldRemoved; + assert.equal(data.id, 23); + done(); + }); }); }); }); From aea0284892babec868491832232ba30b516137a2 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sat, 21 Feb 2015 16:39:18 -0700 Subject: [PATCH 4/5] Easier hasMethod check. --- lib/application.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/application.js b/lib/application.js index 9a3c92f93b..4b2012ce37 100644 --- a/lib/application.js +++ b/lib/application.js @@ -62,14 +62,14 @@ module.exports = { var args = _.toArray(arguments); var location = args.shift(); var service = args.pop(); - var hasMethod = function() { - return _.some(arguments, function(name) { + var hasMethod = function(methods) { + return _.some(methods, function(name) { return (service && typeof service[name] === 'function'); }); }; // Check for service (any object with at least one service method) - if(hasMethod('handle', 'set') || !hasMethod.apply(null, this.methods)) { + if(hasMethod(['handle', 'set']) || !hasMethod(this.methods)) { return this._super.apply(this, arguments); } From b368eefc211327473c6d3cdfb4a0632c68286164 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 22 Feb 2015 23:32:24 -0700 Subject: [PATCH 5/5] Refactoring for better handling of dynamic services and socket connections. --- lib/application.js | 10 +-- lib/mixins/event.js | 6 +- lib/providers/socket/commons.js | 128 +++++++++++++++++++------------ lib/providers/socket/primus.js | 40 +++------- lib/providers/socket/socketio.js | 39 +++------- 5 files changed, 105 insertions(+), 118 deletions(-) diff --git a/lib/application.js b/lib/application.js index 4b2012ce37..149a523002 100644 --- a/lib/application.js +++ b/lib/application.js @@ -45,13 +45,9 @@ module.exports = { provider(location, protoService, options || {}); }); - // If already _setup, just add this single service. - if (this._setup) { + // If we ran setup already, set this service up explicitly + if (this._isSetup) { protoService.setup(this, location); - // If we're using a socket provider, register the service on it. - if (this.addService) { - this.addService(protoService, location); - } } this.services[location] = protoService; @@ -89,7 +85,7 @@ module.exports = { } }.bind(this)); - this._setup = true; + this._isSetup = true; return this; }, diff --git a/lib/mixins/event.js b/lib/mixins/event.js index f17642f53c..bf34382362 100644 --- a/lib/mixins/event.js +++ b/lib/mixins/event.js @@ -17,10 +17,6 @@ var eventMappings = { */ var EventMixin = { setup: function () { - this.applyEvents(); - return this._super ? this._super.apply(this, arguments) : this; - }, - applyEvents:function(){ var emitter = this._rubberDuck = rubberduck.emitter(this); var self = this; @@ -49,6 +45,8 @@ var EventMixin = { }); } }); + + return this._super ? this._super.apply(this, arguments) : this; } }; diff --git a/lib/providers/socket/commons.js b/lib/providers/socket/commons.js index 7de8dc32d6..dd34082e41 100644 --- a/lib/providers/socket/commons.js +++ b/lib/providers/socket/commons.js @@ -1,75 +1,105 @@ -'use strict'; - var _ = require('lodash'); // The position of the params parameters for a service method so that we can extend them // default is 1 -var paramsPositions = { +exports.paramsPositions = { find: 0, update: 2, patch: 2 }; -exports.addService = function addService(service, path){ - // Add handlers for the service to connected sockets. - _.each(this.info.connections, function (spark) { - this.setupMethodHandlers(service, path, spark); - }, this); +// The default event dispatcher +exports.defaultDispatcher = function (data, params, callback) { + callback(null, data); +}; + +// Set up event handlers for a given service using the event dispatching mechanism +exports.setupEventHandlers = function (info, service, path) { + // If the service emits events that we want to listen to (Event mixin) + if (typeof service.on === 'function' && service._serviceEvents) { + var addEvent = function (ev) { + service.on(ev, function (data) { + // Check if there is a method on the service with the same name as the event + var dispatcher = typeof service[ev] === 'function' ? + service[ev] : exports.defaultDispatcher; + var eventName = path + ' ' + ev; + + info.clients().forEach(function (socket) { + dispatcher(data, info.params(socket), function (error, dispatchData) { + if (error) { + socket[info.method]('error', error); + } else if (dispatchData) { // Only dispatch if we have data + socket[info.method](eventName, dispatchData); + } + }); + }); + }); + }; - // Setup events for the service. - exports.setupEventHandlers.call(this, service, path); + _.each(service._serviceEvents, addEvent); + } }; -// Set up the service method handler for a service and socket. -exports.setupMethodHandler = function setupMethodHandler(emitter, params, service, path, method) { - var name = path + '::' + method; - var position = typeof paramsPositions[method] !== 'undefined' ? paramsPositions[method] : 1; +// Set up all method handlers for a service and socket. +exports.setupMethodHandlers = function (info, socket, service, path) { + this.methods.forEach(function (method) { + if (typeof service[method] !== 'function') { + return; + } + + var name = path + '::' + method; + var params = info.params(socket); + var position = typeof exports.paramsPositions[method] !== 'undefined' ? + exports.paramsPositions[method] : 1; - if (typeof service[method] === 'function') { - emitter.on(name, function () { + socket.on(name, function () { var args = _.toArray(arguments); // If the service is called with no parameter object // insert an empty object - if(typeof args[position] === 'function') { + if (typeof args[position] === 'function') { args.splice(position, 0, {}); } - args[position] = _.extend({ query: args[position] }, params); + args[position] = _.extend({query: args[position]}, params); service[method].apply(service, args); }); - } + }); }; -exports.setupEventHandlers = function setupEventHandlers(service, path){ - // If the service emits events that we want to listen to (Event mixin) - if (typeof service.on === 'function' && service._serviceEvents) { - _.each(service._serviceEvents, function (ev) { - exports.setupEventHandler(this.info, service, path, ev); - }, this); - } +// Common setup functionality taking the info object which abstracts websocket access +exports.setup = function (info) { + var app = this; + var setupEventHandlers = exports.setupEventHandlers.bind(this, info); + + app._commons = info; + + // For a new connection, set up the service method handlers + info.connection().on('connection', function (socket) { + var setupMethodHandlers = exports.setupMethodHandlers.bind(app, info, socket); + // Process all registered services + _.each(app.services, setupMethodHandlers); + }); + + // Set up events and event dispatching + _.each(app.services, setupEventHandlers); }; -// Set up event handlers for a given service and connected sockets. -// Send it through the service dispatching mechanism (`removed(data, params, callback)`, -// `updated(data, params, callback)` and `created(data, params, callback)`) if it -// exists. -exports.setupEventHandler = function setupEventHandler (info, service, path, ev) { - var defaultDispatcher = function (data, params, callback) { - callback(null, data); - }; - - service.on(ev, function (data) { - // Check if there is a method on the service with the same name as the event - var dispatcher = typeof service[ev] === 'function' ? service[ev] : defaultDispatcher; - var eventName = path + ' ' + ev; - - info.emitters().forEach(function (emitter) { - dispatcher(data, info.params(emitter), function (error, dispatchData) { - if (error) { - emitter[info.method]('error', error); - } else if (dispatchData) { - emitter[info.method](eventName, dispatchData); - } - }); +// Socket mixin when a new service is registered +exports.service = function (path, service) { + var protoService = this._super.apply(this, arguments); + var info = this._commons; + + // app._socketInfo will only be available once we are set up + if (service && info) { + var setupEventHandlers = exports.setupEventHandlers.bind(this, info); + var setupMethodHandlers = exports.setupMethodHandlers.bind(this, info); + + // Set up event handlers for this new service + setupEventHandlers(protoService, path); + // For any existing connection add method handlers + info.clients().forEach(function (socket) { + setupMethodHandlers(socket, path, protoService); }); - }); + } + + return protoService; }; diff --git a/lib/providers/socket/primus.js b/lib/providers/socket/primus.js index 423999a8bc..93b3bd451a 100644 --- a/lib/providers/socket/primus.js +++ b/lib/providers/socket/primus.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var Proto = require('uberproto'); var Primus = require('primus'); var Emitter = require('primus-emitter'); @@ -14,8 +13,9 @@ module.exports = function(config, configurer) { // Monkey patch app.setup(server) Proto.mixin({ + service: commons.service, + setup: function(server) { - var self = this; var result = this._super.apply(this, arguments); if (this.disabled('feathers primus')) { @@ -23,45 +23,27 @@ module.exports = function(config, configurer) { } var primus = this.primus = new Primus(server, config); - this.info = { - emitters: function() { + + commons.setup.call(this, { + method: 'send', + connection: function() { + return primus; + }, + clients: function() { return primus; }, params: function(spark) { return spark.request.feathers; - }, - method: 'send', - connections: this.primus.connections - }; - - primus.use('emitter', Emitter); - - // For a new connection, set up the service method handlers - primus.on('connection', function (spark) { - // Process services that were registered at startup. - _.each(self.services, function (service, path) { - self.setupMethodHandlers.call(self, service, path, spark); - }); + } }); - // Set up events and event dispatching - _.each(this.services, function (service, path) { - commons.setupEventHandlers.call(this, service, path); - }, this); + primus.use('emitter', Emitter); if (typeof configurer === 'function') { configurer.call(this, primus); } return result; - }, - - addService: commons.addService, - - setupMethodHandlers: function(service, path, spark){ - _.each(this.methods, function (method) { - commons.setupMethodHandler(spark, spark.request.feathers, service, path, method); - }, this); } }, app); }; diff --git a/lib/providers/socket/socketio.js b/lib/providers/socket/socketio.js index 1691fff79e..a744195b78 100644 --- a/lib/providers/socket/socketio.js +++ b/lib/providers/socket/socketio.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var socketio = require('socket.io'); var Proto = require('uberproto'); var commons = require('./commons'); @@ -13,8 +12,9 @@ module.exports = function (config) { // Monkey patch app.setup(server) Proto.mixin({ + service: commons.service, + setup: function (server) { - var self = this; var result = this._super.apply(this, arguments); if (this.disabled('feathers socketio')) { @@ -22,44 +22,25 @@ module.exports = function (config) { } var io = this.io = socketio.listen(server); - // The info object we can pass to commons.setupEventHandler - this.info = { - emitters: function() { + + commons.setup.call(this, { + method: 'emit', + connection: function() { + return io.sockets; + }, + clients: function() { return io.sockets.sockets; }, params: function(socket) { return socket.feathers; - }, - method: 'emit', - connections: this.connections = this.io.sockets.connected - }; - - // For a new connection, set up the service method handlers - io.sockets.on('connection', function (socket) { - // Process services that were registered at startup. - _.each(self.services, function (service, path) { - self.setupMethodHandlers.call(self, service, path, socket); - }); + } }); - // Set up events and event dispatching - _.each(self.services, function (service, path) { - commons.setupEventHandlers.call(this, service, path); - }, this); - if (typeof config === 'function') { config.call(this, io); } return result; - }, - - addService: commons.addService, - - setupMethodHandlers: function(service, path, socket){ - _.each(this.methods, function (method) { - commons.setupMethodHandler(socket, socket.feathers || {}, service, path, method); - }, this); } }, app); };