iotagent-node-lib 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.github/workflows/ci.yml +6 -7
  2. package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  3. package/.nyc_output/processinfo/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  4. package/.nyc_output/processinfo/index.json +1 -1
  5. package/CHANGES_NEXT_RELEASE +1 -2
  6. package/doc/advanced-topics.md +78 -3
  7. package/doc/apiary/iotagent.apib +5 -5
  8. package/doc/architecture.md +52 -5
  9. package/doc/deprecated.md +10 -7
  10. package/doc/expressionLanguage.md +46 -35
  11. package/doc/getting-started.md +1 -1
  12. package/doc/usermanual.md +18 -16
  13. package/examples/TTOpen-service.json +1 -1
  14. package/lib/errors.js +9 -1
  15. package/lib/fiware-iotagent-lib.js +2 -1
  16. package/lib/jexlTranformsMap.js +12 -1
  17. package/lib/plugins/bidirectionalData.js +104 -6
  18. package/lib/plugins/expressionPlugin.js +10 -0
  19. package/lib/plugins/jexlParser.js +2 -2
  20. package/lib/plugins/pluginUtils.js +2 -1
  21. package/lib/request-shim.js +2 -1
  22. package/lib/services/devices/deviceService.js +53 -21
  23. package/lib/services/devices/devices-NGSI-v2.js +3 -1
  24. package/lib/services/ngsi/entities-NGSI-v2.js +14 -4
  25. package/lib/services/northBound/contextServer-NGSI-LD.js +96 -20
  26. package/lib/services/northBound/contextServer-NGSI-v2.js +1 -0
  27. package/lib/services/northBound/restUtils.js +3 -5
  28. package/lib/templates/deviceGroup.json +1 -1
  29. package/package.json +2 -2
  30. package/test/unit/examples/deviceProvisioningRequests/provisionNewDeviceEmpty.json +43 -0
  31. package/test/unit/examples/mongoCollections/configurations.json +3 -3
  32. package/test/unit/expressions/jexlExpression-test.js +29 -8
  33. package/test/unit/general/deviceService-test.js +31 -29
  34. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +62 -10
  35. package/test/unit/mongodb/mongodb-group-registry-test.js +24 -0
  36. package/test/unit/mongodb/mongodb-registry-test.js +68 -10
  37. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin30.json +11 -0
  38. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
  39. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
  40. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +18 -4
  41. package/test/unit/ngsi-ld/general/deviceService-test.js +31 -29
  42. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +656 -2
  43. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
  44. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
  45. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +0 -3
  46. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json +22 -0
  47. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
  48. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +21 -6
  49. package/test/unit/ngsiv2/general/deviceService-test.js +25 -23
  50. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
  51. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
  52. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +58 -0
  53. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +25 -6
  54. package/.nyc_output/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  55. package/.nyc_output/processinfo/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  56. package/config +0 -0
  57. package/docker/Mosquitto/Dockerfile.debian +0 -38
  58. package/docker/Mosquitto/Dockerfile.debian~ +0 -23
  59. package/lib/plugins/multiEntity.js_avg2 +0 -343
  60. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js_avg2 +0 -1224
@@ -0,0 +1,111 @@
1
+ /*
2
+ * Copyright 2022 Telefonica Investigación y Desarrollo, S.A.U
3
+ *
4
+ * This file is part of fiware-iotagent-lib
5
+ *
6
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
7
+ * modify it under the terms of the GNU Affero General Public License as
8
+ * published by the Free Software Foundation, either version 3 of the License,
9
+ * or (at your option) any later version.
10
+ *
11
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ * See the GNU Affero General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU Affero General Public
17
+ * License along with fiware-iotagent-lib.
18
+ * If not, see http://www.gnu.org/licenses/.
19
+ *
20
+ * For those usages not covered by the GNU Affero General Public License
21
+ * please contact with::daniel.moranjimenez@telefonica.com
22
+ *
23
+ * Modified by: Jason Fox - FIWARE Foundation
24
+ */
25
+
26
+ /* eslint-disable no-unused-vars */
27
+
28
+ const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
29
+ const utils = require('../../../tools/utils');
30
+ const request = utils.request;
31
+ const should = require('should');
32
+
33
+ let contextBrokerMock;
34
+ const iotAgentConfig = {
35
+ contextBroker: {
36
+ host: '192.168.1.1',
37
+ port: '1026',
38
+ ngsiVersion: 'ld',
39
+ jsonLdContext: 'http://context.json-ld'
40
+ },
41
+ server: {
42
+ port: 4041
43
+ },
44
+ types: {},
45
+ service: 'smartgondor',
46
+ subservice: 'gardens',
47
+ providerUrl: 'http://smartgondor.com'
48
+ };
49
+
50
+ describe('NGSI-LD - Unsupported Endpoints', function () {
51
+ beforeEach(function (done) {
52
+ iotAgentLib.activate(iotAgentConfig, function () {
53
+ iotAgentLib.clearAll(function () {
54
+ done();
55
+ });
56
+ });
57
+ });
58
+
59
+ afterEach(function (done) {
60
+ iotAgentLib.clearAll(function () {
61
+ iotAgentLib.deactivate(done);
62
+ });
63
+ });
64
+
65
+ describe('When accessing an Unsupported Endpoint', function () {
66
+ it('GET /entities should return a valid NSGI-LD error message', function (done) {
67
+ const options = {
68
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/ngsi-ld/v1/entities',
69
+ method: 'GET'
70
+ };
71
+ request(options, function (error, response, body) {
72
+ response.statusCode.should.equal(501);
73
+ done();
74
+ });
75
+ });
76
+ it('POST /entities should return a valid NSGI-LD error message', function (done) {
77
+ const options = {
78
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/ngsi-ld/v1/entities',
79
+ method: 'POST',
80
+ json: {}
81
+ };
82
+ request(options, function (error, response, body) {
83
+ response.statusCode.should.equal(501);
84
+ done();
85
+ });
86
+ });
87
+ it('DELETE /entities/<entity-id> should return a valid NSGI-LD error message', function (done) {
88
+ const options = {
89
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/ngsi-ld/v1/entities/urn:ngsi-ld:entity',
90
+ method: 'DELETE'
91
+ };
92
+ request(options, function (error, response, body) {
93
+ response.statusCode.should.equal(501);
94
+ done();
95
+ });
96
+ });
97
+ it('DELETE /entities/<entity-id>/attrs/<attr-name> should return a valid NSGI-LD error message', function (done) {
98
+ const options = {
99
+ url:
100
+ 'http://localhost:' +
101
+ iotAgentConfig.server.port +
102
+ '/ngsi-ld/v1/entities/urn:ngsi-ld:entity/attrs/att',
103
+ method: 'DELETE'
104
+ };
105
+ request(options, function (error, response, body) {
106
+ response.statusCode.should.equal(501);
107
+ done();
108
+ });
109
+ });
110
+ });
111
+ });
@@ -264,6 +264,227 @@ describe('NGSI-LD - Bidirectional data plugin', function () {
264
264
  });
265
265
  });
266
266
 
267
+ describe('When a notification with metadata arrives for a bidirectional attribute', function () {
268
+ const notificationOptions = {
269
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
270
+ method: 'POST',
271
+ json: utils.readExampleFile(
272
+ './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json'
273
+ ),
274
+ headers: {
275
+ 'fiware-service': 'smartgondor',
276
+ 'fiware-servicepath': '/gardens'
277
+ }
278
+ };
279
+ let executedHandler = false;
280
+
281
+ beforeEach(function () {
282
+ contextBrokerMock
283
+ .matchHeader('fiware-service', 'smartgondor')
284
+ .post(
285
+ '/ngsi-ld/v1/subscriptions/',
286
+ utils.readExampleFile(
287
+ './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
288
+ )
289
+ )
290
+ .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
291
+
292
+ contextBrokerMock
293
+ .matchHeader('fiware-service', 'smartgondor')
294
+ .post(
295
+ '/ngsi-ld/v1/entityOperations/upsert/',
296
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
297
+ )
298
+ .reply(204);
299
+ });
300
+
301
+ afterEach(function () {
302
+ iotAgentLib.setNotificationHandler();
303
+ });
304
+
305
+ it('should execute the original handler', function (done) {
306
+ function mockedHandler(device, notification, callback) {
307
+ notification[0].name.should.equal('location');
308
+ notification[0].value.should.equal('12.4, -9.6');
309
+ notification[0].metadata.qos.value.should.equal(1);
310
+ executedHandler = true;
311
+ callback();
312
+ }
313
+
314
+ iotAgentLib.setNotificationHandler(mockedHandler);
315
+
316
+ request(options, function (error, response, body) {
317
+ request(notificationOptions, function (error, response, body) {
318
+ executedHandler.should.equal(true);
319
+ contextBrokerMock.done();
320
+ done();
321
+ });
322
+ });
323
+ });
324
+
325
+ it('should return a 200 OK', function (done) {
326
+ function mockedHandler(device, notification, callback) {
327
+ executedHandler = true;
328
+ callback();
329
+ }
330
+
331
+ iotAgentLib.setNotificationHandler(mockedHandler);
332
+
333
+ request(options, function (error, response, body) {
334
+ request(notificationOptions, function (error, response, body) {
335
+ response.statusCode.should.equal(200);
336
+ contextBrokerMock.done();
337
+ done();
338
+ });
339
+ });
340
+ });
341
+
342
+ it('should return the transformed values', function (done) {
343
+ let transformedHandler = false;
344
+
345
+ function mockedHandler(device, values, callback) {
346
+ let latitudeFound = false;
347
+ let longitudeFound = false;
348
+
349
+ for (let i = 0; i < values.length; i++) {
350
+ if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
351
+ latitudeFound = true;
352
+ }
353
+
354
+ if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
355
+ longitudeFound = true;
356
+ }
357
+ }
358
+
359
+ transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
360
+ callback();
361
+ }
362
+
363
+ iotAgentLib.setNotificationHandler(mockedHandler);
364
+
365
+ request(options, function (error, response, body) {
366
+ request(notificationOptions, function (error, response, body) {
367
+ contextBrokerMock.done();
368
+ transformedHandler.should.equal(true);
369
+ done();
370
+ });
371
+ });
372
+ });
373
+ });
374
+
375
+ describe('When a sequential notification with datasetId arrives for a bidirectional attribute', function () {
376
+ const notificationOptions = {
377
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
378
+ method: 'POST',
379
+ json: utils.readExampleFile(
380
+ './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json'
381
+ ),
382
+ headers: {
383
+ 'fiware-service': 'smartgondor',
384
+ 'fiware-servicepath': '/gardens'
385
+ }
386
+ };
387
+ let executedHandler = false;
388
+
389
+ beforeEach(function () {
390
+ contextBrokerMock
391
+ .matchHeader('fiware-service', 'smartgondor')
392
+ .post(
393
+ '/ngsi-ld/v1/subscriptions/',
394
+ utils.readExampleFile(
395
+ './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
396
+ )
397
+ )
398
+ .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
399
+
400
+ contextBrokerMock
401
+ .matchHeader('fiware-service', 'smartgondor')
402
+ .post(
403
+ '/ngsi-ld/v1/entityOperations/upsert/',
404
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
405
+ )
406
+ .reply(204);
407
+ });
408
+
409
+ afterEach(function () {
410
+ iotAgentLib.setNotificationHandler();
411
+ });
412
+
413
+ it('should execute the original handler', function (done) {
414
+ function mockedHandler(device, notification, callback) {
415
+ notification[0].name.should.equal('location');
416
+ notification[0].value.should.equal('12.4, -9.6');
417
+ notification[0].datasetId.should.equal('urn:ngsi-ld:Property:do-this');
418
+
419
+ notification[1].name.should.equal('location');
420
+ notification[1].value.should.equal('6, 10');
421
+ notification[1].datasetId.should.equal('urn:ngsi-ld:Property:then-do-this');
422
+
423
+ executedHandler = true;
424
+ callback();
425
+ }
426
+
427
+ iotAgentLib.setNotificationHandler(mockedHandler);
428
+
429
+ request(options, function (error, response, body) {
430
+ request(notificationOptions, function (error, response, body) {
431
+ executedHandler.should.equal(true);
432
+ contextBrokerMock.done();
433
+ done();
434
+ });
435
+ });
436
+ });
437
+
438
+ it('should return a 200 OK', function (done) {
439
+ function mockedHandler(device, notification, callback) {
440
+ executedHandler = true;
441
+ callback();
442
+ }
443
+
444
+ iotAgentLib.setNotificationHandler(mockedHandler);
445
+
446
+ request(options, function (error, response, body) {
447
+ request(notificationOptions, function (error, response, body) {
448
+ response.statusCode.should.equal(200);
449
+ contextBrokerMock.done();
450
+ done();
451
+ });
452
+ });
453
+ });
454
+
455
+ it('should return the transformed values', function (done) {
456
+ let transformedHandler = false;
457
+
458
+ function mockedHandler(device, values, callback) {
459
+ let latitudeFound = false;
460
+ let longitudeFound = false;
461
+
462
+ for (let i = 0; i < values.length; i++) {
463
+ if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
464
+ latitudeFound = true;
465
+ }
466
+
467
+ if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
468
+ longitudeFound = true;
469
+ }
470
+ }
471
+
472
+ transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
473
+ callback();
474
+ }
475
+
476
+ iotAgentLib.setNotificationHandler(mockedHandler);
477
+
478
+ request(options, function (error, response, body) {
479
+ request(notificationOptions, function (error, response, body) {
480
+ contextBrokerMock.done();
481
+ transformedHandler.should.equal(true);
482
+ done();
483
+ });
484
+ });
485
+ });
486
+ });
487
+
267
488
  describe('When a new Group provisioning request arrives with bidirectional attributes', function () {
268
489
  const provisionGroup = {
269
490
  url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
@@ -65,7 +65,6 @@ const optionsCreationDefault = {
65
65
  services: [
66
66
  {
67
67
  apikey: 'default-test',
68
- cbroker: 'http://orion:1026',
69
68
  entity_type: 'Device',
70
69
  resource: '/iot/default',
71
70
  attributes: [
@@ -90,7 +89,6 @@ const optionsCreationV2 = {
90
89
  services: [
91
90
  {
92
91
  apikey: 'v2-test',
93
- cbroker: 'http://orion:1026',
94
92
  ngsiVersion: 'v2',
95
93
  entity_type: 'Device',
96
94
  resource: '/iot/v2',
@@ -117,7 +115,6 @@ const optionsCreationLD = {
117
115
  services: [
118
116
  {
119
117
  apikey: 'ld-test',
120
- cbroker: 'http://orion:1026',
121
118
  entity_type: 'Device',
122
119
  ngsiVersion: 'ld',
123
120
  resource: '/iot/ld',
@@ -0,0 +1,22 @@
1
+ {
2
+ "actionType":"update",
3
+ "entities":[
4
+ {
5
+ "id":"lightPseudo:id",
6
+ "type":"Light",
7
+ "pressure":{
8
+ "type":"Number",
9
+ "value":60
10
+ }
11
+ },
12
+ {
13
+ "id":"Ligth:mymulti",
14
+ "type":"myType",
15
+ "pressure":{
16
+ "type":"Number",
17
+ "value":90
18
+ }
19
+
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "subscriptionId": "51c0ac9ed714fb3b37d7d5a8",
3
+ "data": [
4
+ {
5
+ "location": {
6
+ "type": "geo:point",
7
+ "value": "12.4, -9.6",
8
+ "metadata": {
9
+ "qos": {
10
+ "type": "Number",
11
+ "value": 1
12
+ }
13
+ }
14
+ },
15
+ "type": "TheLightType",
16
+ "id": "TheFirstLight"
17
+ }
18
+ ]
19
+ }
@@ -437,16 +437,31 @@ describe('Java expression language (JEXL) based transformations plugin', functio
437
437
  const values = [
438
438
  {
439
439
  name: 'p',
440
- type: 'centigrades',
441
- value: '52'
440
+ type: 'Number',
441
+ value: 1040
442
442
  }
443
443
  ];
444
444
 
445
- it('should apply the expression before sending the values', function (done) {
445
+ beforeEach(function () {
446
+ nock.cleanAll();
447
+
448
+ contextBrokerMock = nock('http://192.168.1.1:1026')
449
+ .matchHeader('fiware-service', 'smartgondor')
450
+ .matchHeader('fiware-servicepath', 'gardens')
451
+ .patch(
452
+ '/v2/entities/light1/attrs',
453
+ utils.readExampleFile(
454
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json'
455
+ )
456
+ )
457
+ .query({ type: 'Light' })
458
+ .reply(204);
459
+ });
460
+
461
+ it('should ignore the expression and send the values', function (done) {
446
462
  iotAgentLib.update('light1', 'LightError', '', values, function (error) {
447
- should.exist(error);
448
- error.name.should.equal('INVALID_EXPRESSION');
449
- error.code.should.equal(400);
463
+ should.not.exist(error);
464
+ contextBrokerMock.done();
450
465
  done();
451
466
  });
452
467
  });
@@ -137,7 +137,7 @@ const groupCreation = {
137
137
  apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
138
138
  entity_type: 'TheLightType',
139
139
  trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
140
- cbHost: 'http://unexistentHost:1026',
140
+ cbHost: 'http://192.168.1.1:1026',
141
141
  commands: [],
142
142
  lazy: [],
143
143
  attributes: [
@@ -187,7 +187,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
187
187
  // This mock does not check the payload since the aim of the test is not to verify
188
188
  // device provisioning functionality. Appropriate verification is done in tests under
189
189
  // provisioning folder
190
- contextBrokerMock = nock('http://unexistenthost:1026')
190
+ contextBrokerMock = nock('http://192.168.1.1:1026')
191
191
  .matchHeader('fiware-service', 'testservice')
192
192
  .matchHeader('fiware-servicepath', '/testingPath')
193
193
  .post('/v2/entities?options=upsert')
@@ -217,7 +217,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
217
217
  // This mock does not check the payload since the aim of the test is not to verify
218
218
  // device provisioning functionality. Appropriate verification is done in tests under
219
219
  // provisioning folder
220
- contextBrokerMock = nock('http://unexistenthost:1026')
220
+ contextBrokerMock = nock('http://192.168.1.1:1026')
221
221
  .matchHeader('fiware-service', 'testservice')
222
222
  .matchHeader('fiware-servicepath', '/testingPath')
223
223
  .post('/v2/entities?options=upsert')
@@ -229,32 +229,34 @@ describe('NGSI-v2 - Device Service: utils', function () {
229
229
  });
230
230
 
231
231
  it('should register the device and return it', function (done) {
232
- iotAgentLib.retrieveDevice('UNEXISTENT_DEV', '801230BJKL23Y9090DSFL123HJK09H324HV8732', function (
233
- error,
234
- device
235
- ) {
236
- should.not.exist(error);
237
- should.exist(device);
232
+ iotAgentLib.retrieveDevice(
233
+ 'UNEXISTENT_DEV',
234
+ '801230BJKL23Y9090DSFL123HJK09H324HV8732',
235
+ function (error, device) {
236
+ should.not.exist(error);
237
+ should.exist(device);
238
238
 
239
- device.id.should.equal('UNEXISTENT_DEV');
240
- should.exist(device.protocol);
241
- device.protocol.should.equal('MQTT_UL');
242
- done();
243
- });
239
+ device.id.should.equal('UNEXISTENT_DEV');
240
+ should.exist(device.protocol);
241
+ device.protocol.should.equal('MQTT_UL');
242
+ done();
243
+ }
244
+ );
244
245
  });
245
246
  });
246
247
 
247
248
  describe('When an unexisting device tries to be retrieved for an unexisting APIKey', function () {
248
249
  it('should raise an error', function (done) {
249
- iotAgentLib.retrieveDevice('UNEXISTENT_DEV_AND_GROUP', 'H2332Y909DSF3H346yh20JK092', function (
250
- error,
251
- device
252
- ) {
253
- should.exist(error);
254
- error.name.should.equal('DEVICE_GROUP_NOT_FOUND');
255
- should.not.exist(device);
256
- done();
257
- });
250
+ iotAgentLib.retrieveDevice(
251
+ 'UNEXISTENT_DEV_AND_GROUP',
252
+ 'H2332Y909DSF3H346yh20JK092',
253
+ function (error, device) {
254
+ should.exist(error);
255
+ error.name.should.equal('DEVICE_GROUP_NOT_FOUND');
256
+ should.not.exist(device);
257
+ done();
258
+ }
259
+ );
258
260
  });
259
261
  });
260
262
  });
@@ -311,4 +311,110 @@ describe('NGSI-v2 - Command functionalities', function () {
311
311
  });
312
312
  });
313
313
  });
314
+
315
+ describe('When a command update with metadata arrives to the IoT Agent as Context Provider', function () {
316
+ const options = {
317
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
318
+ method: 'POST',
319
+ json: {
320
+ actionType: 'update',
321
+ entities: [
322
+ {
323
+ id: 'Robot:r2d2',
324
+ type: 'Robot',
325
+ position: {
326
+ type: 'Array',
327
+ value: '[28, -104, 23]',
328
+ metadata: {
329
+ qos: 1
330
+ }
331
+ }
332
+ }
333
+ ]
334
+ },
335
+ headers: {
336
+ 'fiware-service': 'smartgondor',
337
+ 'fiware-servicepath': 'gardens'
338
+ }
339
+ };
340
+
341
+ beforeEach(function (done) {
342
+ iotAgentLib.register(device3, function (error) {
343
+ done();
344
+ });
345
+ });
346
+
347
+ it('should call the client handler once including metadata', function (done) {
348
+ let handlerCalled = 0;
349
+
350
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
351
+ id.should.equal(device3.type + ':' + device3.id);
352
+ type.should.equal(device3.type);
353
+ attributes[0].name.should.equal('position');
354
+ attributes[0].value.should.equal('[28, -104, 23]');
355
+ attributes[0].metadata.qos.should.equal(1);
356
+ handlerCalled++;
357
+ callback(null, {
358
+ id,
359
+ type,
360
+ attributes: [
361
+ {
362
+ name: 'position',
363
+ type: 'Array',
364
+ value: '[28, -104, 23]'
365
+ }
366
+ ]
367
+ });
368
+ });
369
+
370
+ request(options, function (error, response, body) {
371
+ should.not.exist(error);
372
+ handlerCalled.should.equal(1);
373
+ done();
374
+ });
375
+ });
376
+ it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
377
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
378
+ callback(null, {
379
+ id,
380
+ type,
381
+ attributes: [
382
+ {
383
+ name: 'position',
384
+ type: 'Array',
385
+ value: '[28, -104, 23]'
386
+ }
387
+ ]
388
+ });
389
+ });
390
+
391
+ request(options, function (error, response, body) {
392
+ should.not.exist(error);
393
+ done();
394
+ });
395
+ });
396
+ it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
397
+ let serviceAndSubservice = false;
398
+
399
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
400
+ serviceAndSubservice = service === 'smartgondor' && subservice === 'gardens';
401
+ callback(null, {
402
+ id,
403
+ type,
404
+ attributes: [
405
+ {
406
+ name: 'position',
407
+ type: 'Array',
408
+ value: '[28, -104, 23]'
409
+ }
410
+ ]
411
+ });
412
+ });
413
+
414
+ request(options, function (error, response, body) {
415
+ serviceAndSubservice.should.equal(true);
416
+ done();
417
+ });
418
+ });
419
+ });
314
420
  });