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
@@ -387,8 +387,13 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation, payload) {
387
387
  ...jexlParser.extractContext(attsArray),
388
388
  ...extractContextFromPayload(payload)
389
389
  }; //maybe overlapping between this two objects.
390
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
391
- explicitAttrsList = explicitAttrsList.concat(res);
390
+ let res = null;
391
+ try {
392
+ res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
393
+ } catch (e) {
394
+ // nothing to do: exception is already logged at info level
395
+ }
396
+ explicitAttrsList = res ? explicitAttrsList.concat(res) : explicitAttrsList;
392
397
  });
393
398
  }
394
399
  }
@@ -438,8 +443,13 @@ function removeHiddenAttrs(result, typeInformation, payload) {
438
443
  ...jexlParser.extractContext(attsArray),
439
444
  ...extractContextFromPayload(payload)
440
445
  }; //maybe overlapping between this two objects. Measures not in active attrs.
441
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
442
- explicitAttrsList = res;
446
+ let res = null;
447
+ try {
448
+ res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
449
+ } catch (e) {
450
+ // nothing to do: exception is already logged at info level
451
+ }
452
+ explicitAttrsList = res ? res : [];
443
453
  }
444
454
  if (explicitAttrsList && explicitAttrsList.length >= 0) {
445
455
  const hidden = _.difference(_.keys(result), explicitAttrsList);
@@ -46,6 +46,21 @@ const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entit
46
46
  const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
47
47
  const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
48
48
 
49
+ /**
50
+ * Extract metadata attributes from input.
51
+ *
52
+ * @param {Object} obj input object
53
+ */
54
+ function getMetaData(obj) {
55
+ const excludedKeys = ['datasetId', 'value', 'type'];
56
+ const metaData = {};
57
+ _.keys(obj).forEach((key) => {
58
+ if (!excludedKeys.includes(key)) {
59
+ metaData[key] = obj[key];
60
+ }
61
+ });
62
+ return !_.isEmpty(metaData) ? metaData : {};
63
+ }
49
64
  /**
50
65
  * Generate all the update actions corresponding to a update context request using Ngsi2.
51
66
  * Update actions include updates in attributes and execution of commands.
@@ -59,6 +74,7 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
59
74
 
60
75
  const attribute = req.params.attr;
61
76
  const value = req.body.value;
77
+ const datasetId = req.body.datasetId;
62
78
  const incomingAttrs = !req.params.attr ? _.keys(req.body) : [];
63
79
 
64
80
  if (contextElement.id && contextElement.type) {
@@ -75,19 +91,49 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
75
91
 
76
92
  if (device.commands) {
77
93
  for (const j in device.commands) {
78
- if (attribute === device.commands[j].name) {
79
- commands.push({
80
- type: device.commands[j].type,
81
- value,
82
- name: attribute
83
- });
94
+ const name = device.commands[j].name;
95
+ if (attribute === name) {
96
+ if (_.isArray(req.body)) {
97
+ req.body[name].forEach((obj) => {
98
+ commands.push({
99
+ type: device.commands[j].type,
100
+ value: obj.value,
101
+ name: name,
102
+ datasetId: obj.datasetId,
103
+ metadata: getMetaData(obj)
104
+ });
105
+ });
106
+ } else {
107
+ commands.push({
108
+ type: device.commands[j].type,
109
+ value,
110
+ name,
111
+ datasetId,
112
+ metadata: getMetaData(req.body)
113
+ });
114
+ }
84
115
  found = true;
85
- } else if (incomingAttrs.includes(device.commands[j].name)) {
86
- commands.push({
87
- type: device.commands[j].type,
88
- value: req.body[device.commands[j].name].value,
89
- name: device.commands[j].name
90
- });
116
+ } else if (incomingAttrs.includes(name)) {
117
+ if (_.isArray(req.body[name])) {
118
+ req.body[name].forEach((obj) => {
119
+ commands.push({
120
+ type: device.commands[j].type,
121
+ value: obj.value,
122
+ name: name,
123
+ datasetId: obj.datasetId,
124
+ metadata: getMetaData(obj)
125
+ });
126
+ });
127
+ } else {
128
+ const obj = req.body[name];
129
+ commands.push({
130
+ type: device.commands[j].type,
131
+ value: obj.value,
132
+ name: name,
133
+ datasetId: obj.datasetId,
134
+ metadata: getMetaData(obj)
135
+ });
136
+ }
91
137
  found = true;
92
138
  }
93
139
  }
@@ -290,7 +336,7 @@ function handleQueryNgsiLD(req, res, next) {
290
336
 
291
337
  if (device.staticAttributes) {
292
338
  let selectedAttributes = [];
293
- if (attributes === undefined || attributes.length === 0) {
339
+ if (attributes === null || attributes === undefined || attributes.length === 0) {
294
340
  selectedAttributes = device.staticAttributes;
295
341
  } else {
296
342
  selectedAttributes = device.staticAttributes.filter(inAttributes);
@@ -316,8 +362,8 @@ function handleQueryNgsiLD(req, res, next) {
316
362
  const results = device.lazy.map(getName);
317
363
  callback(null, results);
318
364
  } else {
319
- logger.debug(context, "Couldn't find any attributes. Handling with null reference");
320
- callback(null, null);
365
+ logger.debug(context, "Couldn't find any attributes. Handling with empty reference");
366
+ callback(null, []);
321
367
  }
322
368
  }
323
369
 
@@ -472,11 +518,25 @@ function handleNotificationNgsiLD(req, res, next) {
472
518
  /* eslint-disable-next-line no-prototype-builtins */
473
519
  if (dataElement.hasOwnProperty(key)) {
474
520
  if (key !== 'id' && key !== 'type') {
475
- const att = {};
476
- att.type = dataElement[key].type;
477
- att.value = dataElement[key].value;
478
- att.name = key;
479
- atts.push(att);
521
+ if (_.isArray(dataElement[key])) {
522
+ dataElement[key].forEach((obj) => {
523
+ atts.push({
524
+ type: obj.type,
525
+ value: obj.value,
526
+ name: key,
527
+ datasetId: obj.datasetId,
528
+ metadata: getMetaData(obj)
529
+ });
530
+ });
531
+ } else {
532
+ atts.push({
533
+ type: dataElement[key].type,
534
+ value: dataElement[key].value,
535
+ name: key,
536
+ datasetId: dataElement[key].datasetId,
537
+ metadata: getMetaData(dataElement[key])
538
+ });
539
+ }
480
540
  }
481
541
  }
482
542
  }
@@ -563,6 +623,21 @@ function updateErrorHandlingNgsiLD(error, req, res, next) {
563
623
  });
564
624
  }
565
625
 
626
+ /**
627
+ * Load unsupported NGSI-LD entity routes and return proper NGSI-LD not supported responses
628
+ *
629
+ * @param {Object} router Express request router object.
630
+ */
631
+ function loadUnsupportedEndpointsNGSILD(router) {
632
+ const unsupportedEndpoint = function (req, res) {
633
+ return res.status(501).send(new errors.MethodNotSupported(req.method, req.path));
634
+ };
635
+ router.get('/ngsi-ld/v1/entities', unsupportedEndpoint);
636
+ router.post('/ngsi-ld/v1/entities', unsupportedEndpoint);
637
+ router.delete('/ngsi-ld/v1/entities/:entity', unsupportedEndpoint);
638
+ router.delete('/ngsi-ld/v1/entities/:entity/attrs/:attr', unsupportedEndpoint);
639
+ }
640
+
566
641
  /**
567
642
  * Load the routes related to context dispatching (NGSI10 calls).
568
643
  *
@@ -601,6 +676,7 @@ function loadContextRoutesNGSILD(router) {
601
676
  handleNotificationNgsiLD,
602
677
  queryErrorHandlingNgsiLD
603
678
  ]);
679
+ loadUnsupportedEndpointsNGSILD(router);
604
680
  }
605
681
 
606
682
  exports.loadContextRoutes = loadContextRoutesNGSILD;
@@ -286,6 +286,7 @@ function handleNotificationNgsi2(req, res, next) {
286
286
  const att = {};
287
287
  att.type = dataElement[key].type;
288
288
  att.value = dataElement[key].value;
289
+ att.metadata = dataElement[key].metadata || {};
289
290
  att.name = key;
290
291
  atts.push(att);
291
292
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "iotagent-node-lib",
3
3
  "license": "AGPL-3.0-only",
4
4
  "description": "IoT Agent library to interface with NGSI Context Broker",
5
- "version": "2.23.0",
5
+ "version": "2.24.0",
6
6
  "homepage": "https://github.com/telefonicaid/iotagent-node-lib",
7
7
  "keywords": [
8
8
  "fiware",
@@ -194,4 +194,55 @@ describe('NGSI-v2 - In memory device registry', function () {
194
194
  });
195
195
  });
196
196
  });
197
+
198
+ describe('When a the registry is queried for device in a particular name and type', function (){
199
+ beforeEach(function (done) {
200
+ contextBrokerMock = nock('http://192.168.1.1:1026')
201
+ .post('/v2/entities?options=upsert')
202
+ .times(10)
203
+ .reply(204);
204
+
205
+ const devices = [];
206
+
207
+ for (let i = 0; i < 10; i++)
208
+ {
209
+ devices.push(
210
+ {
211
+ id: 'id' + i,
212
+ name : 'name' + i,
213
+ type: 'Light' + i,
214
+ internalId: 'internal' + i,
215
+ service: 'smartgondor',
216
+ subservice: 'gardens',
217
+ active: [ {
218
+ id: 'attrId',
219
+ type: 'attrType' + i,
220
+ value: i
221
+ }]
222
+ });
223
+ }
224
+
225
+ async.map(devices, iotAgentLib.register, function (error, results) {
226
+ done();
227
+ });
228
+ });
229
+
230
+ afterEach(function (done) {
231
+ iotAgentLib.clearRegistry(done);
232
+ });
233
+ it('should return the name and type of device', function (done)
234
+ {
235
+ iotAgentLib.getDeviceByNameAndType('name5', 'Light5','smartgondor', 'gardens' ,function(error, device)
236
+ {
237
+ should.not.exist(error);
238
+ should.exist(device);
239
+ should.exist(device.name);
240
+ should.exist(device.type);
241
+ device.name.should.equal('name5');
242
+ device.type.should.equal('Light5');
243
+ Object.keys(device).length.should.equal(11);
244
+ done();
245
+ });
246
+ });
247
+ });
197
248
  });
@@ -448,4 +448,28 @@ describe('MongoDB Group Registry test', function () {
448
448
  });
449
449
  });
450
450
  });
451
+
452
+ describe('When the device info request with name and type', function () {
453
+ beforeEach(function (done) {
454
+ async.series([async.apply(request, optionsCreation)], done);
455
+ });
456
+
457
+ afterEach(function (done) {
458
+ iotAgentLib.clearRegistry(done);
459
+ });
460
+
461
+ it('should return the name and type of device', function (done) {
462
+ request(optionsGet, function (error, response, body) {
463
+ should.exist(body);
464
+ should.exist(body.count);
465
+ body.count.should.equal(1);
466
+ should.exist(body.services);
467
+ should.exist(body.services.length);
468
+ body.services.length.should.equal(1);
469
+ should.exist(body.services[0].entity_type);
470
+ body.services[0].entity_type.should.equal('Light');
471
+ done();
472
+ });
473
+ });
474
+ });
451
475
  });
@@ -507,4 +507,53 @@ describe('NGSI-v2 - MongoDB Device Registry', function () {
507
507
  });
508
508
  });
509
509
  });
510
+
511
+ describe('When the device is queried with the name and type', function () {
512
+ beforeEach(function (done) {
513
+ contextBrokerMock = nock('http://192.168.1.1:1026')
514
+ .post('/v2/entities?options=upsert')
515
+ .times(10)
516
+ .matchHeader('fiware-service', 'smartgondor')
517
+ .matchHeader('fiware-servicepath', 'gardens')
518
+ .reply(204);
519
+
520
+ const devices = [];
521
+
522
+ for (let i = 0; i < 10; i++) {
523
+ devices.push({
524
+ id: 'id' + i,
525
+ type: 'Light' + i,
526
+ internalId: 'internal' + i,
527
+ service: 'smartgondor',
528
+ subservice: 'gardens',
529
+ active: [
530
+ {
531
+ id: 'attrId',
532
+ type: 'attrType' + i,
533
+ value: i
534
+ }
535
+ ]
536
+ });
537
+ }
538
+ iotAgentLib.activate(iotAgentConfig, function (error) {
539
+ async.map(devices, iotAgentLib.register, function (error, results) {
540
+ done();
541
+ });
542
+ });
543
+ });
544
+ afterEach(function (done) {
545
+ iotAgentLib.clearRegistry(done);
546
+ });
547
+ it('should return the device with name and type', function (done) {
548
+ iotAgentLib.getDeviceByNameAndType('Light4:id4', 'Light4', 'smartgondor', 'gardens', function ( error, device ) {
549
+ should.not.exist(error);
550
+ should.exist(device);
551
+ should.exist(device.name);
552
+ should.exist(device.type);
553
+ device.name.should.equal('Light4:id4');
554
+ device.type.should.equal('Light4');
555
+ done();
556
+ });
557
+ });
558
+ });
510
559
  });
@@ -0,0 +1,21 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "id": "TheFirstLight",
5
+ "location": [
6
+ {
7
+ "type": "GeoProperty",
8
+ "value": "12.4, -9.6",
9
+ "datasetId": "urn:ngsi-ld:Property:do-this"
10
+ },
11
+ {
12
+ "type": "GeoProperty",
13
+ "value": "6, 10",
14
+ "datasetId": "urn:ngsi-ld:Property:then-do-this"
15
+ }
16
+ ],
17
+ "type": "TheLightType"
18
+ }
19
+ ],
20
+ "subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
21
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "id": "TheFirstLight",
5
+ "location": {
6
+ "type": "GeoProperty",
7
+ "value": "12.4, -9.6",
8
+ "qos": {
9
+ "type": "Property",
10
+ "value": 1
11
+ }
12
+ },
13
+ "type": "TheLightType"
14
+ }
15
+ ],
16
+ "subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
17
+ }