iotagent-node-lib 2.24.0 → 2.26.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 (58) hide show
  1. package/.github/workflows/ci.yml +6 -7
  2. package/config.js +6 -1
  3. package/doc/advanced-topics.md +23 -1
  4. package/doc/apiary/iotagent.apib +5 -5
  5. package/doc/deprecated.md +13 -7
  6. package/doc/expressionLanguage.md +44 -34
  7. package/doc/getting-started.md +1 -1
  8. package/doc/howto.md +8 -0
  9. package/doc/installationguide.md +18 -0
  10. package/doc/usermanual.md +77 -0
  11. package/examples/TTOpen-service.json +1 -1
  12. package/lib/commonConfig.js +15 -1
  13. package/lib/constants.js +1 -0
  14. package/lib/fiware-iotagent-lib.js +3 -1
  15. package/lib/jexlTranformsMap.js +12 -1
  16. package/lib/plugins/jexlParser.js +2 -2
  17. package/lib/services/devices/deviceService.js +20 -1
  18. package/lib/services/devices/devices-NGSI-v2.js +3 -1
  19. package/lib/services/devices/registrationUtils.js +21 -1
  20. package/lib/services/northBound/contextServer-NGSI-LD.js +221 -38
  21. package/lib/services/northBound/contextServer.js +14 -1
  22. package/lib/services/northBound/northboundServer.js +1 -0
  23. package/lib/services/northBound/restUtils.js +3 -5
  24. package/lib/templates/deviceGroup.json +1 -1
  25. package/package.json +2 -2
  26. package/test/unit/examples/deviceProvisioningRequests/provisionNewDeviceEmpty.json +43 -0
  27. package/test/unit/examples/mongoCollections/configurations.json +3 -3
  28. package/test/unit/expressions/jexlExpression-test.js +29 -8
  29. package/test/unit/general/deviceService-test.js +31 -29
  30. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +26 -25
  31. package/test/unit/mongodb/mongodb-group-registry-test.js +3 -3
  32. package/test/unit/mongodb/mongodb-registry-test.js +30 -21
  33. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent1.json +24 -14
  34. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent2.json +25 -15
  35. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent4.json +15 -5
  36. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommands.json +9 -1
  37. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommandsAndLazy.json +32 -0
  38. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice.json +12 -1
  39. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDevice2.json +9 -1
  40. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json +12 -1
  41. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json +12 -1
  42. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json +9 -1
  43. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateCommands1.json +10 -2
  44. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent1.json +11 -1
  45. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/updateIoTAgent2.json +12 -1
  46. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin30.json +11 -0
  47. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +18 -4
  48. package/test/unit/ngsi-ld/general/deviceService-test.js +31 -29
  49. package/test/unit/ngsi-ld/general/startup-test.js +17 -2
  50. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +1 -12
  51. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +249 -0
  52. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +171 -0
  53. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +0 -3
  54. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json +22 -0
  55. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +21 -6
  56. package/test/unit/ngsiv2/general/deviceService-test.js +25 -23
  57. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +58 -0
  58. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +25 -6
@@ -310,9 +310,11 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
310
310
  return sendUnregistrationsNgsiLD(deviceData, callback);
311
311
  }
312
312
 
313
+ const operations = [];
313
314
  const properties = [];
314
315
  const lazy = deviceData.lazy || [];
315
316
  const commands = deviceData.commands || [];
317
+ const supportMerge = config.getConfig().server.ldSupport.merge;
316
318
 
317
319
  lazy.forEach((element) => {
318
320
  properties.push(element.name);
@@ -321,6 +323,16 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
321
323
  properties.push(element.name);
322
324
  });
323
325
 
326
+ if (lazy.length > 0){
327
+ operations.push('retrieveOps');
328
+ }
329
+ if (commands.length > 0){
330
+ operations.push('updateOps');
331
+ }
332
+ if (supportMerge){
333
+ operations.push('mergeEntity');
334
+ }
335
+
324
336
  if (properties.length === 0) {
325
337
  logger.debug(context, 'Registration with Context Provider is not needed. Device without lazy atts or commands');
326
338
  return callback(null, deviceData);
@@ -348,10 +360,18 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
348
360
  information: [
349
361
  {
350
362
  entities: [{ type: deviceData.type, id }],
351
- properties
363
+ propertyNames: properties
352
364
  }
353
365
  ],
366
+ mode: 'exclusive',
367
+ operations,
354
368
  endpoint: config.getConfig().providerUrl,
369
+ contextSourceInfo: [
370
+ {
371
+ 'key': 'jsonldContext',
372
+ 'value': config.getConfig().contextBroker.jsonLdContext
373
+ }
374
+ ],
355
375
  '@context': config.getConfig().contextBroker.jsonLdContext
356
376
  },
357
377
  headers: {
@@ -31,6 +31,7 @@ const async = require('async');
31
31
  const apply = async.apply;
32
32
  const logger = require('logops');
33
33
  const errors = require('../../errors');
34
+ const constants = require('../../constants');
34
35
  const deviceService = require('../devices/deviceService');
35
36
  const middlewares = require('../common/genericMiddleware');
36
37
  const _ = require('underscore');
@@ -41,11 +42,98 @@ const updateContextTemplateNgsiLD = require('../../templates/updateContextNgsiLD
41
42
  const notificationTemplateNgsiLD = require('../../templates/notificationTemplateNgsiLD.json');
42
43
  const contextServerUtils = require('./contextServerUtils');
43
44
  const ngsiLD = require('../ngsi/entities-NGSI-LD');
45
+ const config = require('../../commonConfig');
44
46
 
45
47
  const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
46
48
  const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
47
49
  const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
48
50
 
51
+
52
+ /**
53
+ * Replacement of NGSI-LD Null placeholders with real null values
54
+ *
55
+ */
56
+ function replaceNGSILDNull(payload){
57
+ Object.keys(payload).forEach((key) =>{
58
+ const value = payload[key];
59
+ if ( value === constants.NGSI_LD_NULL){
60
+ payload[key] = null;
61
+ } else if (typeof value === 'object' &&
62
+ !Array.isArray(value) &&
63
+ value !== null){
64
+ payload[key] = replaceNGSILDNull(payload[key]);
65
+ }
66
+ })
67
+ return payload;
68
+ }
69
+
70
+ /**
71
+ * Check to see if the payload or its subattributes contain null values
72
+ *
73
+ */
74
+ function containsNulls(payload, result){
75
+ Object.keys(payload).forEach((key) =>{
76
+ const value = payload[key];
77
+ if ( value === null){
78
+ result.nulls = true;
79
+ } else if (typeof value === 'object' &&
80
+ !Array.isArray(value) &&
81
+ value !== null){
82
+ containsNulls(payload[key], result);
83
+ }
84
+ })
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * An Express middleware for preprocessing NGSI-LD payloads. Converts NGSI-LD Nulls
90
+ * to real nulls and checks for the presence of null and datasetId
91
+ *
92
+ */
93
+ function preprocessNGSILD(req, res, next){
94
+ res.locals.hasDatasetId = false;
95
+ const payload = req.body
96
+ if (payload && typeof payload === 'object'){
97
+ Object.keys(payload).forEach((key) =>{
98
+ if (_.isArray(payload[key])){
99
+ payload[key].forEach((obj) => {
100
+ if (obj.datasetId){
101
+ res.locals.hasDatasetId = true;
102
+ }
103
+ });
104
+ } else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none'){
105
+ res.locals.hasDatasetId = true;
106
+ }
107
+ });
108
+ req.body = replaceNGSILDNull(payload);
109
+ const result = { nulls: false }
110
+ containsNulls(payload, result);
111
+ res.locals.hasNulls = result.nulls;
112
+ }
113
+ next();
114
+ }
115
+
116
+ /**
117
+ * A configurable Middleware that makes additional NGSI-LD checks within the payload.
118
+
119
+ *
120
+ * @param {Boolean} supportNull Whether to support NGSI-LD nulls in the payload
121
+ * @param {Boolean} supportDatasetId Whether to support multiattributes in the payload.
122
+ * @return {Object} Express middleware used in request validation.
123
+ */
124
+ function validateNGSILD(supportNull, supportDatasetId) {
125
+ return function validate(req, res, next) {
126
+ if (!supportNull && res.locals.hasNulls) {
127
+ next(new errors.BadRequest('NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'));
128
+ } else if (!supportDatasetId && res.locals.hasDatasetId) {
129
+ next(new errors.BadRequest('datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'));
130
+ } else {
131
+ next();
132
+ }
133
+ };
134
+ }
135
+
136
+
49
137
  /**
50
138
  * Extract metadata attributes from input.
51
139
  *
@@ -318,6 +406,104 @@ function defaultQueryHandlerNgsiLD(id, type, service, subservice, attributes, ca
318
406
  });
319
407
  }
320
408
 
409
+ /**
410
+ * Generate a merge-patch action corresponding to the request using NGSI-LD.
411
+ * Merge-patch is an NGSI-LD specific action.
412
+ *
413
+ * @param {Object} req Update request to generate Actions from
414
+ */
415
+ function generateMergePatchActionNgsiLD(req, callback) {
416
+
417
+ const entityId = req.params.entity;
418
+
419
+
420
+ function addAttributes(deviceData, body, attributes){
421
+ const keys = Object.keys(body);
422
+
423
+ for (const j in deviceData) {
424
+ if (keys.includes(deviceData[j].name)) {
425
+ const obj = body[deviceData[j].name]
426
+ if ( obj === null) {
427
+ attributes.push({
428
+ type: deviceData[j].type,
429
+ value: null,
430
+ name: deviceData[j].name
431
+ });
432
+ } else {
433
+ attributes.push({
434
+ type: deviceData[j].type,
435
+ value: obj.value,
436
+ name: deviceData[j].name
437
+ });
438
+ }
439
+ }
440
+ }
441
+ return attributes;
442
+ }
443
+
444
+
445
+ deviceService.getDeviceByName(
446
+ entityId,
447
+ contextServerUtils.getLDTenant(req),
448
+ contextServerUtils.getLDPath(req),
449
+ function (error, deviceObj) {
450
+ if (error) {
451
+ callback(error);
452
+ } else {
453
+ const attributes = [];
454
+ addAttributes(deviceObj.commands, req.body, attributes)
455
+ addAttributes(deviceObj.lazy, req.body, attributes)
456
+ const executeMergePatchHandler = apply(
457
+ contextServerUtils.mergePatchHandler,
458
+ entityId,
459
+ deviceObj.type,
460
+ contextServerUtils.getLDTenant(req),
461
+ contextServerUtils.getLDPath(req),
462
+ attributes
463
+ );
464
+ async.waterfall(
465
+ [executeMergePatchHandler],
466
+ callback()
467
+ );
468
+ }
469
+ }
470
+ );
471
+ }
472
+
473
+ /**
474
+ * Express middleware to manage incoming merge-patch requests using NGSI-LD.
475
+ *
476
+ * @param {Object} req Request that was handled in first place.
477
+ * @param {Object} res Response that will be sent.
478
+ */
479
+ function handleMergePatchNgsiLD(req, res, next) {
480
+
481
+ function handleMergePatchRequest(error, result) {
482
+ if (error) {
483
+ logger.debug(context, 'There was an error handling the merge-patch: %s.', error);
484
+ next(error);
485
+ } else {
486
+ logger.debug(context, 'Merge-patch from [%s] handled successfully.', req.get('host'));
487
+ res.status(200).json(result);
488
+ }
489
+ }
490
+
491
+ logger.debug(context, 'Handling merge-patch from [%s]', req.get('host'));
492
+ if ((req.is('json') || req.is('application/ld+json')) === false) {
493
+ return handleMergePatchRequest(new errors.UnsupportedContentType(req.header('content-type')));
494
+ }
495
+
496
+ if (req.body) {
497
+ logger.debug(context, JSON.stringify(req.body, null, 4));
498
+ }
499
+
500
+ if (contextServerUtils.mergePatchHandler){
501
+ generateMergePatchActionNgsiLD(req, handleMergePatchRequest);
502
+ } else {
503
+ return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path))
504
+ }
505
+ }
506
+
321
507
  /**
322
508
  * Express middleware to manage incoming query context requests using NGSI-LD.
323
509
  *
@@ -489,19 +675,21 @@ function handleQueryNgsiLD(req, res, next) {
489
675
  * @param {Object} req Request that was handled in first place.
490
676
  * @param {Object} res Response that will be sent.
491
677
  */
492
- function queryErrorHandlingNgsiLD(error, req, res, next) {
493
- let code = 500;
678
+ function ErrorHandlingNgsiLD(action) {
679
+ return function errorHandle(error, req, res, next) {
680
+ let code = 500;
494
681
 
495
- logger.debug(context, 'Query NGSI-LD error [%s] handling request: %s', error.name, error.message);
682
+ logger.debug(context, action + ' NGSI-LD error [%s] handling request: %s', error.name, error.message);
496
683
 
497
- if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
498
- code = error.code;
499
- }
684
+ if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
685
+ code = error.code;
686
+ }
500
687
 
501
- res.status(code).json({
502
- error: error.name,
503
- description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
504
- });
688
+ res.status(code).json({
689
+ error: error.name,
690
+ description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
691
+ });
692
+ }
505
693
  }
506
694
 
507
695
  /**
@@ -601,28 +789,6 @@ function handleNotificationNgsiLD(req, res, next) {
601
789
  }
602
790
  }
603
791
 
604
- /**
605
- * Error handler for NGSI-LD update requests.
606
- *
607
- * @param {Object} error Incoming error
608
- * @param {Object} req Request that was handled in first place.
609
- * @param {Object} res Response that will be sent.
610
- */
611
- function updateErrorHandlingNgsiLD(error, req, res, next) {
612
- let code = 500;
613
-
614
- logger.debug(context, 'Update NGSI-LD error [%s] handing request: %s', error.name, error.message);
615
-
616
- if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
617
- code = error.code;
618
- }
619
-
620
- res.status(code).json({
621
- error: error.name,
622
- description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
623
- });
624
- }
625
-
626
792
  /**
627
793
  * Load unsupported NGSI-LD entity routes and return proper NGSI-LD not supported responses
628
794
  *
@@ -645,36 +811,53 @@ function loadUnsupportedEndpointsNGSILD(router) {
645
811
  */
646
812
  function loadContextRoutesNGSILD(router) {
647
813
  // In a more evolved implementation, more endpoints could be added to queryPathsNgsi2
648
- // according to http://fiware.github.io/specifications/ngsiv2/stable.
649
-
814
+ // according to https://www.etsi.org/standards-search#page=1&search=GS%20CIM%20009
815
+
816
+ const support = config.getConfig().server.ldSupport;
650
817
  let i;
651
818
 
652
819
  logger.info(context, 'Loading NGSI-LD Context server routes');
820
+ // Update Patch endpoints - this endpoint may accept NGSI-LD Null
653
821
  for (i = 0; i < updatePaths.length; i++) {
654
822
  router.patch(updatePaths[i], [
655
823
  middlewares.ensureType,
656
824
  middlewares.validateJson(updateContextTemplateNgsiLD),
825
+ preprocessNGSILD,
826
+ validateNGSILD(support.null, support.datasetId),
657
827
  handleUpdateNgsiLD,
658
- updateErrorHandlingNgsiLD
828
+ ErrorHandlingNgsiLD('Partial Update')
659
829
  ]);
660
830
  }
831
+ // Merge Patch endpoints - this endpoint may accept NGSI-LD Null
832
+ router.patch('/ngsi-ld/v1/entities/:entity', [
833
+ preprocessNGSILD,
834
+ validateNGSILD(support.null, support.datasetId),
835
+ handleMergePatchNgsiLD,
836
+ ErrorHandlingNgsiLD('Merge-Patch')
837
+ ]);
661
838
 
839
+ // Overwrite/PUT endpoints - this endpoint does not accept NGSI-LD Null
662
840
  for (i = 0; i < overwritePaths.length; i++) {
663
841
  router.put(overwritePaths[i], [
664
842
  middlewares.ensureType,
665
843
  middlewares.validateJson(updateContextTemplateNgsiLD),
844
+ preprocessNGSILD,
845
+ validateNGSILD(false, support.datasetId),
666
846
  handleUpdateNgsiLD,
667
- updateErrorHandlingNgsiLD
847
+ ErrorHandlingNgsiLD('Overwrite')
668
848
  ]);
669
849
  }
850
+ // Query/GET endpoints - no payload to check.
670
851
  for (i = 0; i < queryPaths.length; i++) {
671
- router.get(queryPaths[i], [handleQueryNgsiLD, queryErrorHandlingNgsiLD]);
852
+ router.get(queryPaths[i], [handleQueryNgsiLD, ErrorHandlingNgsiLD('Query')]);
672
853
  }
673
854
  router.post('/notify', [
674
855
  middlewares.ensureType,
675
856
  middlewares.validateJson(notificationTemplateNgsiLD),
857
+ preprocessNGSILD,
858
+ validateNGSILD(false, support.datasetId),
676
859
  handleNotificationNgsiLD,
677
- queryErrorHandlingNgsiLD
860
+ ErrorHandlingNgsiLD('Notify')
678
861
  ]);
679
862
  loadUnsupportedEndpointsNGSILD(router);
680
863
  }
@@ -64,7 +64,7 @@ function setUpdateHandler(newHandler) {
64
64
  }
65
65
 
66
66
  /**
67
- * Sets the new user handler for commadn execution requests. This handler will be called whenever an update request
67
+ * Sets the new user handler for command execution requests. This handler will be called whenever an update request
68
68
  * arrives to a with the following parameters: (id, type, attributes, callback). The callback is in charge of updating
69
69
  * the corresponding values in the devices with the appropriate protocol.
70
70
  *
@@ -77,6 +77,18 @@ function setCommandHandler(newHandler) {
77
77
  contextServerUtils.commandHandler = newHandler;
78
78
  }
79
79
 
80
+ /**
81
+ * Sets the new user handler for NGSI-LD merge-patch requests. This handler will be called whenever an update request
82
+ * arrives to a with the following parameters: (id, type, attributes, callback). The callback is in charge of updating
83
+ * the corresponding values in the devices with the appropriate protocol.
84
+ *
85
+ *
86
+ * @param {Function} newHandler User handler for update requests
87
+ */
88
+ function setMergePatchHandler(newHandler) {
89
+ contextServerUtils.mergePatchHandler = newHandler;
90
+ }
91
+
80
92
  /**
81
93
  * Sets the new user handler for Entity query requests. This handler will be called whenever an update request arrives
82
94
  * with the following parameters: (id, type, attributes, callback). The handler must retrieve all the corresponding
@@ -140,6 +152,7 @@ function clear(callback) {
140
152
  exports.clear = clear;
141
153
  exports.loadContextRoutes = intoTrans(context, loadContextRoutes);
142
154
  exports.setUpdateHandler = intoTrans(context, setUpdateHandler);
155
+ exports.setMergePatchHandler = intoTrans(context, setMergePatchHandler);
143
156
  exports.setCommandHandler = intoTrans(context, setCommandHandler);
144
157
  exports.setNotificationHandler = intoTrans(context, setNotificationHandler);
145
158
  exports.addNotificationMiddleware = intoTrans(context, addNotificationMiddleware);
@@ -110,6 +110,7 @@ function clear(callback) {
110
110
  async.series([deviceProvisioning.clear, groupProvisioning.clear, contextServer.clear], callback);
111
111
  }
112
112
 
113
+ exports.setMergePatchHandler = intoTrans(context, contextServer.setMergePatchHandler);
113
114
  exports.setUpdateHandler = intoTrans(context, contextServer.setUpdateHandler);
114
115
  exports.setQueryHandler = intoTrans(context, contextServer.setQueryHandler);
115
116
  exports.setCommandHandler = intoTrans(context, contextServer.setCommandHandler);
@@ -53,11 +53,9 @@ function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
53
53
  for (const p in mandatoryAttributes) {
54
54
  let found = false;
55
55
 
56
- for (const i in body) {
57
- if (body.hasOwnProperty(i)) {
58
- if (i === mandatoryAttributes[p]) {
59
- found = true;
60
- }
56
+ if (body.hasOwnProperty(mandatoryAttributes[p])) {
57
+ if (body[mandatoryAttributes[p]] !== '' && body[mandatoryAttributes[p]] !== null) {
58
+ found = true;
61
59
  }
62
60
  }
63
61
 
@@ -24,7 +24,7 @@
24
24
  "description": "token",
25
25
  "type": "string"
26
26
  },
27
- "cbroker": {
27
+ "cbHost": {
28
28
  "description": "uri for the context broker",
29
29
  "type": "string"
30
30
  },
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.24.0",
5
+ "version": "2.26.0",
6
6
  "homepage": "https://github.com/telefonicaid/iotagent-node-lib",
7
7
  "keywords": [
8
8
  "fiware",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "main": "lib/fiware-iotagent-lib",
25
25
  "engines": {
26
- "node": ">=12"
26
+ "node": ">=14"
27
27
  },
28
28
  "scripts": {
29
29
  "clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage",
@@ -0,0 +1,43 @@
1
+ {
2
+ "devices": [
3
+ {
4
+ "device_id": "",
5
+ "protocol": "GENERIC_PROTO",
6
+ "entity_name": "TheFirstLight",
7
+ "entity_type": "TheLightType",
8
+ "timezone": "America/Santiago",
9
+ "endpoint": "http://fakedEndpoint:1234",
10
+ "transport": "MQTT",
11
+ "attributes": [
12
+ {
13
+ "name": "attr_name",
14
+ "type": "string"
15
+ }
16
+ ],
17
+ "lazy": [
18
+ {
19
+ "name": "luminance",
20
+ "type": "lumens"
21
+ }
22
+ ],
23
+ "static_attributes": [
24
+ {
25
+ "name": "hardcodedAttr",
26
+ "type": "hardcodedType",
27
+ "value": "hardcodedValue"
28
+ }
29
+ ],
30
+ "commands": [
31
+ {
32
+ "name": "commandAttr",
33
+ "type": "commandType"
34
+ }
35
+ ],
36
+ "internal_attributes": [
37
+ {
38
+ "customField": "customValue"
39
+ }
40
+ ]
41
+ }
42
+ ]
43
+ }
@@ -2,7 +2,7 @@
2
2
  {
3
3
  "apikey" : "7e5721f478220be21f41f4700e50be2",
4
4
  "token" : "e50b7781d5e2d39d577e7c15e4a21f470ed39d5777e7c15e2e2d39d5777e7c1e2",
5
- "cbroker" : "http://10.0.0.2:1026",
5
+ "cbHost" : "http://10.0.0.2:1026",
6
6
  "entity_type" : "device",
7
7
  "resource" : "/iot/d",
8
8
  "service" : "smart_gondor",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "apikey" : "gdb13gq3f8q4beuk263besr8eh9ri",
13
- "cbroker" : "http://10.0.0.2:1026",
13
+ "cbHost" : "http://10.0.0.2:1026",
14
14
  "entity_type" : "device",
15
15
  "resource" : "/iot/d",
16
16
  "service" : "dumb_mordor",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "apikey" : "hs4h2ke7oerfdvrexbyh54w2",
28
- "cbroker" : "http://10.0.0.2:1026",
28
+ "cbHost" : "http://10.0.0.2:1026",
29
29
  "entity_type" : "device",
30
30
  "resource" : "/iot/d",
31
31
  "service" : "demo",
@@ -234,6 +234,26 @@ describe('Jexl expression interpreter', function () {
234
234
  });
235
235
  });
236
236
 
237
+ describe('When an concatarr function is used with an array', function () {
238
+ it('should work on the expression value', function (done) {
239
+ expressionParser.parse('array|concatarr(array)', scope, function (error, result) {
240
+ should.not.exist(error);
241
+ result.should.deepEqual([1, 2, 1, 2]);
242
+ done();
243
+ });
244
+ });
245
+ });
246
+
247
+ describe('When an joinarrtostr function is used with an array', function () {
248
+ it('should work on the expression value', function (done) {
249
+ expressionParser.parse('array|joinarrtostr("_")', scope, function (error, result) {
250
+ should.not.exist(error);
251
+ result.should.equal('1_2');
252
+ done();
253
+ });
254
+ });
255
+ });
256
+
237
257
  describe('When a function is applied to an array', function () {
238
258
  it('should work on the expression value', function (done) {
239
259
  expressionParser.parse('array[1]+1', scope, function (error, result) {
@@ -313,14 +333,15 @@ describe('Jexl expression interpreter', function () {
313
333
 
314
334
  describe('When a JSON parse transformation is applied', function () {
315
335
  it('should work on the expression value', function (done) {
316
- expressionParser.parse('"{\\"name\\":\\"John\\",\\"surname\\":\\"Doe\\"}"|jsonparse', scope, function (
317
- error,
318
- result
319
- ) {
320
- should.not.exist(error);
321
- result.should.eql(scope.object);
322
- done();
323
- });
336
+ expressionParser.parse(
337
+ '"{\\"name\\":\\"John\\",\\"surname\\":\\"Doe\\"}"|jsonparse',
338
+ scope,
339
+ function (error, result) {
340
+ should.not.exist(error);
341
+ result.should.eql(scope.object);
342
+ done();
343
+ }
344
+ );
324
345
  });
325
346
  });
326
347