From 50b4ee94f6fb4686fdeca4145f00dbe250e61a11 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Thu, 19 Oct 2023 08:26:40 +0200 Subject: [PATCH 01/16] Add initial files and changes --- package.json | 1 + test/functional/config-test.js | 74 +++++++++ test/functional/functional-tests.js | 227 ++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 test/functional/config-test.js create mode 100755 test/functional/functional-tests.js diff --git a/package.json b/package.json index e60a6386e..025cd8308 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "prettier:text": "prettier 'README.md' 'docs/*.md' 'docs/**/*.md' --no-config --tab-width 4 --print-width 120 --write --prose-wrap always", "start": "node ./bin/iotagent-json", "test": "nyc --reporter=text mocha --recursive 'test/**/*.js' --reporter spec --timeout 5000 --ui bdd --exit --color true", + "test:functional": "nyc --reporter=text mocha --recursive 'test/functional/*.js' --reporter spec --timeout 5000 --ui bdd --exit --color true", "test:coverage": "nyc --reporter=lcov mocha -- --recursive 'test/**/*.js' --reporter spec --exit", "test:coveralls": "npm run test:coverage && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage", "test:watch": "npm run test -- -w ./lib", diff --git a/test/functional/config-test.js b/test/functional/config-test.js new file mode 100644 index 000000000..094f68597 --- /dev/null +++ b/test/functional/config-test.js @@ -0,0 +1,74 @@ +/* + * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of iotagent-json + * + * iotagent-json is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * iotagent-json is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with iotagent-json. + * If not, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Miguel Angel Pedraza + */ + +/* eslint-disable no-unused-vars */ + +const config = {}; + +config.mqtt = { + host: 'localhost', + port: 1883 +}; + +config.http = { + port: 7896, + host: 'localhost' +}; + +config.amqp = { + port: 5672, + exchange: 'amq.topic', + queue: 'iota_queue', + options: { durable: true } +}; + +config.iota = { + logLevel: 'FATAL', + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041, + host: 'localhost' + }, + deviceRegistry: { + type: 'memory' + }, + types: {}, + service: 'howtoservice', + subservice: '/howto', + providerUrl: 'http://localhost:4041', + deviceRegistrationDuration: 'P1M', + defaultType: 'Thing', + defaultResource: '/iot/json', + compressTimestamp: true +}; + +config.defaultKey = '1234'; +config.defaultTransport = 'MQTT'; + +module.exports = config; diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js new file mode 100755 index 000000000..fefa7d1f5 --- /dev/null +++ b/test/functional/functional-tests.js @@ -0,0 +1,227 @@ +/* + * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of iotagent-json + * + * iotagent-json is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * iotagent-json is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with iotagent-json. + * If not, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Miguel Angel Pedraza + */ + +/* eslint-disable no-unused-vars */ + +const iotaJson = require('../..'); +const config = require('./config-test.js'); +const nock = require('nock'); +const iotAgentLib = require('iotagent-node-lib'); +const should = require('should'); +const async = require('async'); + +const utils = require('../utils'); +const request = utils.request; + +const logger = require('logops'); + +let contextBrokerMock; + +describe('FUNCTIONAL TESTS', function () { + beforeEach(function (done) { + nock.cleanAll(); + iotaJson.start(config, function () { + done(); + }); + }); + + afterEach(function (done) { + nock.cleanAll(); + + async.series([iotAgentLib.clearAll, iotaJson.stop], done); + }); + + describe('Basic group provision without attributes', function () { + // Provision + const provision = { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + + // Measure + const measure = { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + json: { + status: true + }, + qs: { + i: 'MQTT_2', + k: '123456' + } + }; + + const expectation = { + id: 'TheLightType:MQTT_2', + type: 'TheLightType', + status: { + value: true, + type: 'string' + } + }; + + beforeEach(function (done) { + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/v2/entities?options=upsert', expectation) + .reply(204); + + request(provision, function (error, response, body) { + done(); + }); + }); + + it('should return a 200 OK with no error', function (done) { + request(measure, function (error, result, body) { + should.not.exist(error); + result.statusCode.should.equal(200); + done(); + }); + }); + + it('should send its value to the Context Broker', function (done) { + request(measure, function (error, result, body) { + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('Basic group provision with attributes', function () { + // Provision + const provision = { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + + // Measure + const measure = { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + json: { + s: true, + t: 20 + }, + qs: { + i: 'MQTT_2', + k: '123456' + } + }; + + const expectation = { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: true, + type: 'Boolean' + }, + temperature: { + value: 20, + type: 'Number' + } + }; + + beforeEach(function (done) { + // logger.setLevel('DEBUG'); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/v2/entities?options=upsert', expectation) + .reply(204); + + // iotaJson.start(config, function () { + request(provision, function (error, response, body) { + done(); + }); + // }); + }); + + it('should return a 200 OK with no error', function (done) { + request(measure, function (error, result, body) { + should.not.exist(error); + result.statusCode.should.equal(200); + done(); + }); + }); + + it('should send its value to the Context Broker', function (done) { + request(measure, function (error, result, body) { + contextBrokerMock.done(); + done(); + }); + }); + }); +}); From 0a8d8c21943bdc2c8fa8564b3e109ae17f08b94a Mon Sep 17 00:00:00 2001 From: mapedraza Date: Thu, 19 Oct 2023 09:51:19 +0200 Subject: [PATCH 02/16] Remove leftovers + suggestions --- test/functional/config-test.js | 3 +-- test/functional/functional-tests.js | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/test/functional/config-test.js b/test/functional/config-test.js index 094f68597..f312e3d3a 100644 --- a/test/functional/config-test.js +++ b/test/functional/config-test.js @@ -64,8 +64,7 @@ config.iota = { providerUrl: 'http://localhost:4041', deviceRegistrationDuration: 'P1M', defaultType: 'Thing', - defaultResource: '/iot/json', - compressTimestamp: true + defaultResource: '/iot/json' }; config.defaultKey = '1234'; diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index fefa7d1f5..cf799b074 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -54,7 +54,6 @@ describe('FUNCTIONAL TESTS', function () { }); describe('Basic group provision without attributes', function () { - // Provision const provision = { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', @@ -64,7 +63,6 @@ describe('FUNCTIONAL TESTS', function () { resource: '/iot/json', apikey: '123456', entity_type: 'TheLightType', - trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', cbHost: 'http://192.168.1.1:1026', commands: [], lazy: [], @@ -79,7 +77,6 @@ describe('FUNCTIONAL TESTS', function () { } }; - // Measure const measure = { url: 'http://localhost:' + config.http.port + '/iot/json', method: 'POST', @@ -130,7 +127,6 @@ describe('FUNCTIONAL TESTS', function () { }); describe('Basic group provision with attributes', function () { - // Provision const provision = { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', @@ -140,7 +136,6 @@ describe('FUNCTIONAL TESTS', function () { resource: '/iot/json', apikey: '123456', entity_type: 'TheLightType2', - trust: '8970A9078A803H3BL98PINEQRW8342HBAMS', cbHost: 'http://192.168.1.1:1026', commands: [], lazy: [], @@ -166,7 +161,6 @@ describe('FUNCTIONAL TESTS', function () { } }; - // Measure const measure = { url: 'http://localhost:' + config.http.port + '/iot/json', method: 'POST', @@ -194,19 +188,15 @@ describe('FUNCTIONAL TESTS', function () { }; beforeEach(function (done) { - // logger.setLevel('DEBUG'); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', '/gardens') .post('/v2/entities?options=upsert', expectation) .reply(204); - // iotaJson.start(config, function () { request(provision, function (error, response, body) { done(); }); - // }); }); it('should return a 200 OK with no error', function (done) { From ce2e261bc92a794d9ed1f9ff0727a7b31c1088e0 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 20 Oct 2023 17:32:30 +0200 Subject: [PATCH 03/16] Make assertions more verbose --- package.json | 1 + test/functional/functional-tests.js | 146 +++++++++------------------- 2 files changed, 45 insertions(+), 102 deletions(-) diff --git a/package.json b/package.json index 025cd8308..c77b9a495 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "amqplib": "~0.5.1", "async": "2.6.4", "body-parser": "1.20.0", + "chai": "^4.3.10", "dateformat": "3.0.3", "express": "4.18.1", "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master", diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index cf799b074..3eb33417e 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -28,8 +28,9 @@ const iotaJson = require('../..'); const config = require('./config-test.js'); const nock = require('nock'); +const chai = require('chai'); +const expect = chai.expect; const iotAgentLib = require('iotagent-node-lib'); -const should = require('should'); const async = require('async'); const utils = require('../utils'); @@ -41,91 +42,18 @@ let contextBrokerMock; describe('FUNCTIONAL TESTS', function () { beforeEach(function (done) { + this.timeout(6000); nock.cleanAll(); - iotaJson.start(config, function () { - done(); + iotaJson.start(config, function (error) { + done(error); }); }); afterEach(function (done) { nock.cleanAll(); - async.series([iotAgentLib.clearAll, iotaJson.stop], done); }); - describe('Basic group provision without attributes', function () { - const provision = { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: '123456', - entity_type: 'TheLightType', - cbHost: 'http://192.168.1.1:1026', - commands: [], - lazy: [], - attributes: [], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }; - - const measure = { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - json: { - status: true - }, - qs: { - i: 'MQTT_2', - k: '123456' - } - }; - - const expectation = { - id: 'TheLightType:MQTT_2', - type: 'TheLightType', - status: { - value: true, - type: 'string' - } - }; - - beforeEach(function (done) { - contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', 'smartgondor') - .matchHeader('fiware-servicepath', '/gardens') - .post('/v2/entities?options=upsert', expectation) - .reply(204); - - request(provision, function (error, response, body) { - done(); - }); - }); - - it('should return a 200 OK with no error', function (done) { - request(measure, function (error, result, body) { - should.not.exist(error); - result.statusCode.should.equal(200); - done(); - }); - }); - - it('should send its value to the Context Broker', function (done) { - request(measure, function (error, result, body) { - contextBrokerMock.done(); - done(); - }); - }); - }); - describe('Basic group provision with attributes', function () { const provision = { url: 'http://localhost:' + config.iota.server.port + '/iot/services', @@ -164,54 +92,68 @@ describe('FUNCTIONAL TESTS', function () { const measure = { url: 'http://localhost:' + config.http.port + '/iot/json', method: 'POST', - json: { - s: true, - t: 20 - }, qs: { i: 'MQTT_2', k: '123456' + }, + json: { + s: true, + t: 20 } }; const expectation = { id: 'TheLightType2:MQTT_2', type: 'TheLightType2', - status: { - value: true, - type: 'Boolean' - }, temperature: { - value: 20, + value: 10, type: 'Number' + }, + status: { + value: false, + type: 'Boolean' } }; beforeEach(function (done) { + request(provision, function (error, response, body) { + let err = null; + if (response.statusCode != 201) { + err = new Error('Error creating the service'); + } + done(err); + }); + }); + + afterEach(function () { + nock.cleanAll(); + }); + + it('should send its value to the Context Broker', async function () { + let receivedBody; contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', '/gardens') - .post('/v2/entities?options=upsert', expectation) + .post('/v2/entities?options=upsert', function (body) { + receivedBody = body; // Save the received body for later comparison + return true; + }) .reply(204); - request(provision, function (error, response, body) { - done(); + // Send a measure to the IoT Agent and wait for the response + const response = await new Promise((resolve, reject) => { + request(measure, function (error, result, body) { + error ? reject(error) : resolve(result); + }); }); - }); - it('should return a 200 OK with no error', function (done) { - request(measure, function (error, result, body) { - should.not.exist(error); - result.statusCode.should.equal(200); - done(); - }); - }); + // Validate the response status code and the receivedBody + expect(response.statusCode, 'Measure response: Status code differs from 200').to.equal(200); + expect(response.body, 'Measure response: body not empty').to.be.empty; - it('should send its value to the Context Broker', function (done) { - request(measure, function (error, result, body) { - contextBrokerMock.done(); - done(); - }); + // Validate Context Broker Expectation + contextBrokerMock.done(); // Ensure the request was made, no matter the body content + expect(receivedBody, 'CB payload: message differs from expectation').to.deep.equal(expectation); }); }); }); From fcbdd43d427dd3a944f39e85e7526e77aa914551 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 20 Oct 2023 17:40:45 +0200 Subject: [PATCH 04/16] Fix lint --- test/functional/functional-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index 3eb33417e..cda37ad3c 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -23,7 +23,7 @@ * Modified by: Miguel Angel Pedraza */ -/* eslint-disable no-unused-vars */ +/* eslint-disable no-unused-vars no-unused-expressions */ const iotaJson = require('../..'); const config = require('./config-test.js'); @@ -118,7 +118,7 @@ describe('FUNCTIONAL TESTS', function () { beforeEach(function (done) { request(provision, function (error, response, body) { let err = null; - if (response.statusCode != 201) { + if (response.statusCode !== 201) { err = new Error('Error creating the service'); } done(err); From 9fdb31c2652d435561a2f2db21489fad74790e6a Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 20 Oct 2023 17:46:44 +0200 Subject: [PATCH 05/16] Fix lint 2 --- test/functional/functional-tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index cda37ad3c..b5563abe6 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -23,7 +23,8 @@ * Modified by: Miguel Angel Pedraza */ -/* eslint-disable no-unused-vars no-unused-expressions */ +/* eslint-disable no-unused-vars*/ +/* eslint-disable no-unused-expressions*/ const iotaJson = require('../..'); const config = require('./config-test.js'); From 67270148d384877f12faa52a876ee29ea68f6591 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Mon, 23 Oct 2023 09:19:55 +0200 Subject: [PATCH 06/16] Wrap measure promise + err messages --- test/functional/functional-tests.js | 22 +++++++++++----------- test/utils.js | 9 +++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index b5563abe6..135741329 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -41,6 +41,10 @@ const logger = require('logops'); let contextBrokerMock; +const ERR_CB_EXPECTATION_DIFFER = 'Assertion Error - Context Broker received payload differs from expectation'; +const ERR_MEAS_BODY = 'Assertion Error - Measure response is not empty'; +const ERR_MEAS_CODE = 'Assertion Error - Measure response status code differs from 200'; + describe('FUNCTIONAL TESTS', function () { beforeEach(function (done) { this.timeout(6000); @@ -131,30 +135,26 @@ describe('FUNCTIONAL TESTS', function () { }); it('should send its value to the Context Broker', async function () { - let receivedBody; + let receivedContext = null; contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', '/gardens') .post('/v2/entities?options=upsert', function (body) { - receivedBody = body; // Save the received body for later comparison + receivedContext = body; // Save the received body for later comparison return true; }) .reply(204); // Send a measure to the IoT Agent and wait for the response - const response = await new Promise((resolve, reject) => { - request(measure, function (error, result, body) { - error ? reject(error) : resolve(result); - }); - }); + const response = await utils.sendMeasurePromise(measure); - // Validate the response status code and the receivedBody - expect(response.statusCode, 'Measure response: Status code differs from 200').to.equal(200); - expect(response.body, 'Measure response: body not empty').to.be.empty; + // Validate the response status code and the response body + expect(response.statusCode, ERR_MEAS_CODE).to.equal(200); + expect(response.body, ERR_MEAS_BODY).to.be.empty; // Validate Context Broker Expectation contextBrokerMock.done(); // Ensure the request was made, no matter the body content - expect(receivedBody, 'CB payload: message differs from expectation').to.deep.equal(expectation); + expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.deep.equal(expectation); }); }); }); diff --git a/test/utils.js b/test/utils.js index 74d5aa81d..55caf62f4 100644 --- a/test/utils.js +++ b/test/utils.js @@ -41,6 +41,15 @@ function delay(ms) { }; } +function sendMeasurePromise(measure) { + return new Promise((resolve, reject) => { + request(measure, function (error, result, body) { + error ? reject(error) : resolve(result); + }); + }); +} + exports.readExampleFile = readExampleFile; exports.delay = delay; exports.request = request; +exports.sendMeasurePromise = sendMeasurePromise; From e5d10cf6f9ca4b56fcfed19820d02121aea5c802 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 27 Oct 2023 17:14:58 +0200 Subject: [PATCH 07/16] Add automated tests suite + doc --- package.json | 3 +- test/functional/README.md | 313 ++++ test/functional/functional-tests-auto.js | 113 ++ test/functional/functional-tests.js | 130 +- test/functional/testCases.js | 1969 ++++++++++++++++++++++ test/functional/testUtils.js | 123 ++ test/utils.js | 9 - 7 files changed, 2622 insertions(+), 38 deletions(-) create mode 100644 test/functional/README.md create mode 100755 test/functional/functional-tests-auto.js create mode 100644 test/functional/testCases.js create mode 100644 test/functional/testUtils.js diff --git a/package.json b/package.json index c77b9a495..c5a0b8857 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "watch": "watch 'npm test && npm run lint' ./lib ./test" }, "devDependencies": { + "async-mqtt": "~2.6.3", + "chai": "^4.3.10", "coveralls": "~3.1.0", "eslint": "~7.5.0", "eslint-config-tamia": "~7.2.5", @@ -62,7 +64,6 @@ "amqplib": "~0.5.1", "async": "2.6.4", "body-parser": "1.20.0", - "chai": "^4.3.10", "dateformat": "3.0.3", "express": "4.18.1", "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master", diff --git a/test/functional/README.md b/test/functional/README.md new file mode 100644 index 000000000..8f118151e --- /dev/null +++ b/test/functional/README.md @@ -0,0 +1,313 @@ +## Functional test suite + +This directory contains the functional test suite for the IoTA JSON. This test suite is based on mocha and chai. For +mocks, we use the nock library. Additionally, it uses some specific functions to ease to implement the test. Helper +functions are located in the `testUtils.js` file. + +There are 2 tests files in this directory: + +- `fuctional-tests.js`: This file contains the test defined in the "classic way". This means, coded in the JS file as + any other mocha test. It uses the functions defined in the `testUtils.js` file to simplify the tests. +- `functional-tests-auto.js`: This file contains the test defined in the "automatic way". This means, the test cases + are defined as JSON in a separate file (`testCases.js`). This file is loaded by the test suite and the test cases + are automatically generated. This is the recommended way to define the test cases. + +### Automatic test cases + +Each test case is defined as a JSON object in the `testCases.js` file. This file is loaded by the test suite and the +test cases are automatically generated. Each test case is defined as an object with the following elements: + +- `describeName`: The name of the `DESCRIBE` test case. This will be used to generate the test case name in the mocha + test suite. +- `provision`: The JSON object that will be sent to the IoTA JSON provisioning API. This will be used to create the + group. It contains the following elements: + - `url`: The URL of the provisioning API (group) + - `method`: The HTTP method to use (POST) + - `json`: The JSON object that defines the group + - `headers`: The headers to send to the provisioning API. This should contain the `fiware-service` and + `fiware-servicepath` headers. +- `should`: The array of test cases to execute. Each test case is defined as an object with the following elements: + - `transport`: The transport to use to send the measure. This can be `HTTP` or `MQTT`. It uses `HTTP` by default + or if the `transport` element is not defined. See the "Advanced features" section for more information. + - `shouldName`: The name of the `IT` test case. This will be used to generate the test case name in the mocha test + suite. + - `type`: The type of the test case. This can be `single` or `multientity`. See the "Advanced features" section + for more information. + - `measure`: The JSON object that will be sent to the IoTA JSON measure API. This will be used to send the + measure. It contains the following elements: + - `url`: The URL of the measure API (group) + - `method`: The HTTP method to use (POST) + - `qs`: The query string to send to the measure API. This should contain the `i` and `k` parameters. + - `json`: The JSON object that defines the measure + - `expectation`: The JSON object that defines the expectation. This will be used to check that the measure has + been correctly sent to the Context Broker. + +#### Example + +```javascript +{ + describeName: 'Basic group provision with attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }, + should:[ + { + shouldName: 'should send its value to the Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + }, + status: { + value: false, + type: 'Boolean' + } + } + }, + { + transport: 'MQTT', + shouldName: 'should send its value to the Context Broker when using MQTT', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + }, + status: { + value: false, + type: 'Boolean' + } + } + } + ] +} +``` + +### Advanced features + +#### Multientity + +This test suite support the multientity feature. To test this feature, you need to set add to the test case the +parameter `type: 'multientity'`. This will automatically take care of the multientity feature. This means that the suite +will configure the mock to listen to `/v2/op/update` instead of `/v2/entities?options=upsert`. + +In particular, it will configure the mock to listen the correct URL. You should define the expectation for the test case +as a batch operation (see the following example). + +```javascript +{ + "entities": [ + { + "id": "TheLightType2:MQTT_2", + "type": "TheLightType2", + "status": { + "value": false, + "type": "Boolean" + } + }, + { + "id": "TheLightType2:MQTT_3", + "type": "TheLightType2", + "temperature": { + "value": 10, + "type": "Number" + } + } + ], + "actionType": "append" +} +``` + +#### Multimeasures + +It is also supported to test cases in which is sent more than one measure. To do so, you need to define the test case +expectation as an array, with one object for each measurement. Then, the suite will recognize the array length and will +expect the same number of NGSI requests. I.E: + +```js +[ + { + id: "TheLightType2:MQTT_2", + type: "TheLightType2", + temperature: { + value: 10, + type: "Number", + }, + status: { + value: false, + type: "Boolean", + }, + }, + { + id: "TheLightType2:MQTT_2", + type: "TheLightType2", + temperature: { + value: 20, + type: "Number", + }, + status: { + value: true, + type: "Boolean", + }, + }, +]; +``` + +You also should define the measure as multimeasure. This is done by defining the measure JSON element as an array of +objects. Each object will be a measure that will be sent to the Context Broker in a different request. I.E: + +```javascript +measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: [ + { + s: false, + t: 10 + }, + { + s: true, + t: 20 + } + ] +} +``` + +#### MQTT Support + +The test suite also supports MQTT for measure sending. To do so, you need to define the measure as MQTT. This is done by +adding the `transport` element to each should case having the value set to `MQTT`. By doing so, the suite will +automatically configure the mock to connect to the MQTT broker and send the measure to the correct topic based on the +`i` and `k` parameters. It will ignore the `url` and `method` parameters present in the measure JSON element. I.E: + +```javascript +should: [ + { + transport: "MQTT", + shouldName: "should send its value to the Context Broker when using MQTT", + type: "single", + measure: { + url: "http://localhost:" + config.http.port + "/iot/json", + method: "POST", + qs: { + i: "MQTT_2", + k: "123456", + }, + json: { + s: false, + t: 10, + }, + }, + expectation: { + id: "TheLightType2:MQTT_2", + type: "TheLightType2", + temperature: { + value: 10, + type: "Number", + }, + status: { + value: false, + type: "Boolean", + }, + }, + }, +]; +``` + +#### No payload reception + +The test suite also supports the case in which the Context Broker does not receive any payload. This is done by defining +the expectation as an empty object. I.E: + +```javascript + ... + expectation: [] + ... +``` + +### Debugging automated tests + +It is possible to debug the automated tests by using the loglevel parameter set to `debug` for each should case. This +parameter configures the IoTA log level. By setting it to `debug`, the agent will log all the debug messages to the +console. This is useful to check the messages sent to the Context Broker. + +It is also useful to debug the test by adding a breakpoint in the `testUtils.js` (read the comments in the file to know +where to add the breakpoint). This will allow you to debug just that particular test case stopping the execution in the +breakpoint. + +Example of a test case with the loglevel set to `debug`: + +```javascript +should:[ + { + transport: 'MQTT', + shouldName: 'should send its value to the Context Broker when using MQTT', + type: 'single', + measure: {...}, + expectation: {...} + } +] +``` diff --git a/test/functional/functional-tests-auto.js b/test/functional/functional-tests-auto.js new file mode 100755 index 000000000..141acb12a --- /dev/null +++ b/test/functional/functional-tests-auto.js @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of iotagent-json + * + * iotagent-json is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * iotagent-json is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with iotagent-json. + * If not, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Miguel Angel Pedraza + */ + +/* eslint-disable no-unused-vars*/ +/* eslint-disable no-unused-expressions*/ + +const iotaJson = require('../../lib/iotagent-json'); +const config = require('./config-test.js'); +const nock = require('nock'); +const chai = require('chai'); +const expect = chai.expect; +const iotAgentLib = require('iotagent-node-lib'); +const async = require('async'); +const utils = require('../utils'); +const testUtils = require('./testUtils'); +const request = utils.request; +const logger = require('logops'); + +const baseTestCases = require('./testCases.js').testCases; + +const env = { + service: 'smartgondor', + servicePath: '/gardens' +}; + +// You can add here your own test cases to be executed in addition to the base ones +// It is useful to test new features or to test specific scenarios. If you are going +// to add a new test case, please, add it to the testCases.js file instead of adding +// it here. +let testCases = []; + +// If you want to execute only the test cases defined above, you can comment +// the following line. Otherwise, the tests defined in testCases.js will be +// executed as well. +testCases = testCases.concat(baseTestCases); + +describe('FUNCTIONAL TESTS', function () { + beforeEach(function (done) { + iotaJson.start(config, function (error) { + done(error); + }); + }); + + afterEach(function (done) { + async.series([iotAgentLib.clearAll, iotaJson.stop], done); + }); + + testCases.forEach((testCase) => { + describe(testCase.describeName, function () { + beforeEach(function (done) { + if (testCase.loglevel) { + logger.setLevel(testCase.loglevel); + } + request(testCase.provision, function (error, response, body) { + let err = null; + if (response.statusCode !== 201) { + err = new Error('Error creating the device group'); + } + done(err); + }); + }); + + afterEach(function () { + logger.setLevel('FATAL'); + nock.cleanAll(); + }); + + testCase.should.forEach((should) => { + it(should.shouldName, async function () { + this.retries(2); // pass the maximum no of retries + if (should.loglevel) { + // You can use this line to set a breakpoint in the test in order to debug it + // You just need to add a loglevel element to the test case with the desired log level + // and then set a breakpoint in the next line. By default, the log level is FATAL and + // the following line will never be executed + logger.setLevel(should.loglevel); + } + + await testUtils.testCase( + should.measure, + should.expectation, + env, + config, + should.type ? should.type : 'single', + should.transport ? should.transport : 'HTTP' + ); + }); + }); + }); + }); +}); diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index 135741329..662174d23 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -33,29 +33,30 @@ const chai = require('chai'); const expect = chai.expect; const iotAgentLib = require('iotagent-node-lib'); const async = require('async'); - const utils = require('../utils'); +const testUtils = require('./testUtils'); const request = utils.request; - const logger = require('logops'); let contextBrokerMock; +const env = { + service: 'smartgondor', + servicePath: '/gardens' +}; + const ERR_CB_EXPECTATION_DIFFER = 'Assertion Error - Context Broker received payload differs from expectation'; const ERR_MEAS_BODY = 'Assertion Error - Measure response is not empty'; const ERR_MEAS_CODE = 'Assertion Error - Measure response status code differs from 200'; describe('FUNCTIONAL TESTS', function () { beforeEach(function (done) { - this.timeout(6000); - nock.cleanAll(); iotaJson.start(config, function (error) { done(error); }); }); afterEach(function (done) { - nock.cleanAll(); async.series([iotAgentLib.clearAll, iotaJson.stop], done); }); @@ -102,8 +103,8 @@ describe('FUNCTIONAL TESTS', function () { k: '123456' }, json: { - s: true, - t: 20 + s: false, + t: 10 } }; @@ -124,7 +125,98 @@ describe('FUNCTIONAL TESTS', function () { request(provision, function (error, response, body) { let err = null; if (response.statusCode !== 201) { - err = new Error('Error creating the service'); + err = new Error('Error creating the device group'); + } + done(err); + }); + }); + + afterEach(function () { + nock.cleanAll(); + }); + + it('should send its value to the Context Broker', async function () { + await testUtils.testCase(measure, expectation, env, config, 'single', 'HTTP'); + }); + }); + + describe('Basic group provision with attributes and multientity', function () { + const provision = { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number', + entity_name: 'TheLightType2:MQTT_3', + entity_type: 'TheLightType2' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + + const measure = { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: { + s: false, + t: 10 + } + }; + + const expectation = { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: false, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + } + } + ], + actionType: 'append' + }; + + beforeEach(function (done) { + request(provision, function (error, response, body) { + let err = null; + if (response.statusCode !== 201) { + err = new Error('Error creating the device group'); } done(err); }); @@ -135,26 +227,8 @@ describe('FUNCTIONAL TESTS', function () { }); it('should send its value to the Context Broker', async function () { - let receivedContext = null; - contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', 'smartgondor') - .matchHeader('fiware-servicepath', '/gardens') - .post('/v2/entities?options=upsert', function (body) { - receivedContext = body; // Save the received body for later comparison - return true; - }) - .reply(204); - - // Send a measure to the IoT Agent and wait for the response - const response = await utils.sendMeasurePromise(measure); - - // Validate the response status code and the response body - expect(response.statusCode, ERR_MEAS_CODE).to.equal(200); - expect(response.body, ERR_MEAS_BODY).to.be.empty; - - // Validate Context Broker Expectation - contextBrokerMock.done(); // Ensure the request was made, no matter the body content - expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.deep.equal(expectation); + // logger.setLevel('DEBUG'); + await testUtils.testCase(measure, expectation, env, config, 'multientity', 'HTTP'); }); }); }); diff --git a/test/functional/testCases.js b/test/functional/testCases.js new file mode 100644 index 000000000..85b1e8979 --- /dev/null +++ b/test/functional/testCases.js @@ -0,0 +1,1969 @@ +const config = require('./config-test.js'); + +globalEnv = { + service: 'smartgondor', + servicePath: '/gardens', + apikey: '123456', + entity_type: 'TestType', + entity_name: 'TestType:TestDevice', + deviceId: 'TestDevice' +}; + +const testCases = [ + // BASIC TESTS + { + describeName: '0010 - Simple group without attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending measures through http IT should send measures to Context Broker preserving value types', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false, + b: 10, + c: 'text', + d: 10.5, + e: [1, 2], + f: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: false, + type: 'string' + }, + b: { + value: 10, + type: 'string' + }, + c: { + type: 'string', + value: 'text' + }, + d: { + type: 'string', + value: 10.5 + }, + e: { + type: 'string', + value: [1, 2] + }, + f: { + type: 'string', + value: { + a: 1, + b: 2 + } + } + } + }, + { + transport: 'MQTT', + shouldName: + 'B - WHEN sending measures through mqtt IT should send measures to Context Broker preserving value types', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false, + b: 10, + c: 'text', + d: 10.5, + e: [1, 2], + f: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: false, + type: 'string' + }, + b: { + value: 10, + type: 'string' + }, + c: { + type: 'string', + value: 'text' + }, + d: { + type: 'string', + value: 10.5 + }, + e: { + type: 'string', + value: [1, 2] + }, + f: { + type: 'string', + value: { + a: 1, + b: 2 + } + } + } + } + ] + }, + { + describeName: '0020 - Simple group with attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Boolean' + }, + { + object_id: 'b', + name: 'attr_b', + type: 'Integer' + }, + { + object_id: 'c', + name: 'attr_c', + type: 'Text' + }, + { + object_id: 'd', + name: 'attr_d', + type: 'Float' + }, + { + object_id: 'e', + name: 'attr_e', + type: 'Array' + }, + { + object_id: 'f', + name: 'attr_f', + type: 'Object' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types and name mappings', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false, + b: 10, + c: 'text', + d: 10.5, + e: [1, 2], + f: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: false, + type: 'Boolean' + }, + attr_b: { + value: 10, + type: 'Integer' + }, + attr_c: { + type: 'Text', + value: 'text' + }, + attr_d: { + type: 'Float', + value: 10.5 + }, + attr_e: { + type: 'Array', + value: [1, 2] + }, + attr_f: { + type: 'Object', + value: { + a: 1, + b: 2 + } + } + } + }, + { + shouldName: + 'B - WHEN sending defined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types and name mappings', + transport: 'MQTT', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false, + b: 10, + c: 'text', + d: 10.5, + e: [1, 2], + f: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: false, + type: 'Boolean' + }, + attr_b: { + value: 10, + type: 'Integer' + }, + attr_c: { + type: 'Text', + value: 'text' + }, + attr_d: { + type: 'Float', + value: 10.5 + }, + attr_e: { + type: 'Array', + value: [1, 2] + }, + attr_f: { + type: 'Object', + value: { + a: 1, + b: 2 + } + } + } + }, + { + shouldName: + 'C - WHEN sending undefined object_ids (measures) through http IT should send measures to Context Broker preserving value types', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + u: false, + v: 10, + w: 'text', + y: 10.5, + x: [1, 2], + z: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + u: { + value: false, + type: 'string' + }, + v: { + value: 10, + type: 'string' + }, + w: { + type: 'string', + value: 'text' + }, + y: { + type: 'string', + value: 10.5 + }, + x: { + type: 'string', + value: [1, 2] + }, + z: { + type: 'string', + value: { + a: 1, + b: 2 + } + } + } + }, + { + shouldName: + 'D - WHEN sending undefined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types', + transport: 'MQTT', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + u: false, + v: 10, + w: 'text', + y: 10.5, + x: [1, 2], + z: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + u: { + value: false, + type: 'string' + }, + v: { + value: 10, + type: 'string' + }, + w: { + type: 'string', + value: 'text' + }, + y: { + type: 'string', + value: 10.5 + }, + x: { + type: 'string', + value: [1, 2] + }, + z: { + type: 'string', + value: { + a: 1, + b: 2 + } + } + } + } + ] + }, + // JEXL TESTS + { + describeName: '0030 - Simple group with attribute + JEXL expression boolean (!)', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Boolean', + expression: '!a' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a boolean value (false) through http IT should send to Context Broker the value true ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: true, + type: 'Boolean' + } + } + }, + { + shouldName: + 'B - WHEN sending a numeric value (3) through http IT should send to Context Broker the value false ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 3 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: false, + type: 'Boolean' + } + } + }, + { + shouldName: + 'C - WHEN sending a text value (abcd) through http IT should send to Context Broker the value false ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'abcd' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: false, + type: 'Boolean' + } + } + }, + { + shouldName: + 'D - WHEN not sending the object ID (undefined) required by the expression through http IT should send to Context Broker the value true ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + b: 1 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: true, + type: 'Boolean' + }, + b: { + value: 1, + type: 'string' + } + } + } + ] + }, + { + describeName: '0040 - Simple group with attribute + JEXL expression numeric (+3)', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a+3' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 4 ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: true + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 4, + type: 'Number' + } + } + }, + { + shouldName: + 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 3 ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 3, + type: 'Number' + } + } + }, + { + shouldName: + 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -4 ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: -7 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: -4, + type: 'Number' + } + } + }, + { + shouldName: + 'D - WHEN sending a text value (abcd) through http IT should send to Context Broker the value abcd3', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'abcd' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 'abcd3', + type: 'Number' + } + } + }, + { + shouldName: + 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + b: 1 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + b: { + value: 1, + type: 'string' + } + } + } + ] + }, + { + describeName: '0050 - Simple group with attribute + JEXL expression numeric (*3)', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a*3' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 3 ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: true + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 3, + type: 'Number' + } + } + }, + { + shouldName: + 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 0', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 0, + type: 'Number' + } + } + }, + { + shouldName: + 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -21 ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: -7 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: -21, + type: 'Number' + } + } + }, + { + shouldName: + 'D - WHEN sending a text value (abcd) through http IT should not send to anything to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'abcd' + } + }, + expectation: [] + }, + { + shouldName: + 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + b: 1 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + b: { + value: 1, + type: 'string' + } + } + } + ] + }, + { + describeName: '0060 - Simple group with attribute + JEXL expression text (a|substr(0,2))', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a|substr(0,3)' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value "tru" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: true + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 'tru', + type: 'Number' + } + } + }, + { + shouldName: + 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value "fal"', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 'fal', + type: 'Number' + } + } + }, + { + shouldName: + 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value "-7" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: -7 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: '-7', + type: 'Number' + } + } + }, + { + shouldName: + 'D - WHEN sending a text value (abcd) through http IT should not send to anything to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'abcd' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 'abc', + type: 'Number' + } + } + }, + { + shouldName: + 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + b: 1 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 'und', + type: 'Number' + }, + b: { + value: 1, + type: 'string' + } + } + } + ] + }, + { + describeName: '0070 - Simple group with attribute + chained JEXL expression text (a|substr(0,2))', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'a', + type: 'Text', + expression: 'a | trim | replacestr("hello","hi")' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a expected value ("Say hello and smile") through http IT should send to Context Broker the value "Say hi and smile" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'Say hello and smile' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 'Say hi and smile', + type: 'Text' + } + } + } + ] + }, + { + describeName: '0080 - Simple group with attribute + JEXL expression text reusing previous values)', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a*10' + }, + { + object_id: 'b', + name: 'attr_b', + type: 'Number', + expression: 'attr_a*10' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a value (a:3) through http IT should apply nested expressions and send to Context Broker the value "attr_b=300" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 3 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 30, + type: 'Number' + }, + attr_b: { + value: 300, + type: 'Number' + } + } + } + ] + }, + { + describeName: '0090 - Simple group with attribute + JEXL expression referencing static attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number', + expression: 'a*coef' + } + ], + static_attributes: [ + { + name: 'coef', + type: 'Number', + value: 1.5 + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a value (a:6) through http IT should apply the expression using the static attribute value and send to Context Broker the value "attr_a:9" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 6 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 9, + type: 'Number' + }, + coef: { + value: 1.5, + type: 'Number' + } + } + } + ] + }, + { + describeName: '0100 - Simple group with attribute + JEXL expression referencing context attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Text', + expression: 'a+":"+service+subservice+id+type' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a value (text) through http IT should apply the expression using the context attributes value and send to Context Broker the value "text:smartgondor/gardensTestDeviceTestType" ', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 'text' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: + 'text:' + + globalEnv.service + + globalEnv.servicePath + + globalEnv.deviceId + + globalEnv.entity_type, + type: 'Text' + } + } + } + ] + }, + { + describeName: '0110 - Simple group with attributes + JEXL multiples expressions at same time', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Boolean', + expression: '!a' + }, + { + object_id: 'b', + name: 'attr_b', + type: 'Integer', + expression: 'b+1' + }, + { + object_id: 'c', + name: 'attr_c', + type: 'Text', + expression: 'c+":"+service+subservice+id+type' + }, + { + object_id: 'd', + name: 'attr_d', + type: 'Float', + expression: 'd/2+attr_b' + }, + { + object_id: 'e', + name: 'attr_e', + type: 'Array', + expression: 'e|concatarr([3,4])' + }, + { + object_id: 'f', + name: 'attr_f', + type: 'Object', + expression: '{coordinates: [f.a,f.b], type: "Point"}' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending multiples object_ids (measures) through http IT should send measures to Context Broker applying all expressions at same time', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: false, + b: 10, + c: 'text', + d: 10.5, + e: [1, 2], + f: { a: 1, b: 2 } + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: true, + type: 'Boolean' + }, + attr_b: { + value: 11, + type: 'Integer' + }, + attr_c: { + type: 'Text', + value: + 'text:' + + globalEnv.service + + globalEnv.servicePath + + globalEnv.deviceId + + globalEnv.entity_type + }, + attr_d: { + type: 'Float', + value: 16.25 + }, + attr_e: { + type: 'Array', + value: [1, 2, 3, 4] + }, + attr_f: { + type: 'Object', + value: { coordinates: [1, 2], type: 'Point' } + } + } + } + ] + }, + // TIMESTAMP TESTS + { + describeName: '0150 - Simple group with attribute + timestamp:false', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + timestamp: false, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure not named TimeInstant through http IT should not add the timestamp to the attributes sent to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 23, + type: 'Number' + } + } + }, + { + shouldName: + 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other attributes sent to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + TimeInstant: '2015-12-14T08:06:01.468Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 23, + type: 'Number' + }, + TimeInstant: { + value: '2015-12-14T08:06:01.468Z', + type: 'DateTime' + } + } + } + ] + }, + { + describeName: '0160 - Simple group with attribute + timestamp:true', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + timestamp: true, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'attr_a', + type: 'Number' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure not named TimeInstant through http IT should add the timestamp to the attributes sent to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 23, + type: 'Number', + metadata: { + TimeInstant: { + type: 'DateTime', + value: '2015-12-14T08:06:01.468Z' + } + } + } + } + }, + { + shouldName: + 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other attributes sent to Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + TimeInstant: '2015-12-14T08:06:01.468Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + attr_a: { + value: 23, + type: 'Number', + metadata: { + TimeInstant: { + type: 'DateTime', + value: '2015-12-14T08:06:01.468Z' + } + } + }, + TimeInstant: { + value: '2015-12-14T08:06:01.468Z', + type: 'DateTime' + } + } + } + ] + }, + + // OTHER TESTS + + { + describeName: '0010 - Provision de grupo sin atributos', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: 'A - Enviar su valor al Context Broker cuando se usa HTTP', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + temperature: { + value: 10, + type: 'Number' + }, + status: { + value: false, + type: 'Boolean' + } + } + }, + { + transport: 'MQTT', + shouldName: 'should send its value to the Context Broker when using MQTT', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + temperature: { + value: 10, + type: 'Number' + }, + status: { + value: false, + type: 'Boolean' + } + } + } + ] + }, + { + describeName: 'Basic group provision with attributes', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }, + should: [ + { + transport: 'MQTT', + shouldName: 'should send its value to the Context Broker', + type: 'single', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + }, + status: { + value: false, + type: 'Boolean' + } + } + } + ] + }, + { + describeName: 'Basic group provision with attributes and multientity', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number', + entity_name: 'TheLightType2:MQTT_3', + entity_type: 'TheLightType2' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }, + should: [ + { + shouldName: 'should send its value to the Context Broker', + type: 'multientity', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: { + s: false, + t: 10 + } + }, + expectation: { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: false, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + } + } + ], + actionType: 'append' + } + }, + { + shouldName: 'should send its value to the Context Broker with multimeasures', + type: 'multientity', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: [ + { + s: false, + t: 10 + }, + { + s: true, + t: 20 + } + ] + }, + expectation: [ + { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: false, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + } + } + ], + actionType: 'append' + }, + { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: true, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 20, + type: 'Number' + } + } + ], + actionType: 'append' + } + ] + } + ] + }, + { + describeName: 'Basic group provision with attributes and multientity', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: '123456', + entity_type: 'TheLightType2', + cbHost: 'http://192.168.1.1:1026', + commands: [], + lazy: [], + attributes: [ + { + object_id: 's', + name: 'status', + type: 'Boolean' + }, + { + object_id: 't', + name: 'temperature', + type: 'Number', + entity_name: 'TheLightType2:MQTT_3', + entity_type: 'TheLightType2' + } + ], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }, + should: [ + { + shouldName: 'should send its value to the Context Broker', + type: 'multientity', + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: 'MQTT_2', + k: '123456' + }, + json: [ + { + s: false, + t: 10 + }, + { + s: true, + t: 20 + } + ] + }, + expectation: [ + { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: false, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 10, + type: 'Number' + } + } + ], + actionType: 'append' + }, + { + entities: [ + { + id: 'TheLightType2:MQTT_2', + type: 'TheLightType2', + status: { + value: true, + type: 'Boolean' + } + }, + { + id: 'TheLightType2:MQTT_3', + type: 'TheLightType2', + temperature: { + value: 20, + type: 'Number' + } + } + ], + actionType: 'append' + } + ] + } + ] + } +]; + +exports.testCases = testCases; diff --git a/test/functional/testUtils.js b/test/functional/testUtils.js new file mode 100644 index 000000000..8095e2cf5 --- /dev/null +++ b/test/functional/testUtils.js @@ -0,0 +1,123 @@ +/* + * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of iotagent-json + * + * iotagent-json is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * iotagent-json is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with iotagent-json. + * If not, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Miguel Angel Pedraza + */ + +/* eslint-disable no-unused-vars*/ +/* eslint-disable no-unused-expressions*/ + +const nock = require('nock'); +const request = require('iotagent-node-lib').request; +const async = require('async'); +const chai = require('chai'); +const expect = chai.expect; +const MQTT = require('async-mqtt'); + +// Error messages +const ERR_CB_EXPECTATION_DIFFER = 'Assertion Error - Context Broker received payload differs from expectation'; +const ERR_MEAS_BODY = 'Assertion Error - Measure response is not empty'; +const ERR_MEAS_CODE = 'Assertion Error - Measure response status code differs from 200'; +const ERR_MQTT = 'Error with MQTT: '; +const ERR_CB_NOT_EMPTY = 'Assertion Error - unexpected Context Broker request received (no request expected)'; + +/** + * Send a measure to the IoT Agent + * @brief Sends a measure to the IoT Agent and returns a promise with the response + * + * @param {Object} measure Measure to be sent to the IoT Agent + */ +function sendMeasurePromise(measure) { + return new Promise((resolve, reject) => { + request(measure, function (error, result, body) { + error ? reject(error) : resolve(result); + }); + }); +} + +/** + * Test Case function + * @brief Sends a measure to the IoT Agent and validates the response + * and validates the Context Broker expectation + * + * @param {Object} measure Measure to be sent to the IoT Agent + * @param {Object} expectation Expectation for the Context Broker + * @param {Object} env Environment variables + * @param {String} type Type of test (multientity or multimeasure) + */ + +async function testCase(measure, expectation, env, config, type, transport) { + let receivedContext = []; + // Set the correct route depending if the test is multientity or not + if (type === 'multientity') { + cbMockRoute = '/v2/op/update'; + } else { + cbMockRoute = '/v2/entities?options=upsert'; + } + + // Set the correct mock times depending if the test is multimeasure or not + // based on the length of the expectation array + let mockTimes = 1; + if (expectation.length > 1) { + mockTimes = expectation.length; + } + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', env.service) + .matchHeader('fiware-servicepath', env.servicePath) + .post(cbMockRoute, function (body) { + mockTimes === 1 ? (receivedContext = body) : receivedContext.push(body); // Save the received body for later comparison + return true; + }) + .times(mockTimes) + .reply(204); + + // Send a measure to the IoT Agent and wait for the response + if (transport === 'MQTT') { + try { + let client = await MQTT.connectAsync('mqtt://' + config.mqtt.host); + await client.publish('/' + measure.qs.k + '/' + measure.qs.i + '/attrs', JSON.stringify(measure.json)); + await client.end(); + } catch (error) { + expect.fail(ERR_MQTT + error); + } + } else { + // HTTP + const response = await sendMeasurePromise(measure); + // Validate the response status code and the response body + expect(response.statusCode, ERR_MEAS_CODE).to.equal(200); + expect(response.body, ERR_MEAS_BODY).to.be.empty; + } + + // Validate Context Broker Expectation + if ((Array.isArray(expectation) && expectation.length > 0) || !Array.isArray(expectation)) { + // Filter empty expectations + expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.deep.equal(expectation); + contextBrokerMock.done(); // Ensure the request was made, no matter the body content + } else { + expect(contextBrokerMock.isDone(), ERR_CB_NOT_EMPTY).to.be.false; + expect(receivedContext, ERR_CB_NOT_EMPTY).to.be.empty; + } +} + +exports.sendMeasurePromise = sendMeasurePromise; +exports.testCase = testCase; diff --git a/test/utils.js b/test/utils.js index 55caf62f4..74d5aa81d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -41,15 +41,6 @@ function delay(ms) { }; } -function sendMeasurePromise(measure) { - return new Promise((resolve, reject) => { - request(measure, function (error, result, body) { - error ? reject(error) : resolve(result); - }); - }); -} - exports.readExampleFile = readExampleFile; exports.delay = delay; exports.request = request; -exports.sendMeasurePromise = sendMeasurePromise; From c245d8b8f58d0e544e04cd72716e38b74e234247 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 27 Oct 2023 17:16:53 +0200 Subject: [PATCH 08/16] Fix doc --- test/functional/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/README.md b/test/functional/README.md index 8f118151e..2086cf711 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -303,8 +303,9 @@ Example of a test case with the loglevel set to `debug`: ```javascript should:[ { - transport: 'MQTT', shouldName: 'should send its value to the Context Broker when using MQTT', + loglevel: 'debug', + transport: 'MQTT', type: 'single', measure: {...}, expectation: {...} From 0a5b7ed1cb991f5eb079963e863dda3590fc91bd Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 27 Oct 2023 17:20:52 +0200 Subject: [PATCH 09/16] fix lint --- test/functional/testCases.js | 2 +- test/functional/testUtils.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 85b1e8979..1d27e4b00 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1,6 +1,6 @@ const config = require('./config-test.js'); -globalEnv = { +const globalEnv = { service: 'smartgondor', servicePath: '/gardens', apikey: '123456', diff --git a/test/functional/testUtils.js b/test/functional/testUtils.js index 8095e2cf5..e1d9ef924 100644 --- a/test/functional/testUtils.js +++ b/test/functional/testUtils.js @@ -67,6 +67,7 @@ function sendMeasurePromise(measure) { async function testCase(measure, expectation, env, config, type, transport) { let receivedContext = []; + let cbMockRoute = ''; // Set the correct route depending if the test is multientity or not if (type === 'multientity') { cbMockRoute = '/v2/op/update'; @@ -81,7 +82,7 @@ async function testCase(measure, expectation, env, config, type, transport) { mockTimes = expectation.length; } - contextBrokerMock = nock('http://192.168.1.1:1026') + let contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', env.service) .matchHeader('fiware-servicepath', env.servicePath) .post(cbMockRoute, function (body) { From 650c7bee3fe21f9f4495e866297f05352a49534c Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 27 Oct 2023 17:22:24 +0200 Subject: [PATCH 10/16] fix deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5a0b8857..9425791d7 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "async-mqtt": "~2.6.3", - "chai": "^4.3.10", + "chai": "~4.3.10", "coveralls": "~3.1.0", "eslint": "~7.5.0", "eslint-config-tamia": "~7.2.5", From 39f5bfce608fbc0126adddaaf56a32941e6c39bd Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 17 Nov 2023 13:15:27 +0100 Subject: [PATCH 11/16] rename file --- .../{functional-tests-auto.js => functional-tests-runner.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/functional/{functional-tests-auto.js => functional-tests-runner.js} (100%) diff --git a/test/functional/functional-tests-auto.js b/test/functional/functional-tests-runner.js similarity index 100% rename from test/functional/functional-tests-auto.js rename to test/functional/functional-tests-runner.js From d415b7fe4a480be890cf36b62c417c2a58353cf7 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Fri, 17 Nov 2023 13:53:22 +0100 Subject: [PATCH 12/16] Use node-lib cases + utils --- package.json | 3 +- test/functional/README.md | 314 ---- test/functional/functional-tests-runner.js | 23 +- test/functional/functional-tests.js | 9 +- test/functional/testCases.js | 1957 +------------------- test/functional/testUtils.js | 124 -- 6 files changed, 29 insertions(+), 2401 deletions(-) delete mode 100644 test/functional/README.md delete mode 100644 test/functional/testUtils.js diff --git a/package.json b/package.json index 9425791d7..6b28ccfb2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "devDependencies": { "async-mqtt": "~2.6.3", "chai": "~4.3.10", + "chai-match-pattern": "~1.3.0", "coveralls": "~3.1.0", "eslint": "~7.5.0", "eslint-config-tamia": "~7.2.5", @@ -66,7 +67,7 @@ "body-parser": "1.20.0", "dateformat": "3.0.3", "express": "4.18.1", - "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master", + "iotagent-node-lib": "github:telefonicaid/iotagent-node-lib#task/add-functional-tests", "logops": "2.1.2", "mqtt": "4.3.7", "sinon": "~6.1.0", diff --git a/test/functional/README.md b/test/functional/README.md deleted file mode 100644 index 2086cf711..000000000 --- a/test/functional/README.md +++ /dev/null @@ -1,314 +0,0 @@ -## Functional test suite - -This directory contains the functional test suite for the IoTA JSON. This test suite is based on mocha and chai. For -mocks, we use the nock library. Additionally, it uses some specific functions to ease to implement the test. Helper -functions are located in the `testUtils.js` file. - -There are 2 tests files in this directory: - -- `fuctional-tests.js`: This file contains the test defined in the "classic way". This means, coded in the JS file as - any other mocha test. It uses the functions defined in the `testUtils.js` file to simplify the tests. -- `functional-tests-auto.js`: This file contains the test defined in the "automatic way". This means, the test cases - are defined as JSON in a separate file (`testCases.js`). This file is loaded by the test suite and the test cases - are automatically generated. This is the recommended way to define the test cases. - -### Automatic test cases - -Each test case is defined as a JSON object in the `testCases.js` file. This file is loaded by the test suite and the -test cases are automatically generated. Each test case is defined as an object with the following elements: - -- `describeName`: The name of the `DESCRIBE` test case. This will be used to generate the test case name in the mocha - test suite. -- `provision`: The JSON object that will be sent to the IoTA JSON provisioning API. This will be used to create the - group. It contains the following elements: - - `url`: The URL of the provisioning API (group) - - `method`: The HTTP method to use (POST) - - `json`: The JSON object that defines the group - - `headers`: The headers to send to the provisioning API. This should contain the `fiware-service` and - `fiware-servicepath` headers. -- `should`: The array of test cases to execute. Each test case is defined as an object with the following elements: - - `transport`: The transport to use to send the measure. This can be `HTTP` or `MQTT`. It uses `HTTP` by default - or if the `transport` element is not defined. See the "Advanced features" section for more information. - - `shouldName`: The name of the `IT` test case. This will be used to generate the test case name in the mocha test - suite. - - `type`: The type of the test case. This can be `single` or `multientity`. See the "Advanced features" section - for more information. - - `measure`: The JSON object that will be sent to the IoTA JSON measure API. This will be used to send the - measure. It contains the following elements: - - `url`: The URL of the measure API (group) - - `method`: The HTTP method to use (POST) - - `qs`: The query string to send to the measure API. This should contain the `i` and `k` parameters. - - `json`: The JSON object that defines the measure - - `expectation`: The JSON object that defines the expectation. This will be used to check that the measure has - been correctly sent to the Context Broker. - -#### Example - -```javascript -{ - describeName: 'Basic group provision with attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: '123456', - entity_type: 'TheLightType2', - cbHost: 'http://192.168.1.1:1026', - commands: [], - lazy: [], - attributes: [ - { - object_id: 's', - name: 'status', - type: 'Boolean' - }, - { - object_id: 't', - name: 'temperature', - type: 'Number' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }, - should:[ - { - shouldName: 'should send its value to the Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - }, - status: { - value: false, - type: 'Boolean' - } - } - }, - { - transport: 'MQTT', - shouldName: 'should send its value to the Context Broker when using MQTT', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - }, - status: { - value: false, - type: 'Boolean' - } - } - } - ] -} -``` - -### Advanced features - -#### Multientity - -This test suite support the multientity feature. To test this feature, you need to set add to the test case the -parameter `type: 'multientity'`. This will automatically take care of the multientity feature. This means that the suite -will configure the mock to listen to `/v2/op/update` instead of `/v2/entities?options=upsert`. - -In particular, it will configure the mock to listen the correct URL. You should define the expectation for the test case -as a batch operation (see the following example). - -```javascript -{ - "entities": [ - { - "id": "TheLightType2:MQTT_2", - "type": "TheLightType2", - "status": { - "value": false, - "type": "Boolean" - } - }, - { - "id": "TheLightType2:MQTT_3", - "type": "TheLightType2", - "temperature": { - "value": 10, - "type": "Number" - } - } - ], - "actionType": "append" -} -``` - -#### Multimeasures - -It is also supported to test cases in which is sent more than one measure. To do so, you need to define the test case -expectation as an array, with one object for each measurement. Then, the suite will recognize the array length and will -expect the same number of NGSI requests. I.E: - -```js -[ - { - id: "TheLightType2:MQTT_2", - type: "TheLightType2", - temperature: { - value: 10, - type: "Number", - }, - status: { - value: false, - type: "Boolean", - }, - }, - { - id: "TheLightType2:MQTT_2", - type: "TheLightType2", - temperature: { - value: 20, - type: "Number", - }, - status: { - value: true, - type: "Boolean", - }, - }, -]; -``` - -You also should define the measure as multimeasure. This is done by defining the measure JSON element as an array of -objects. Each object will be a measure that will be sent to the Context Broker in a different request. I.E: - -```javascript -measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: [ - { - s: false, - t: 10 - }, - { - s: true, - t: 20 - } - ] -} -``` - -#### MQTT Support - -The test suite also supports MQTT for measure sending. To do so, you need to define the measure as MQTT. This is done by -adding the `transport` element to each should case having the value set to `MQTT`. By doing so, the suite will -automatically configure the mock to connect to the MQTT broker and send the measure to the correct topic based on the -`i` and `k` parameters. It will ignore the `url` and `method` parameters present in the measure JSON element. I.E: - -```javascript -should: [ - { - transport: "MQTT", - shouldName: "should send its value to the Context Broker when using MQTT", - type: "single", - measure: { - url: "http://localhost:" + config.http.port + "/iot/json", - method: "POST", - qs: { - i: "MQTT_2", - k: "123456", - }, - json: { - s: false, - t: 10, - }, - }, - expectation: { - id: "TheLightType2:MQTT_2", - type: "TheLightType2", - temperature: { - value: 10, - type: "Number", - }, - status: { - value: false, - type: "Boolean", - }, - }, - }, -]; -``` - -#### No payload reception - -The test suite also supports the case in which the Context Broker does not receive any payload. This is done by defining -the expectation as an empty object. I.E: - -```javascript - ... - expectation: [] - ... -``` - -### Debugging automated tests - -It is possible to debug the automated tests by using the loglevel parameter set to `debug` for each should case. This -parameter configures the IoTA log level. By setting it to `debug`, the agent will log all the debug messages to the -console. This is useful to check the messages sent to the Context Broker. - -It is also useful to debug the test by adding a breakpoint in the `testUtils.js` (read the comments in the file to know -where to add the breakpoint). This will allow you to debug just that particular test case stopping the execution in the -breakpoint. - -Example of a test case with the loglevel set to `debug`: - -```javascript -should:[ - { - shouldName: 'should send its value to the Context Broker when using MQTT', - loglevel: 'debug', - transport: 'MQTT', - type: 'single', - measure: {...}, - expectation: {...} - } -] -``` diff --git a/test/functional/functional-tests-runner.js b/test/functional/functional-tests-runner.js index 141acb12a..2f5d3f5d1 100755 --- a/test/functional/functional-tests-runner.js +++ b/test/functional/functional-tests-runner.js @@ -34,11 +34,15 @@ const expect = chai.expect; const iotAgentLib = require('iotagent-node-lib'); const async = require('async'); const utils = require('../utils'); -const testUtils = require('./testUtils'); +const testUtils = require('../../node_modules/iotagent-node-lib/test/functional/testUtils.js'); const request = utils.request; const logger = require('logops'); +const chaiMatchPattern = require('chai-match-pattern'); +const e = require('express'); +chai.config.truncateThreshold = 0; -const baseTestCases = require('./testCases.js').testCases; +const baseTestCases = require('../../node_modules/iotagent-node-lib/test/functional/testCases.js').testCases; +const jsonTestCases = require('./testCases.js').testCases; const env = { service: 'smartgondor', @@ -56,8 +60,12 @@ let testCases = []; // executed as well. testCases = testCases.concat(baseTestCases); +// Add specific test cases for IoTA JSON +testCases = testCases.concat(jsonTestCases); + describe('FUNCTIONAL TESTS', function () { beforeEach(function (done) { + // Check if the test case should be skipped iotaJson.start(config, function (error) { done(error); }); @@ -70,6 +78,9 @@ describe('FUNCTIONAL TESTS', function () { testCases.forEach((testCase) => { describe(testCase.describeName, function () { beforeEach(function (done) { + if (testCase.skip && testUtils.checkSkip(testCase.skip, 'json')) { + this.skip(); + } if (testCase.loglevel) { logger.setLevel(testCase.loglevel); } @@ -89,6 +100,10 @@ describe('FUNCTIONAL TESTS', function () { testCase.should.forEach((should) => { it(should.shouldName, async function () { + if (testCase.skip && testUtils.checkSkip(testCase.skip, 'json')) { + this.skip(); + } + this.retries(2); // pass the maximum no of retries if (should.loglevel) { // You can use this line to set a breakpoint in the test in order to debug it @@ -101,10 +116,12 @@ describe('FUNCTIONAL TESTS', function () { await testUtils.testCase( should.measure, should.expectation, + testCase.provision, env, config, should.type ? should.type : 'single', - should.transport ? should.transport : 'HTTP' + should.transport ? should.transport : 'HTTP', + should.isRegex ? should.isRegex : false ); }); }); diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js index 662174d23..531b20d7c 100755 --- a/test/functional/functional-tests.js +++ b/test/functional/functional-tests.js @@ -34,10 +34,13 @@ const expect = chai.expect; const iotAgentLib = require('iotagent-node-lib'); const async = require('async'); const utils = require('../utils'); -const testUtils = require('./testUtils'); +const testUtils = require('../../node_modules/iotagent-node-lib/test/functional/testUtils.js'); const request = utils.request; const logger = require('logops'); +var chaiMatchPattern = require('chai-match-pattern'); +chai.use(chaiMatchPattern); +var _ = chaiMatchPattern.getLodashModule(); let contextBrokerMock; const env = { @@ -136,7 +139,7 @@ describe('FUNCTIONAL TESTS', function () { }); it('should send its value to the Context Broker', async function () { - await testUtils.testCase(measure, expectation, env, config, 'single', 'HTTP'); + await testUtils.testCase(measure, expectation, provision, env, config, 'single', 'HTTP'); }); }); @@ -228,7 +231,7 @@ describe('FUNCTIONAL TESTS', function () { it('should send its value to the Context Broker', async function () { // logger.setLevel('DEBUG'); - await testUtils.testCase(measure, expectation, env, config, 'multientity', 'HTTP'); + await testUtils.testCase(measure, expectation, provision, env, config, 'multientity', 'HTTP'); }); }); }); diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 1d27e4b00..d3db90fc6 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -9,1961 +9,6 @@ const globalEnv = { deviceId: 'TestDevice' }; -const testCases = [ - // BASIC TESTS - { - describeName: '0010 - Simple group without attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending measures through http IT should send measures to Context Broker preserving value types', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false, - b: 10, - c: 'text', - d: 10.5, - e: [1, 2], - f: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - a: { - value: false, - type: 'string' - }, - b: { - value: 10, - type: 'string' - }, - c: { - type: 'string', - value: 'text' - }, - d: { - type: 'string', - value: 10.5 - }, - e: { - type: 'string', - value: [1, 2] - }, - f: { - type: 'string', - value: { - a: 1, - b: 2 - } - } - } - }, - { - transport: 'MQTT', - shouldName: - 'B - WHEN sending measures through mqtt IT should send measures to Context Broker preserving value types', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false, - b: 10, - c: 'text', - d: 10.5, - e: [1, 2], - f: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - a: { - value: false, - type: 'string' - }, - b: { - value: 10, - type: 'string' - }, - c: { - type: 'string', - value: 'text' - }, - d: { - type: 'string', - value: 10.5 - }, - e: { - type: 'string', - value: [1, 2] - }, - f: { - type: 'string', - value: { - a: 1, - b: 2 - } - } - } - } - ] - }, - { - describeName: '0020 - Simple group with attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Boolean' - }, - { - object_id: 'b', - name: 'attr_b', - type: 'Integer' - }, - { - object_id: 'c', - name: 'attr_c', - type: 'Text' - }, - { - object_id: 'd', - name: 'attr_d', - type: 'Float' - }, - { - object_id: 'e', - name: 'attr_e', - type: 'Array' - }, - { - object_id: 'f', - name: 'attr_f', - type: 'Object' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types and name mappings', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false, - b: 10, - c: 'text', - d: 10.5, - e: [1, 2], - f: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: false, - type: 'Boolean' - }, - attr_b: { - value: 10, - type: 'Integer' - }, - attr_c: { - type: 'Text', - value: 'text' - }, - attr_d: { - type: 'Float', - value: 10.5 - }, - attr_e: { - type: 'Array', - value: [1, 2] - }, - attr_f: { - type: 'Object', - value: { - a: 1, - b: 2 - } - } - } - }, - { - shouldName: - 'B - WHEN sending defined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types and name mappings', - transport: 'MQTT', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false, - b: 10, - c: 'text', - d: 10.5, - e: [1, 2], - f: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: false, - type: 'Boolean' - }, - attr_b: { - value: 10, - type: 'Integer' - }, - attr_c: { - type: 'Text', - value: 'text' - }, - attr_d: { - type: 'Float', - value: 10.5 - }, - attr_e: { - type: 'Array', - value: [1, 2] - }, - attr_f: { - type: 'Object', - value: { - a: 1, - b: 2 - } - } - } - }, - { - shouldName: - 'C - WHEN sending undefined object_ids (measures) through http IT should send measures to Context Broker preserving value types', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - u: false, - v: 10, - w: 'text', - y: 10.5, - x: [1, 2], - z: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - u: { - value: false, - type: 'string' - }, - v: { - value: 10, - type: 'string' - }, - w: { - type: 'string', - value: 'text' - }, - y: { - type: 'string', - value: 10.5 - }, - x: { - type: 'string', - value: [1, 2] - }, - z: { - type: 'string', - value: { - a: 1, - b: 2 - } - } - } - }, - { - shouldName: - 'D - WHEN sending undefined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types', - transport: 'MQTT', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - u: false, - v: 10, - w: 'text', - y: 10.5, - x: [1, 2], - z: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - u: { - value: false, - type: 'string' - }, - v: { - value: 10, - type: 'string' - }, - w: { - type: 'string', - value: 'text' - }, - y: { - type: 'string', - value: 10.5 - }, - x: { - type: 'string', - value: [1, 2] - }, - z: { - type: 'string', - value: { - a: 1, - b: 2 - } - } - } - } - ] - }, - // JEXL TESTS - { - describeName: '0030 - Simple group with attribute + JEXL expression boolean (!)', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Boolean', - expression: '!a' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a boolean value (false) through http IT should send to Context Broker the value true ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: true, - type: 'Boolean' - } - } - }, - { - shouldName: - 'B - WHEN sending a numeric value (3) through http IT should send to Context Broker the value false ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 3 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: false, - type: 'Boolean' - } - } - }, - { - shouldName: - 'C - WHEN sending a text value (abcd) through http IT should send to Context Broker the value false ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'abcd' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: false, - type: 'Boolean' - } - } - }, - { - shouldName: - 'D - WHEN not sending the object ID (undefined) required by the expression through http IT should send to Context Broker the value true ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - b: 1 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: true, - type: 'Boolean' - }, - b: { - value: 1, - type: 'string' - } - } - } - ] - }, - { - describeName: '0040 - Simple group with attribute + JEXL expression numeric (+3)', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number', - expression: 'a+3' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 4 ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: true - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 4, - type: 'Number' - } - } - }, - { - shouldName: - 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 3 ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 3, - type: 'Number' - } - } - }, - { - shouldName: - 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -4 ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: -7 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: -4, - type: 'Number' - } - } - }, - { - shouldName: - 'D - WHEN sending a text value (abcd) through http IT should send to Context Broker the value abcd3', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'abcd' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 'abcd3', - type: 'Number' - } - } - }, - { - shouldName: - 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - b: 1 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - b: { - value: 1, - type: 'string' - } - } - } - ] - }, - { - describeName: '0050 - Simple group with attribute + JEXL expression numeric (*3)', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number', - expression: 'a*3' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 3 ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: true - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 3, - type: 'Number' - } - } - }, - { - shouldName: - 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 0', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 0, - type: 'Number' - } - } - }, - { - shouldName: - 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -21 ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: -7 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: -21, - type: 'Number' - } - } - }, - { - shouldName: - 'D - WHEN sending a text value (abcd) through http IT should not send to anything to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'abcd' - } - }, - expectation: [] - }, - { - shouldName: - 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - b: 1 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - b: { - value: 1, - type: 'string' - } - } - } - ] - }, - { - describeName: '0060 - Simple group with attribute + JEXL expression text (a|substr(0,2))', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number', - expression: 'a|substr(0,3)' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value "tru" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: true - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 'tru', - type: 'Number' - } - } - }, - { - shouldName: - 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value "fal"', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 'fal', - type: 'Number' - } - } - }, - { - shouldName: - 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value "-7" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: -7 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: '-7', - type: 'Number' - } - } - }, - { - shouldName: - 'D - WHEN sending a text value (abcd) through http IT should not send to anything to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'abcd' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 'abc', - type: 'Number' - } - } - }, - { - shouldName: - 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - b: 1 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 'und', - type: 'Number' - }, - b: { - value: 1, - type: 'string' - } - } - } - ] - }, - { - describeName: '0070 - Simple group with attribute + chained JEXL expression text (a|substr(0,2))', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'a', - type: 'Text', - expression: 'a | trim | replacestr("hello","hi")' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a expected value ("Say hello and smile") through http IT should send to Context Broker the value "Say hi and smile" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'Say hello and smile' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - a: { - value: 'Say hi and smile', - type: 'Text' - } - } - } - ] - }, - { - describeName: '0080 - Simple group with attribute + JEXL expression text reusing previous values)', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number', - expression: 'a*10' - }, - { - object_id: 'b', - name: 'attr_b', - type: 'Number', - expression: 'attr_a*10' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a value (a:3) through http IT should apply nested expressions and send to Context Broker the value "attr_b=300" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 3 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 30, - type: 'Number' - }, - attr_b: { - value: 300, - type: 'Number' - } - } - } - ] - }, - { - describeName: '0090 - Simple group with attribute + JEXL expression referencing static attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number', - expression: 'a*coef' - } - ], - static_attributes: [ - { - name: 'coef', - type: 'Number', - value: 1.5 - } - ] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a value (a:6) through http IT should apply the expression using the static attribute value and send to Context Broker the value "attr_a:9" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 6 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 9, - type: 'Number' - }, - coef: { - value: 1.5, - type: 'Number' - } - } - } - ] - }, - { - describeName: '0100 - Simple group with attribute + JEXL expression referencing context attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Text', - expression: 'a+":"+service+subservice+id+type' - } - ] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a value (text) through http IT should apply the expression using the context attributes value and send to Context Broker the value "text:smartgondor/gardensTestDeviceTestType" ', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 'text' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: - 'text:' + - globalEnv.service + - globalEnv.servicePath + - globalEnv.deviceId + - globalEnv.entity_type, - type: 'Text' - } - } - } - ] - }, - { - describeName: '0110 - Simple group with attributes + JEXL multiples expressions at same time', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Boolean', - expression: '!a' - }, - { - object_id: 'b', - name: 'attr_b', - type: 'Integer', - expression: 'b+1' - }, - { - object_id: 'c', - name: 'attr_c', - type: 'Text', - expression: 'c+":"+service+subservice+id+type' - }, - { - object_id: 'd', - name: 'attr_d', - type: 'Float', - expression: 'd/2+attr_b' - }, - { - object_id: 'e', - name: 'attr_e', - type: 'Array', - expression: 'e|concatarr([3,4])' - }, - { - object_id: 'f', - name: 'attr_f', - type: 'Object', - expression: '{coordinates: [f.a,f.b], type: "Point"}' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending multiples object_ids (measures) through http IT should send measures to Context Broker applying all expressions at same time', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: false, - b: 10, - c: 'text', - d: 10.5, - e: [1, 2], - f: { a: 1, b: 2 } - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: true, - type: 'Boolean' - }, - attr_b: { - value: 11, - type: 'Integer' - }, - attr_c: { - type: 'Text', - value: - 'text:' + - globalEnv.service + - globalEnv.servicePath + - globalEnv.deviceId + - globalEnv.entity_type - }, - attr_d: { - type: 'Float', - value: 16.25 - }, - attr_e: { - type: 'Array', - value: [1, 2, 3, 4] - }, - attr_f: { - type: 'Object', - value: { coordinates: [1, 2], type: 'Point' } - } - } - } - ] - }, - // TIMESTAMP TESTS - { - describeName: '0150 - Simple group with attribute + timestamp:false', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - timestamp: false, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number' - } - ] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a measure not named TimeInstant through http IT should not add the timestamp to the attributes sent to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 23 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 23, - type: 'Number' - } - } - }, - { - shouldName: - 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other attributes sent to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - TimeInstant: '2015-12-14T08:06:01.468Z', - a: 23 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 23, - type: 'Number' - }, - TimeInstant: { - value: '2015-12-14T08:06:01.468Z', - type: 'DateTime' - } - } - } - ] - }, - { - describeName: '0160 - Simple group with attribute + timestamp:true', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - timestamp: true, - commands: [], - lazy: [], - attributes: [ - { - object_id: 'a', - name: 'attr_a', - type: 'Number' - } - ] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: - 'A - WHEN sending a measure not named TimeInstant through http IT should add the timestamp to the attributes sent to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - a: 23 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 23, - type: 'Number', - metadata: { - TimeInstant: { - type: 'DateTime', - value: '2015-12-14T08:06:01.468Z' - } - } - } - } - }, - { - shouldName: - 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other attributes sent to Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - TimeInstant: '2015-12-14T08:06:01.468Z', - a: 23 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - attr_a: { - value: 23, - type: 'Number', - metadata: { - TimeInstant: { - type: 'DateTime', - value: '2015-12-14T08:06:01.468Z' - } - } - }, - TimeInstant: { - value: '2015-12-14T08:06:01.468Z', - type: 'DateTime' - } - } - } - ] - }, - - // OTHER TESTS - - { - describeName: '0010 - Provision de grupo sin atributos', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: globalEnv.apikey, - entity_type: globalEnv.entity_type, - commands: [], - lazy: [], - attributes: [ - { - object_id: 's', - name: 'status', - type: 'Boolean' - }, - { - object_id: 't', - name: 'temperature', - type: 'Number' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': globalEnv.service, - 'fiware-servicepath': globalEnv.servicePath - } - }, - should: [ - { - shouldName: 'A - Enviar su valor al Context Broker cuando se usa HTTP', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - temperature: { - value: 10, - type: 'Number' - }, - status: { - value: false, - type: 'Boolean' - } - } - }, - { - transport: 'MQTT', - shouldName: 'should send its value to the Context Broker when using MQTT', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: globalEnv.deviceId, - k: globalEnv.apikey - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - temperature: { - value: 10, - type: 'Number' - }, - status: { - value: false, - type: 'Boolean' - } - } - } - ] - }, - { - describeName: 'Basic group provision with attributes', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: '123456', - entity_type: 'TheLightType2', - cbHost: 'http://192.168.1.1:1026', - commands: [], - lazy: [], - attributes: [ - { - object_id: 's', - name: 'status', - type: 'Boolean' - }, - { - object_id: 't', - name: 'temperature', - type: 'Number' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }, - should: [ - { - transport: 'MQTT', - shouldName: 'should send its value to the Context Broker', - type: 'single', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - }, - status: { - value: false, - type: 'Boolean' - } - } - } - ] - }, - { - describeName: 'Basic group provision with attributes and multientity', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: '123456', - entity_type: 'TheLightType2', - cbHost: 'http://192.168.1.1:1026', - commands: [], - lazy: [], - attributes: [ - { - object_id: 's', - name: 'status', - type: 'Boolean' - }, - { - object_id: 't', - name: 'temperature', - type: 'Number', - entity_name: 'TheLightType2:MQTT_3', - entity_type: 'TheLightType2' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }, - should: [ - { - shouldName: 'should send its value to the Context Broker', - type: 'multientity', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: { - s: false, - t: 10 - } - }, - expectation: { - entities: [ - { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - status: { - value: false, - type: 'Boolean' - } - }, - { - id: 'TheLightType2:MQTT_3', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - } - } - ], - actionType: 'append' - } - }, - { - shouldName: 'should send its value to the Context Broker with multimeasures', - type: 'multientity', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: [ - { - s: false, - t: 10 - }, - { - s: true, - t: 20 - } - ] - }, - expectation: [ - { - entities: [ - { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - status: { - value: false, - type: 'Boolean' - } - }, - { - id: 'TheLightType2:MQTT_3', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - } - } - ], - actionType: 'append' - }, - { - entities: [ - { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - status: { - value: true, - type: 'Boolean' - } - }, - { - id: 'TheLightType2:MQTT_3', - type: 'TheLightType2', - temperature: { - value: 20, - type: 'Number' - } - } - ], - actionType: 'append' - } - ] - } - ] - }, - { - describeName: 'Basic group provision with attributes and multientity', - provision: { - url: 'http://localhost:' + config.iota.server.port + '/iot/services', - method: 'POST', - json: { - services: [ - { - resource: '/iot/json', - apikey: '123456', - entity_type: 'TheLightType2', - cbHost: 'http://192.168.1.1:1026', - commands: [], - lazy: [], - attributes: [ - { - object_id: 's', - name: 'status', - type: 'Boolean' - }, - { - object_id: 't', - name: 'temperature', - type: 'Number', - entity_name: 'TheLightType2:MQTT_3', - entity_type: 'TheLightType2' - } - ], - static_attributes: [] - } - ] - }, - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }, - should: [ - { - shouldName: 'should send its value to the Context Broker', - type: 'multientity', - measure: { - url: 'http://localhost:' + config.http.port + '/iot/json', - method: 'POST', - qs: { - i: 'MQTT_2', - k: '123456' - }, - json: [ - { - s: false, - t: 10 - }, - { - s: true, - t: 20 - } - ] - }, - expectation: [ - { - entities: [ - { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - status: { - value: false, - type: 'Boolean' - } - }, - { - id: 'TheLightType2:MQTT_3', - type: 'TheLightType2', - temperature: { - value: 10, - type: 'Number' - } - } - ], - actionType: 'append' - }, - { - entities: [ - { - id: 'TheLightType2:MQTT_2', - type: 'TheLightType2', - status: { - value: true, - type: 'Boolean' - } - }, - { - id: 'TheLightType2:MQTT_3', - type: 'TheLightType2', - temperature: { - value: 20, - type: 'Number' - } - } - ], - actionType: 'append' - } - ] - } - ] - } -]; +const testCases = []; exports.testCases = testCases; diff --git a/test/functional/testUtils.js b/test/functional/testUtils.js deleted file mode 100644 index e1d9ef924..000000000 --- a/test/functional/testUtils.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U - * - * This file is part of iotagent-json - * - * iotagent-json is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * iotagent-json is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with iotagent-json. - * If not, seehttp://www.gnu.org/licenses/. - * - * For those usages not covered by the GNU Affero General Public License - * please contact with::[contacto@tid.es] - * - * Modified by: Miguel Angel Pedraza - */ - -/* eslint-disable no-unused-vars*/ -/* eslint-disable no-unused-expressions*/ - -const nock = require('nock'); -const request = require('iotagent-node-lib').request; -const async = require('async'); -const chai = require('chai'); -const expect = chai.expect; -const MQTT = require('async-mqtt'); - -// Error messages -const ERR_CB_EXPECTATION_DIFFER = 'Assertion Error - Context Broker received payload differs from expectation'; -const ERR_MEAS_BODY = 'Assertion Error - Measure response is not empty'; -const ERR_MEAS_CODE = 'Assertion Error - Measure response status code differs from 200'; -const ERR_MQTT = 'Error with MQTT: '; -const ERR_CB_NOT_EMPTY = 'Assertion Error - unexpected Context Broker request received (no request expected)'; - -/** - * Send a measure to the IoT Agent - * @brief Sends a measure to the IoT Agent and returns a promise with the response - * - * @param {Object} measure Measure to be sent to the IoT Agent - */ -function sendMeasurePromise(measure) { - return new Promise((resolve, reject) => { - request(measure, function (error, result, body) { - error ? reject(error) : resolve(result); - }); - }); -} - -/** - * Test Case function - * @brief Sends a measure to the IoT Agent and validates the response - * and validates the Context Broker expectation - * - * @param {Object} measure Measure to be sent to the IoT Agent - * @param {Object} expectation Expectation for the Context Broker - * @param {Object} env Environment variables - * @param {String} type Type of test (multientity or multimeasure) - */ - -async function testCase(measure, expectation, env, config, type, transport) { - let receivedContext = []; - let cbMockRoute = ''; - // Set the correct route depending if the test is multientity or not - if (type === 'multientity') { - cbMockRoute = '/v2/op/update'; - } else { - cbMockRoute = '/v2/entities?options=upsert'; - } - - // Set the correct mock times depending if the test is multimeasure or not - // based on the length of the expectation array - let mockTimes = 1; - if (expectation.length > 1) { - mockTimes = expectation.length; - } - - let contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', env.service) - .matchHeader('fiware-servicepath', env.servicePath) - .post(cbMockRoute, function (body) { - mockTimes === 1 ? (receivedContext = body) : receivedContext.push(body); // Save the received body for later comparison - return true; - }) - .times(mockTimes) - .reply(204); - - // Send a measure to the IoT Agent and wait for the response - if (transport === 'MQTT') { - try { - let client = await MQTT.connectAsync('mqtt://' + config.mqtt.host); - await client.publish('/' + measure.qs.k + '/' + measure.qs.i + '/attrs', JSON.stringify(measure.json)); - await client.end(); - } catch (error) { - expect.fail(ERR_MQTT + error); - } - } else { - // HTTP - const response = await sendMeasurePromise(measure); - // Validate the response status code and the response body - expect(response.statusCode, ERR_MEAS_CODE).to.equal(200); - expect(response.body, ERR_MEAS_BODY).to.be.empty; - } - - // Validate Context Broker Expectation - if ((Array.isArray(expectation) && expectation.length > 0) || !Array.isArray(expectation)) { - // Filter empty expectations - expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.deep.equal(expectation); - contextBrokerMock.done(); // Ensure the request was made, no matter the body content - } else { - expect(contextBrokerMock.isDone(), ERR_CB_NOT_EMPTY).to.be.false; - expect(receivedContext, ERR_CB_NOT_EMPTY).to.be.empty; - } -} - -exports.sendMeasurePromise = sendMeasurePromise; -exports.testCase = testCase; From a8b531e275d9ee8d18a9a0beb60e6e509c65cef8 Mon Sep 17 00:00:00 2001 From: mapedraza Date: Tue, 21 Nov 2023 17:02:33 +0100 Subject: [PATCH 13/16] Add header --- test/functional/testCases.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index d3db90fc6..e5612fe68 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1,3 +1,30 @@ +/* + * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U + * + * This file is part of iotagent-json + * + * iotagent-json is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * iotagent-json is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with iotagent-json. + * If not, seehttp://www.gnu.org/licenses/. + * + * For those usages not covered by the GNU Affero General Public License + * please contact with::[contacto@tid.es] + * + * Modified by: Miguel Angel Pedraza + */ + +/* eslint-disable no-unused-vars */ + const config = require('./config-test.js'); const globalEnv = { From f9b53e95cf59ed9723f5113a2f6f0104623b8f2d Mon Sep 17 00:00:00 2001 From: mapedraza Date: Wed, 22 Nov 2023 13:10:41 +0100 Subject: [PATCH 14/16] Add readme file --- test/functional/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/functional/README.md diff --git a/test/functional/README.md b/test/functional/README.md new file mode 100644 index 000000000..a4a7cdbff --- /dev/null +++ b/test/functional/README.md @@ -0,0 +1,18 @@ +## Functional test suite + +This directory contains the functional test suite for the project. This relies on the IoT Agent Node Lib Functional test +suite. For further information, visit the +[documentation](https://github.com/telefonicaid/iotagent-node-lib/tree/master/test/functional). + +The `functional-tests-runner.js` script is used to run the functional test suite. It is similar to the one used in the +IoT Agent Node Lib, but it has been adapted to the particularities of the IoT Agent JSON. This script imports both the +IoT Agent Node Lib `testCases.js` and the local `testCases.js` file. The latter contains the test cases that are +specific to the IoT Agent JSON, while the former contains the test cases that are common to all IoT Agents. + +If you plan to include a tests for an specific feature of the IoT Agent JSON, please, consider added it into the IoTA +Node Lib `testCases.js` file and use the skip feature to avoid running it for other agents that do not support it. You +can check the documentation of the IoT Agent Node Lib Functional test suite linked previously for further information. + +Additionally, the `functional-tests.js` file is a simple example of how to implement code bases tests using the IoT +Agent Node Lib Functional test suite utilities. (This test is coded implemented and suites more complex cases than the +ones contained in the `testCases.js` file). From 9ef8e9c48a8c1add28a30f25d7fde69e56ef51b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 22 Nov 2023 13:12:52 +0100 Subject: [PATCH 15/16] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b28ccfb2..748e8f341 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "body-parser": "1.20.0", "dateformat": "3.0.3", "express": "4.18.1", - "iotagent-node-lib": "github:telefonicaid/iotagent-node-lib#task/add-functional-tests", + "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master", "logops": "2.1.2", "mqtt": "4.3.7", "sinon": "~6.1.0", From f4ab12666161fdc98e20c1f68e3b03bf80ff9643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 22 Nov 2023 13:39:58 +0100 Subject: [PATCH 16/16] ADD link to new README.md from root README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9e814014b..c4e3f5192 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,8 @@ To run tests, type npm test ``` +Please have a look to extra information about functional tests in [this specific document](test/functional/README.md). + #### Requirements All the tests are designed to test end-to-end scenarios, and there are some requirements for its current execution: