iotagent-node-lib 2.23.0 → 2.24.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 (36) hide show
  1. package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  2. package/.nyc_output/processinfo/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  3. package/.nyc_output/processinfo/index.json +1 -1
  4. package/CHANGES_NEXT_RELEASE +0 -2
  5. package/doc/advanced-topics.md +55 -2
  6. package/doc/architecture.md +52 -5
  7. package/doc/expressionLanguage.md +2 -1
  8. package/doc/usermanual.md +18 -16
  9. package/lib/errors.js +9 -1
  10. package/lib/plugins/bidirectionalData.js +104 -6
  11. package/lib/plugins/expressionPlugin.js +10 -0
  12. package/lib/plugins/pluginUtils.js +2 -1
  13. package/lib/request-shim.js +2 -1
  14. package/lib/services/devices/deviceService.js +34 -20
  15. package/lib/services/ngsi/entities-NGSI-v2.js +14 -4
  16. package/lib/services/northBound/contextServer-NGSI-LD.js +96 -20
  17. package/lib/services/northBound/contextServer-NGSI-v2.js +1 -0
  18. package/package.json +1 -1
  19. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +51 -0
  20. package/test/unit/mongodb/mongodb-group-registry-test.js +24 -0
  21. package/test/unit/mongodb/mongodb-registry-test.js +49 -0
  22. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
  23. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
  24. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +656 -2
  25. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
  26. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
  27. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
  28. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
  29. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
  30. package/.nyc_output/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  31. package/.nyc_output/processinfo/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  32. package/config +0 -0
  33. package/docker/Mosquitto/Dockerfile.debian +0 -38
  34. package/docker/Mosquitto/Dockerfile.debian~ +0 -23
  35. package/lib/plugins/multiEntity.js_avg2 +0 -343
  36. 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',
@@ -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
+ }
@@ -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
  });
@@ -237,6 +237,120 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
237
237
  });
238
238
  });
239
239
 
240
+ it('should return the transformed values', function (done) {
241
+ let transformedHandler = false;
242
+
243
+ function mockedHandler(device, values, callback) {
244
+
245
+ let latitudeFound = false;
246
+ let longitudeFound = false;
247
+
248
+ for (let i = 0; i < values.length; i++) {
249
+ if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
250
+ latitudeFound = true;
251
+ }
252
+
253
+ if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
254
+ longitudeFound = true;
255
+ }
256
+ }
257
+
258
+ transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
259
+ callback();
260
+ }
261
+
262
+ iotAgentLib.setNotificationHandler(mockedHandler);
263
+
264
+ request(options, function (error, response, body) {
265
+ request(notificationOptions, function (error, response, body) {
266
+ contextBrokerMock.done();
267
+ transformedHandler.should.equal(true);
268
+ done();
269
+ });
270
+ });
271
+ });
272
+ });
273
+
274
+
275
+ describe('When a notification with metadata arrives for a bidirectional attribute', function () {
276
+ const notificationOptions = {
277
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
278
+ method: 'POST',
279
+ json: utils.readExampleFile(
280
+ './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json'
281
+ ),
282
+ headers: {
283
+ 'fiware-service': 'smartgondor',
284
+ 'fiware-servicepath': '/gardens'
285
+ }
286
+ };
287
+ let executedHandler = false;
288
+
289
+ beforeEach(function () {
290
+ contextBrokerMock
291
+ .matchHeader('fiware-service', 'smartgondor')
292
+ .matchHeader('fiware-servicepath', '/gardens')
293
+ .post(
294
+ '/v2/subscriptions',
295
+ utils.readExampleFile(
296
+ './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
297
+ )
298
+ )
299
+ .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
300
+
301
+ contextBrokerMock
302
+ .matchHeader('fiware-service', 'smartgondor')
303
+ .matchHeader('fiware-servicepath', '/gardens')
304
+ .post(
305
+ '/v2/entities?options=upsert',
306
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
307
+ )
308
+ .reply(204);
309
+ });
310
+
311
+ afterEach(function () {
312
+ iotAgentLib.setNotificationHandler();
313
+ });
314
+
315
+ it('should execute the original handler', function (done) {
316
+ function mockedHandler(device, notification, callback) {
317
+
318
+ notification[0].name.should.equal('location');
319
+ notification[0].value.should.equal('12.4, -9.6');
320
+ notification[0].metadata.qos.value.should.equal(1);
321
+
322
+ executedHandler = true;
323
+ callback();
324
+ }
325
+
326
+ iotAgentLib.setNotificationHandler(mockedHandler);
327
+
328
+ request(options, function (error, response, body) {
329
+ request(notificationOptions, function (error, response, body) {
330
+ executedHandler.should.equal(true);
331
+ contextBrokerMock.done();
332
+ done();
333
+ });
334
+ });
335
+ });
336
+
337
+ it('should return a 200 OK', function (done) {
338
+ function mockedHandler(device, notification, callback) {
339
+ executedHandler = true;
340
+ callback();
341
+ }
342
+
343
+ iotAgentLib.setNotificationHandler(mockedHandler);
344
+
345
+ request(options, function (error, response, body) {
346
+ request(notificationOptions, function (error, response, body) {
347
+ response.statusCode.should.equal(200);
348
+ contextBrokerMock.done();
349
+ done();
350
+ });
351
+ });
352
+ });
353
+
240
354
  it('should return the transformed values', function (done) {
241
355
  let transformedHandler = false;
242
356
 
@@ -270,6 +384,7 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
270
384
  });
271
385
  });
272
386
 
387
+
273
388
  describe('When a new Group provisioning request arrives with bidirectional attributes', function () {
274
389
  const provisionGroup = {
275
390
  url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',