From 1fb366d11cdee26e7b995e765574c9664fde0356 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez <=> Date: Fri, 20 Apr 2018 12:53:30 +0200 Subject: [PATCH 1/4] #190 install crud model backend unit test CR Mr Joan --- .../methods/sale/saleComponentFilter.js | 24 ++++---- .../methods/vnModel/installCrudModel.js | 26 ++++---- .../vnModel/specs/installCrudModel.spec.js | 59 ++++++++++++++++--- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/services/loopback/common/methods/sale/saleComponentFilter.js b/services/loopback/common/methods/sale/saleComponentFilter.js index ed9472574..7d730514b 100644 --- a/services/loopback/common/methods/sale/saleComponentFilter.js +++ b/services/loopback/common/methods/sale/saleComponentFilter.js @@ -10,36 +10,36 @@ module.exports = Self => { limit: params.size, order: params.order || 'concept ASC', include: [{ - relation: "item", + relation: 'item', scope: { include: { - relation: "itemTag", + relation: 'itemTag', scope: { - fields: ["tagFk", "value"], + fields: ['tagFk', 'value'], include: { - relation: "tag", + relation: 'tag', scope: { - fields: ["name"] + fields: ['name'] } }, limit: 6 } }, - fields: ["itemFk", "name"] + fields: ['itemFk', 'name'] } }, { - relation: "components", + relation: 'components', scope: { - fields: ["componentFk", "value"], + fields: ['componentFk', 'value'], include: { - relation: "componentRate", + relation: 'componentRate', scope: { - fields: ["componentTypeRate", "name"], + fields: ['componentTypeRate', 'name'], include: { - relation: "componentType", + relation: 'componentType', scope: { - fields: ["type"] + fields: ['type'] } } } diff --git a/services/loopback/common/methods/vnModel/installCrudModel.js b/services/loopback/common/methods/vnModel/installCrudModel.js index c45f4817d..5ec2ab997 100644 --- a/services/loopback/common/methods/vnModel/installCrudModel.js +++ b/services/loopback/common/methods/vnModel/installCrudModel.js @@ -18,28 +18,28 @@ module.exports = function(Self) { verb: 'post' } }); - Model[methodName] = async crudStruct => { + Model[methodName] = async crudObject => { let promises = []; - let tx = await Model.beginTransaction({}); - let options = {transaction: tx}; + let transaction = await Model.beginTransaction({}); + let options = {transaction: transaction}; try { - if (crudStruct.delete && crudStruct.delete.length) { - promises.push(Model.destroyAll({id: {inq: crudStruct.delete}}, options)); + if (crudObject.delete && crudObject.delete.length) { + promises.push(Model.destroyAll({id: {inq: crudObject.delete}}, options)); } - if (crudStruct.create.length) { - promises.push(Model.create(crudStruct.create, options)); + if (crudObject.create.length) { + promises.push(Model.create(crudObject.create, options)); } - if (crudStruct.update.length) { - crudStruct.update.forEach(toUpdate => { + if (crudObject.update.length) { + crudObject.update.forEach(toUpdate => { promises.push(Model.upsert(toUpdate, options)); }); } await Promise.all(promises); - await tx.commit(); - } catch (e) { - await tx.rollback(); - throw Array.isArray(e) ? e[0] : e; + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw Array.isArray(error) ? error[0] : error; } }; }; diff --git a/services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js b/services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js index b31eb82fb..3e9bdb6d3 100644 --- a/services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js +++ b/services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js @@ -1,17 +1,60 @@ -// const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors; const app = require('../../../../../item/server/server'); describe('Model installCrudModel()', () => { - it('all models extends installCrudModel propertie', () => { - let someModel = app.models.Item; + it('should extend installCrudModel properties to any model passed', () => { + let exampleModel = app.models.ItemBarcode; - expect(someModel.installCrudModel).toBeDefined(); + expect(exampleModel.installCrudModel).toBeDefined(); }); - it('installCrudModel() create a new remothed method', () => { - let someModel = app.models.Item; - someModel.installCrudModel('someCrudMethod'); + describe('installCrudModel()', () => { + it('should create a new remothed method', () => { + let exampleModel = app.models.ItemBarcode; + exampleModel.installCrudModel('crudItemBarcodes'); - expect(someModel.someCrudMethod).toBeDefined(); + expect(exampleModel.crudItemBarcodes).toBeDefined(); + }); + }); + + describe('ItemBarcode crudMethod()', () => { + let createdId; + it('should create a new barcode', async() => { + crudObject = { + create: [{code: '500', itemFk: '1'}], + update: [], + delete: [] + }; + await app.models.ItemBarcode.crudItemBarcodes(crudObject); + let result = await app.models.ItemBarcode.find({where: {itemFk: 1}}); + createdId = result[3].id; + + expect(result[3].code).toEqual('500'); + expect(result.length).toEqual(4); + }); + + it('should update a barcode', async() => { + crudObject = { + create: [], + update: [{id: createdId, code: '501', itemFk: 1}], + delete: [] + }; + await app.models.ItemBarcode.crudItemBarcodes(crudObject); + let result = await app.models.ItemBarcode.find({where: {itemFk: 1}}); + + expect(result[3].code).toEqual('501'); + expect(result.length).toEqual(4); + }); + + it('should delete a barcode', async() => { + crudObject = { + create: [], + update: [], + delete: [createdId] + }; + await app.models.ItemBarcode.crudItemBarcodes(crudObject); + let result = await app.models.ItemBarcode.find({where: {itemFk: 1}}); + + expect(result.length).toEqual(3); + }); }); }); From 12e67b7b60f5b25902ccde09108ad91947bd8674 Mon Sep 17 00:00:00 2001 From: Joan Date: Fri, 20 Apr 2018 15:16:03 +0200 Subject: [PATCH 2/4] Added method to send message to salesPerson on credit classification. --- .../createWithInsurance.js | 18 +++++-- .../common/models/credit-classification.json | 2 +- .../client/common/models/credit-insurance.js | 47 +++++++++++++++++++ .../loopback/common/methods/message/send.js | 40 ++++++++++++++++ .../common/methods/message/specs/send.spec.js | 12 +++++ services/loopback/common/models/message.js | 3 ++ services/loopback/common/models/message.json | 39 +++++++++++++++ services/loopback/common/models/worker.json | 4 ++ services/loopback/server/model-config.json | 3 ++ 9 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 services/loopback/common/methods/message/send.js create mode 100644 services/loopback/common/methods/message/specs/send.spec.js create mode 100644 services/loopback/common/models/message.js create mode 100644 services/loopback/common/models/message.json diff --git a/services/client/common/methods/creditClassification/createWithInsurance.js b/services/client/common/methods/creditClassification/createWithInsurance.js index d22dd9113..1d4a0abe6 100644 --- a/services/client/common/methods/creditClassification/createWithInsurance.js +++ b/services/client/common/methods/creditClassification/createWithInsurance.js @@ -1,11 +1,17 @@ module.exports = function(Self) { Self.remoteMethod('createWithInsurance', { description: 'Creates both classification and one insurance', - accepts: { + accepts: [{ arg: 'data', type: 'object', http: {source: 'body'} - }, + }, { + arg: 'context', + type: 'object', + http: function(ctx) { + return ctx; + } + }], returns: { root: true, type: 'boolean' @@ -16,21 +22,23 @@ module.exports = function(Self) { } }); - Self.createWithInsurance = async data => { + Self.createWithInsurance = async (data, ctx) => { let transaction = await Self.beginTransaction({}); try { let classificationSchema = {client: data.clientFk, started: data.started}; let newClassification = await Self.create(classificationSchema, {transaction}); + let CreditInsurance = Self.app.models.CreditInsurance; let insuranceSchema = { creditClassification: newClassification.id, credit: data.credit, grade: data.grade }; - Self.app.models.CreditInsurance.create(insuranceSchema, {transaction}); - + let newCreditInsurance = await CreditInsurance.create(insuranceSchema, {transaction}); await transaction.commit(); + await CreditInsurance.messageSend(newCreditInsurance, ctx.req.accessToken); + return newClassification; } catch (e) { transaction.rollback(); diff --git a/services/client/common/models/credit-classification.json b/services/client/common/models/credit-classification.json index 74edb6640..51bda330a 100644 --- a/services/client/common/models/credit-classification.json +++ b/services/client/common/models/credit-classification.json @@ -28,7 +28,7 @@ } }, "relations": { - "client": { + "customer": { "type": "belongsTo", "model": "Client", "foreignKey": "client" diff --git a/services/client/common/models/credit-insurance.js b/services/client/common/models/credit-insurance.js index 155d4de44..63fe8bfaa 100644 --- a/services/client/common/models/credit-insurance.js +++ b/services/client/common/models/credit-insurance.js @@ -19,4 +19,51 @@ module.exports = function(Self) { message: 'The grade must be an integer greater than or equal to zero', allowNull: true }); + + Self.messageSend = async function(data, accessToken) { + let filter = { + include: { + relation: 'classification', + scope: { + fields: ['client'], + include: { + relation: 'customer', + scope: { + fields: ['name', 'salesPersonFk'], + include: { + relation: 'salesPerson', + scope: { + fields: 'userFk', + include: { + relation: 'user', + scope: { + fields: ['name'] + } + } + } + } + } + } + } + } + }; + + let ctx = {req: {accessToken: accessToken}}; + let insurance = await Self.findById(data.id, filter); + let customer = insurance.classification().customer(); + let salesPerson = customer.salesPerson().user().name; + let grade = data.grade ? `(Grado ${data.grade})` : '(Sin grado)'; + let message = { + message: `He cambiado el crédito asegurado del cliente "${customer.name}" a ${data.credit} € ${grade}` + }; + + Self.app.models.Message.send(salesPerson, message, ctx); + }; + + // Update from transaction misses ctx accessToken. + // Fixed passing accessToken from method messageSend() + Self.observe('after save', async function(ctx) { + if (ctx.options.accessToken) + await Self.messageSend(ctx.instance, ctx.options.accessToken); + }); }; diff --git a/services/loopback/common/methods/message/send.js b/services/loopback/common/methods/message/send.js new file mode 100644 index 000000000..0a918a1d2 --- /dev/null +++ b/services/loopback/common/methods/message/send.js @@ -0,0 +1,40 @@ +module.exports = Self => { + Self.remoteMethod('send', { + description: 'Send message to user', + accessType: 'WRITE', + accepts: [{ + arg: 'recipient', + type: 'string', + required: true, + description: 'The user/alias name', + http: {source: 'path'} + }, { + arg: 'data', + type: 'object', + required: true, + description: 'Message data', + http: {source: 'body'} + }, { + arg: 'context', + type: 'object', + http: function(ctx) { + return ctx; + } + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/:recipient/send`, + verb: 'post' + } + }); + + Self.send = async (recipient, data, ctx) => { + let query = `SELECT vn.messageSendWithUser(?, ?, ?) AS sent`; + let [result] = await Self.rawSql(query, [ctx.req.accessToken.userId, recipient, data.message]); + + return result; + }; +}; diff --git a/services/loopback/common/methods/message/specs/send.spec.js b/services/loopback/common/methods/message/specs/send.spec.js new file mode 100644 index 000000000..ad5ae54c1 --- /dev/null +++ b/services/loopback/common/methods/message/specs/send.spec.js @@ -0,0 +1,12 @@ +const app = require(`${servicesDir}/client/server/server`); + +describe('message send()', () => { + it('should call the send method and return the response', done => { + let ctx = {req: {accessToken: {userId: 1}}}; + app.models.Message.send('salesPerson', {message: 'I changed something'}, ctx) + .then(response => { + expect(response.sent).toEqual(1); + done(); + }); + }); +}); diff --git a/services/loopback/common/models/message.js b/services/loopback/common/models/message.js new file mode 100644 index 000000000..ecebfb5d3 --- /dev/null +++ b/services/loopback/common/models/message.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/message/send.js')(Self); +} \ No newline at end of file diff --git a/services/loopback/common/models/message.json b/services/loopback/common/models/message.json new file mode 100644 index 000000000..2a855c907 --- /dev/null +++ b/services/loopback/common/models/message.json @@ -0,0 +1,39 @@ +{ + "name": "Message", + "base": "VnModel", + "options": { + "mysql": { + "table": "message" + } + }, + "properties": { + "id": { + "type": "Number", + "id": true, + "description": "Identifier" + }, + "sender": { + "type": "String", + "required": true + }, + "recipient": { + "type": "String", + "required": true + }, + "message": { + "type": "String" + } + }, + "relations": { + "remitter": { + "type": "belongsTo", + "model": "User", + "foreignKey": "sender" + }, + "receptor": { + "type": "belongsTo", + "model": "User", + "foreignKey": "recipient" + } + } +} \ No newline at end of file diff --git a/services/loopback/common/models/worker.json b/services/loopback/common/models/worker.json index f8399be8d..81807e6f6 100644 --- a/services/loopback/common/models/worker.json +++ b/services/loopback/common/models/worker.json @@ -20,6 +20,10 @@ "name": { "type": "string", "required": true + }, + "userFk": { + "type" : "Number", + "required": true } }, "relations": { diff --git a/services/loopback/server/model-config.json b/services/loopback/server/model-config.json index a46e46ea0..f97479309 100644 --- a/services/loopback/server/model-config.json +++ b/services/loopback/server/model-config.json @@ -101,5 +101,8 @@ }, "Producer": { "dataSource": "vn" + }, + "Message": { + "dataSource": "vn" } } From 27a08dc2a1e7be7a06a6165d8c0928ec90f2cb1f Mon Sep 17 00:00:00 2001 From: Carlos Jimenez <=> Date: Sun, 22 Apr 2018 16:25:44 +0200 Subject: [PATCH 3/4] #186 cancel button + e2e path refactor CR pending --- .../src/address-create/address-create.html | 6 ++++++ client/client/src/create/create.html | 6 ++++++ client/item/src/create/create.html | 6 ++++++ e2e/helpers/selectors.js | 9 ++++++--- .../client-module/01_create_client.spec.js | 20 +++++++++++++++++++ .../client-module/05_add_address.spec.js | 20 +++++++++++++++++++ .../08_item_create_and_clone.spec.js | 20 +++++++++++++++++++ 7 files changed, 84 insertions(+), 3 deletions(-) diff --git a/client/client/src/address-create/address-create.html b/client/client/src/address-create/address-create.html index a9710e1f8..71196c0c4 100644 --- a/client/client/src/address-create/address-create.html +++ b/client/client/src/address-create/address-create.html @@ -41,5 +41,11 @@ + diff --git a/client/client/src/create/create.html b/client/client/src/create/create.html index 0df55e5b4..c29ceaab2 100644 --- a/client/client/src/create/create.html +++ b/client/client/src/create/create.html @@ -60,6 +60,12 @@ + diff --git a/client/item/src/create/create.html b/client/item/src/create/create.html index 0194275d9..2fbf02f29 100644 --- a/client/item/src/create/create.html +++ b/client/item/src/create/create.html @@ -44,6 +44,12 @@ + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 440090d41..b059d25b2 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -29,7 +29,8 @@ export default { email: `${components.vnTextfield}[name="email"]`, salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`, salesBruceBannerOption: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`, - createButton: `${components.vnSubmit}` + createButton: `${components.vnSubmit}`, + cancelButton: `button[href="#!/clients"]` }, clientBasicData: { basicDataButton: `${components.vnMenuItem}[ui-sref="clientCard.basicData"]`, @@ -110,7 +111,8 @@ export default { thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`, thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`, addObservationButton: `${components.vnIcon}[icon="add_circle"]`, - saveButton: `${components.vnSubmit}` + saveButton: `${components.vnSubmit}`, + cancelButton: `button[ui-sref="clientCard.addresses.list"]` }, clientWebAccess: { webAccessButton: `${components.vnMenuItem}[ui-sref="clientCard.webAccess"]`, @@ -168,7 +170,8 @@ export default { intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`, originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, - createButton: `${components.vnSubmit}` + createButton: `${components.vnSubmit}`, + cancelButton: `button[ui-sref="item.index"]` }, itemBasicData: { diff --git a/e2e/paths/client-module/01_create_client.spec.js b/e2e/paths/client-module/01_create_client.spec.js index e2f31c004..d33183906 100644 --- a/e2e/paths/client-module/01_create_client.spec.js +++ b/e2e/paths/client-module/01_create_client.spec.js @@ -42,6 +42,26 @@ describe('Client', () => { }); }); + it('should return to the client index by clicking the cancel button', () => { + return nightmare + .click(selectors.createClientView.cancelButton) + .wait(selectors.clientsIndex.createClientButton) + .parsedUrl() + .then(url => { + expect(url.hash).toEqual('#!/clients'); + }); + }); + + it('should now access to the create client view by clicking the create-client floating button', () => { + return nightmare + .click(selectors.clientsIndex.createClientButton) + .wait(selectors.createClientView.createButton) + .parsedUrl() + .then(url => { + expect(url.hash).toEqual('#!/create'); + }); + }); + it('should receive an error when clicking the create button having all the form fields empty', () => { return nightmare .click(selectors.createClientView.createButton) diff --git a/e2e/paths/client-module/05_add_address.spec.js b/e2e/paths/client-module/05_add_address.spec.js index ae149d3de..a5137fb5c 100644 --- a/e2e/paths/client-module/05_add_address.spec.js +++ b/e2e/paths/client-module/05_add_address.spec.js @@ -56,6 +56,26 @@ describe('Client', () => { }); }); + it(`should return to the addreses section by clicking the cancel button`, () => { + return nightmare + .waitToClick(selectors.clientAddresses.cancelButton) + .waitForURL('addresses/list') + .url() + .then(url => { + expect(url).toContain('addresses/list'); + }); + }); + + it(`should now click on the add new address button to access to the new address form`, () => { + return nightmare + .waitToClick(selectors.clientAddresses.createAddress) + .waitForURL('addresses/create') + .url() + .then(url => { + expect(url).toContain('addresses/create'); + }); + }); + it('should receive an error after clicking save button as consignee, street and town fields are empty', () => { return nightmare .waitToClick(selectors.clientAddresses.defaultCheckboxInput) diff --git a/e2e/paths/item-module/08_item_create_and_clone.spec.js b/e2e/paths/item-module/08_item_create_and_clone.spec.js index 50c53026a..f07d2ac75 100644 --- a/e2e/paths/item-module/08_item_create_and_clone.spec.js +++ b/e2e/paths/item-module/08_item_create_and_clone.spec.js @@ -41,6 +41,26 @@ describe('Item', () => { }); }); + it('should return to the item index by clickig the cancel button', () => { + return nightmare + .click(selectors.itemCreateView.cancelButton) + .wait(selectors.itemsIndex.createItemButton) + .parsedUrl() + .then(url => { + expect(url.hash).toEqual('#!/item/list'); + }); + }); + + it('should now access to the create item view by clicking the create floating button', () => { + return nightmare + .click(selectors.itemsIndex.createItemButton) + .wait(selectors.itemCreateView.createButton) + .parsedUrl() + .then(url => { + expect(url.hash).toEqual('#!/item/create'); + }); + }); + it('should create the Infinity Gauntlet item', () => { return nightmare .type(selectors.itemCreateView.name, 'Infinity Gauntlet') From 7f274a24e68c2501d547ff8367df068a98b83b40 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez <=> Date: Sun, 22 Apr 2018 17:17:24 +0200 Subject: [PATCH 4/4] #193 front end unit test for credit insurance list CR pending --- .../credit-insurance-list.spec.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 client/client/src/credit-insurance-list/credit-insurance-list.spec.js diff --git a/client/client/src/credit-insurance-list/credit-insurance-list.spec.js b/client/client/src/credit-insurance-list/credit-insurance-list.spec.js new file mode 100644 index 000000000..c98e157b7 --- /dev/null +++ b/client/client/src/credit-insurance-list/credit-insurance-list.spec.js @@ -0,0 +1,32 @@ +import './credit-insurance-list'; + +describe('Client', () => { + describe('Component vnClientCreditInsuranceList', () => { + let $componentController; + let controller; + let $httpBackend; + + beforeEach(() => { + angular.mock.module('client'); + }); + + beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { + $componentController = _$componentController_; + let $state = {params: {classificationId: 1}}; + $httpBackend = _$httpBackend_; + controller = $componentController('vnClientCreditInsuranceList', {$state: $state}); + })); + + it('should perform a query to GET credit the credit classification', () => { + let res = [{finished: 'some value'}]; + let query = '/client/api/CreditClassifications?filter=%7B%22fields%22%3A%5B%22finished%22%5D%2C%22where%22%3A%7B%22id%22%3A1%7D%7D'; + + $httpBackend.whenGET(query).respond(res); + $httpBackend.expectGET(query); + controller.$onInit(); + $httpBackend.flush(); + + expect(controller.isClosed).toBe(true); + }); + }); +});