From 697ca68083cc8132ac08925f1ce63b2c7ecc3ea8 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 3 Apr 2024 15:55:51 +0200 Subject: [PATCH 01/15] add test case --- test/functional/testCases.js | 94 +++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 5d558c7c3..d23ee28ea 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -1036,7 +1036,8 @@ const testCases = [ ] }, { - describeName: '0140 - Simple group with active attribute + chained JEXL expression text (a | trim | replacestr("hello","hi"))', + describeName: + '0140 - Simple group with active attribute + chained JEXL expression text (a | trim | replacestr("hello","hi"))', provision: { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', @@ -1976,6 +1977,97 @@ const testCases = [ } ] }, + { + describeName: '0430 - Simple group with active attribute + timestamp mapping defined', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + timestamp: true, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'mydatetime', + name: 'TimeInstant', + type: 'DateTime' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap 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: { + mydatetime: '2022-02-02T02:22:22.222Z' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata 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: { + mydatetime: '2022-02-02T02:22:22.222Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + } + ] + }, // 0500 - EXPLICIT ATTRIBUTES TESTS { describeName: '0500 - Group with explicit attrs:false (boolean) + active atributes', From 61b3c236b866c55ad1437e363d5989f242aeca25 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 3 Apr 2024 16:37:56 +0200 Subject: [PATCH 02/15] check if timestamp is a mapped attribute --- lib/services/ngsi/entities-NGSI-v2.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 38d0a1471..42dfb87a8 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -278,6 +278,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call let jexlctxt = {}; //will store the whole context (not just for JEXL) let payload = {}; //will store the final payload let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. + let timestampAttr = null; let plainMeasures = null; //will contain measures POJO let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); @@ -308,15 +309,14 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt); //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on) - const mustInsertTimeInstant = - typeInformation.timestamp !== undefined - ? typeInformation.timestamp - : false; + const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false; if (mustInsertTimeInstant) { //remove TimeInstant from measures measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE); - + if (typeInformation && typeInformation.active) { + timestampAttr = typeInformation.active.filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); + } if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { //if it comes from a measure if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) { @@ -327,6 +327,16 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call ); return; } + } else if (timestampAttr && timestampAttr.length > 0 && plainMeasures[timestampAttr[0]['object_id']]) { + // Maybe TimeInstant is a mapped attribute + if (moment(plainMeasures[timestampAttr[0]['object_id']], moment.ISO_8601, true).isValid()) { + timestamp.value = plainMeasures[timestampAttr[0]['object_id']]; + } else { + callback( + new errors.BadTimestamp(plainMeasures[timestampAttr[0]['object_id']], entityName, typeInformation) + ); + return; + } } else if (!typeInformation.timezone) { timestamp.value = new Date().toISOString(); jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; From a1014271f2dc604769aef9a322ab45a13c93c5d2 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 4 Apr 2024 09:47:57 +0200 Subject: [PATCH 03/15] update doc --- doc/api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api.md b/doc/api.md index 8849cb276..6b5410d43 100644 --- a/doc/api.md +++ b/doc/api.md @@ -133,9 +133,9 @@ parameters. The specific parameters that can be configured for a given config gr ### Devices A device contains the information that connects a physical device to a particular entity in the Context Broker. Devices -are identified by a `device_id`, and they are associated to an existing config group based in `apiKey` matching or -`type` matching (in the case `apiKey` matching fails). For instance, let's consider a situation in which a config group -has been provisioned with `type=X`/`apiKey=111` and no other config group has been provisioned. +are identified by a `device_id`, and they are associated to an existing config group based in `apiKey` matching. For +instance, let's consider a situation in which a config group has been provisioned with `type=X`/`apiKey=111` and no +other config group has been provisioned. The IoT Agents offer a provisioning API where devices can be preregistered, so all the information about service and subservice mapping, security information and attribute configuration can be specified in a per device way instead of From bfe95a3dfa3cc5352546dd32eae16dc01a358cfd Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 4 Apr 2024 10:56:17 +0200 Subject: [PATCH 04/15] add new tests --- test/functional/testCases.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index d23ee28ea..f927e7d60 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2065,6 +2065,40 @@ const testCases = [ type: 'DateTime' } } + }, + { + shouldName: + 'A - WHEN sending a measure without timestamp through http IT should use system timestamp for mapped attribute and use it for timestmap and other metadata 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, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + } + }, + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + } } ] }, From 1d3092af76e8e32b36df964c39c448feb5bee2f6 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 4 Apr 2024 11:15:06 +0200 Subject: [PATCH 05/15] update test --- lib/services/ngsi/entities-NGSI-v2.js | 17 ++++++++++------- test/functional/testCases.js | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 42dfb87a8..32b0c3c86 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -278,7 +278,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call let jexlctxt = {}; //will store the whole context (not just for JEXL) let payload = {}; //will store the final payload let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. - let timestampAttr = null; + let timestampAttrs = null; //list of mapped TimeInstant attributes let plainMeasures = null; //will contain measures POJO let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); @@ -314,8 +314,11 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call if (mustInsertTimeInstant) { //remove TimeInstant from measures measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE); + //search for a Timestamp mapped in an attribute if (typeInformation && typeInformation.active) { - timestampAttr = typeInformation.active.filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); + timestampAttrs = typeInformation.active.filter( + (item) => item.name === constants.TIMESTAMP_ATTRIBUTE && item.object_id !== null + ); } if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { //if it comes from a measure @@ -327,13 +330,13 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call ); return; } - } else if (timestampAttr && timestampAttr.length > 0 && plainMeasures[timestampAttr[0]['object_id']]) { - // Maybe TimeInstant is a mapped attribute - if (moment(plainMeasures[timestampAttr[0]['object_id']], moment.ISO_8601, true).isValid()) { - timestamp.value = plainMeasures[timestampAttr[0]['object_id']]; + } else if (timestampAttrs && timestampAttrs.length > 0 && plainMeasures[timestampAttrs[0]['object_id']]) { + // Maybe TimeInstant is a mapped attribute, but just the first one + if (moment(plainMeasures[timestampAttrs[0]['object_id']], moment.ISO_8601, true).isValid()) { + timestamp.value = plainMeasures[timestampAttrs[0]['object_id']]; } else { callback( - new errors.BadTimestamp(plainMeasures[timestampAttr[0]['object_id']], entityName, typeInformation) + new errors.BadTimestamp(plainMeasures[timestampAttrs[0]['object_id']], entityName, typeInformation) ); return; } diff --git a/test/functional/testCases.js b/test/functional/testCases.js index f927e7d60..28f0181fb 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2070,6 +2070,7 @@ const testCases = [ shouldName: 'A - WHEN sending a measure without timestamp through http IT should use system timestamp for mapped attribute and use it for timestmap and other metadata attributes sent to Context Broker', type: 'single', + isRegex: true, measure: { url: 'http://localhost:' + config.http.port + '/iot/json', method: 'POST', From c36ef984368bc886298866c69a0073d726409450 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 4 Apr 2024 13:26:32 +0200 Subject: [PATCH 06/15] add case for a bad timestamp when timestamp is an attribute mapped --- lib/services/ngsi/entities-NGSI-v2.js | 8 +++- test/functional/testCases.js | 57 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 32b0c3c86..d9a01629d 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -332,7 +332,13 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call } } else if (timestampAttrs && timestampAttrs.length > 0 && plainMeasures[timestampAttrs[0]['object_id']]) { // Maybe TimeInstant is a mapped attribute, but just the first one - if (moment(plainMeasures[timestampAttrs[0]['object_id']], moment.ISO_8601, true).isValid()) { + if (timestampAttrs[0]['expression']) { + timestamp.value = expressionPlugin.applyExpression( + timestampAttrs[0]['expression'], + jexlctxt, + typeInformation + ); + } else if (moment(plainMeasures[timestampAttrs[0]['object_id']], moment.ISO_8601, true).isValid()) { timestamp.value = plainMeasures[timestampAttrs[0]['object_id']]; } else { callback( diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 28f0181fb..5153eec7b 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2103,6 +2103,63 @@ const testCases = [ } ] }, + { + describeName: '0431 - Simple group with active attribute + bad timestamp mapping defined', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + timestamp: true, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'mydatetime', + name: 'TimeInstant', + type: 'DateTime', + expression: '"2033-03-03T" + "03:33:33.333Z"' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to fixed timestamp attribute and use it for timestmap 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: { + mydatetime: '2022-02-02T02:22:22.222Z' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + TimeInstant: { + value: '2033-03-03T03:33:33.333Z', + type: 'DateTime' + } + } + } + ] + }, // 0500 - EXPLICIT ATTRIBUTES TESTS { describeName: '0500 - Group with explicit attrs:false (boolean) + active atributes', From 7c1fb9eb0246a4fc51e308462c94f1ef04b13b99 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 4 Apr 2024 13:29:12 +0200 Subject: [PATCH 07/15] Update CNR --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index e69de29bb..7490ae7a5 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -0,0 +1 @@ +- Fix timestamp attribute mapped cases (#1557) From 2514fbba7d8c9b298e96b60e7facf6563a1d17ca Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 5 Apr 2024 13:10:59 +0200 Subject: [PATCH 08/15] add new case when measure timestamp is send with a attributed mapped to timestamp --- lib/services/ngsi/entities-NGSI-v2.js | 23 +++++++++-------- test/functional/testCases.js | 36 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index d9a01629d..e15ada2d5 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -320,17 +320,8 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call (item) => item.name === constants.TIMESTAMP_ATTRIBUTE && item.object_id !== null ); } - if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { - //if it comes from a measure - if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) { - timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE]; - } else { - callback( - new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName, typeInformation) - ); - return; - } - } else if (timestampAttrs && timestampAttrs.length > 0 && plainMeasures[timestampAttrs[0]['object_id']]) { + + if (timestampAttrs && timestampAttrs.length > 0 && plainMeasures[timestampAttrs[0]['object_id']]) { // Maybe TimeInstant is a mapped attribute, but just the first one if (timestampAttrs[0]['expression']) { timestamp.value = expressionPlugin.applyExpression( @@ -346,6 +337,16 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call ); return; } + } else if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { + //if it comes from a measure + if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) { + timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE]; + } else { + callback( + new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName, typeInformation) + ); + return; + } } else if (!typeInformation.timezone) { timestamp.value = new Date().toISOString(); jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 5153eec7b..25cbe2ae0 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2100,6 +2100,42 @@ const testCases = [ type: 'DateTime' } } + }, + { + shouldName: + 'A - WHEN sending a measure with timestamp through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata 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: { + mydatetime: '2022-02-02T02:22:22.222Z', + TimeInstant: '2033-03-03T03:33:33.333Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } } ] }, From 79a406a64d331a7d5ec48e8a7964ac1268d2f6b5 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 8 Apr 2024 10:36:45 +0200 Subject: [PATCH 09/15] update tests with explicitAttrs case --- test/functional/testCases.js | 170 ++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 25cbe2ae0..d87005902 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2140,7 +2140,175 @@ const testCases = [ ] }, { - describeName: '0431 - Simple group with active attribute + bad timestamp mapping defined', + describeName: '0431 - Simple group with active attribute + timestamp mapping defined + explicitAttrs', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + timestamp: true, + explicitAttrs: true, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'mydatetime', + name: 'TimeInstant', + type: 'DateTime' + }, + { + object_id: 'a', + name: 'a', + type: 'Text' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap 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: { + mydatetime: '2022-02-02T02:22:22.222Z' + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata 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: { + mydatetime: '2022-02-02T02:22:22.222Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + { + shouldName: + 'A - WHEN sending a measure without timestamp through http IT should use system timestamp for mapped attribute and use it for timestmap and other metadata attributes sent to Context Broker', + type: 'single', + isRegex: true, + 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, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + } + }, + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + } + }, + { + shouldName: + 'A - WHEN sending a measure with timestamp through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata 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: { + mydatetime: '2022-02-02T02:22:22.222Z', + TimeInstant: '2033-03-03T03:33:33.333Z', + a: 23 + } + }, + expectation: { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + } + ] + }, + { + describeName: '0432 - Simple group with active attribute + bad timestamp mapping defined', provision: { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', From 9e3e7bba9e30789e0ecbeff50f32394a6598b230 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 8 Apr 2024 13:25:32 +0200 Subject: [PATCH 10/15] refactor timestamp procesing --- lib/services/ngsi/entities-NGSI-v2.js | 104 ++++++++++---------------- test/functional/testCases.js | 61 +++------------ 2 files changed, 53 insertions(+), 112 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index e15ada2d5..bc4d5710d 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -311,51 +311,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on) const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false; - if (mustInsertTimeInstant) { - //remove TimeInstant from measures - measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE); - //search for a Timestamp mapped in an attribute - if (typeInformation && typeInformation.active) { - timestampAttrs = typeInformation.active.filter( - (item) => item.name === constants.TIMESTAMP_ATTRIBUTE && item.object_id !== null - ); - } - - if (timestampAttrs && timestampAttrs.length > 0 && plainMeasures[timestampAttrs[0]['object_id']]) { - // Maybe TimeInstant is a mapped attribute, but just the first one - if (timestampAttrs[0]['expression']) { - timestamp.value = expressionPlugin.applyExpression( - timestampAttrs[0]['expression'], - jexlctxt, - typeInformation - ); - } else if (moment(plainMeasures[timestampAttrs[0]['object_id']], moment.ISO_8601, true).isValid()) { - timestamp.value = plainMeasures[timestampAttrs[0]['object_id']]; - } else { - callback( - new errors.BadTimestamp(plainMeasures[timestampAttrs[0]['object_id']], entityName, typeInformation) - ); - return; - } - } else if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { - //if it comes from a measure - if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) { - timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE]; - } else { - callback( - new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName, typeInformation) - ); - return; - } - } else if (!typeInformation.timezone) { - timestamp.value = new Date().toISOString(); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; - } else { - timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; - } - } - logger.debug( context, 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j with value=%j', @@ -498,15 +453,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call currentAttr.hitted = hitted; currentAttr.value = valueExpression; - - //add TimeInstant to attr metadata - if (mustInsertTimeInstant) { - if (!currentAttr.metadata) { - currentAttr.metadata = {}; - } - currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; - } - //store de New Attributte in entity data structure if (hitted === true) { if (entities[attrEntityName] === undefined) { @@ -547,17 +493,47 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call //more mesures may be added to the attribute list (unnhandled/left mesaures) l if (explicit === false && Object.keys(measures).length > 0) { - //add Timestamp to measures if needed - if (mustInsertTimeInstant) { - for (let currentMeasure of measures) { - if (!currentMeasure.metadata) { - currentMeasure.metadata = {}; + entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures); + } + + if (mustInsertTimeInstant) { + // search timestamp just in entity attrs + for (let ename in entities) { + for (let etype in entities[ename]) { + timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); + if (timestampAttrs && timestampAttrs.length > 0) { + timestamp.value = timestampAttrs[0]['value']; + } + } + } + if (timestamp.value) { + if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) { + callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation)); + return; + } + } else { + if (!typeInformation.timezone) { + timestamp.value = new Date().toISOString(); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; + } else { + timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; + } + } + + // Add TimeInstant to all attribute metadata of all entities + for (let ename in entities) { + for (let etype in entities[ename]) { + for (let currentAttr of entities[ename][etype]) { + if (currentAttr.name !== constants.TIMESTAMP_ATTRIBUTE) { + if (!currentAttr.metadata) { + currentAttr.metadata = {}; + } + currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; + } } - currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; } - //If just measures in the principal entity we missed the Timestamp. } - entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures); } //PRE-PROCESSING FINISHED @@ -587,7 +563,9 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call (item) => attr.object_id !== undefined && item.object_id === attr.object_id )))) ) { - isEmpty = false; + if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) { + isEmpty = false; + } e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata }; } } diff --git a/test/functional/testCases.js b/test/functional/testCases.js index d87005902..1d876a783 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2007,30 +2007,6 @@ const testCases = [ } }, should: [ - { - shouldName: - 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap 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: { - mydatetime: '2022-02-02T02:22:22.222Z' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - TimeInstant: { - value: '2022-02-02T02:22:22.222Z', - type: 'DateTime' - } - } - }, { shouldName: 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker', @@ -2175,30 +2151,6 @@ const testCases = [ } }, should: [ - { - shouldName: - 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap 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: { - mydatetime: '2022-02-02T02:22:22.222Z' - } - }, - expectation: { - id: globalEnv.entity_name, - type: globalEnv.entity_type, - TimeInstant: { - value: '2022-02-02T02:22:22.222Z', - type: 'DateTime' - } - } - }, { shouldName: 'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker', @@ -2350,12 +2302,23 @@ const testCases = [ k: globalEnv.apikey }, json: { - mydatetime: '2022-02-02T02:22:22.222Z' + mydatetime: '2022-02-02T02:22:22.222Z', + a: 23 } }, expectation: { id: globalEnv.entity_name, type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2033-03-03T03:33:33.333Z', + type: 'DateTime' + } + } + }, TimeInstant: { value: '2033-03-03T03:33:33.333Z', type: 'DateTime' From 75fe27697390ad4bd14e9ed9c43d8e9d6b7baa70 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Mon, 8 Apr 2024 16:17:39 +0200 Subject: [PATCH 11/15] move timestampAttrs to local block --- lib/services/ngsi/entities-NGSI-v2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index bc4d5710d..d6342c8ed 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -278,7 +278,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call let jexlctxt = {}; //will store the whole context (not just for JEXL) let payload = {}; //will store the final payload let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. - let timestampAttrs = null; //list of mapped TimeInstant attributes let plainMeasures = null; //will contain measures POJO let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); @@ -498,6 +497,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call if (mustInsertTimeInstant) { // search timestamp just in entity attrs + let timestampAttrs = null; //list of mapped TimeInstant attributes for (let ename in entities) { for (let etype in entities[ename]) { timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); From e940fae4960fda2a789183c31f6b49ad5c268d07 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 9 Apr 2024 13:17:57 +0200 Subject: [PATCH 12/15] add multientity cases --- lib/services/ngsi/entities-NGSI-v2.js | 81 +++++----- test/functional/testCases.js | 141 ++++++++++++++++++ .../ngsiv2/plugins/multientity-plugin_test.js | 36 ++++- 3 files changed, 211 insertions(+), 47 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index d6342c8ed..8b2dfe007 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -277,7 +277,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture let jexlctxt = {}; //will store the whole context (not just for JEXL) let payload = {}; //will store the final payload - let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. let plainMeasures = null; //will contain measures POJO let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); @@ -312,13 +311,12 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call logger.debug( context, - 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j with value=%j', + 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j', entityName, plainMeasures, typeInformation, jexlctxt, - mustInsertTimeInstant, - timestamp.value + mustInsertTimeInstant ); //Now we can calculate the EntityName of primary entity @@ -495,47 +493,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures); } - if (mustInsertTimeInstant) { - // search timestamp just in entity attrs - let timestampAttrs = null; //list of mapped TimeInstant attributes - for (let ename in entities) { - for (let etype in entities[ename]) { - timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); - if (timestampAttrs && timestampAttrs.length > 0) { - timestamp.value = timestampAttrs[0]['value']; - } - } - } - if (timestamp.value) { - if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) { - callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation)); - return; - } - } else { - if (!typeInformation.timezone) { - timestamp.value = new Date().toISOString(); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; - } else { - timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; - } - } - - // Add TimeInstant to all attribute metadata of all entities - for (let ename in entities) { - for (let etype in entities[ename]) { - for (let currentAttr of entities[ename][etype]) { - if (currentAttr.name !== constants.TIMESTAMP_ATTRIBUTE) { - if (!currentAttr.metadata) { - currentAttr.metadata = {}; - } - currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; - } - } - } - } - } - //PRE-PROCESSING FINISHED //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload @@ -548,6 +505,31 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call let e = {}; e.id = String(ename); e.type = String(etype); + let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. + let timestampAttrs = null; + if (mustInsertTimeInstant) { + // get timestamp for current entity + + timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE); + if (timestampAttrs && timestampAttrs.length > 0) { + timestamp.value = timestampAttrs[0]['value']; + } + + if (timestamp.value) { + if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) { + callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation)); + return; + } + } else { + if (!typeInformation.timezone) { + timestamp.value = new Date().toISOString(); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; // nosense + } else { + timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; // nosense + } + } + } //extract attributes let isEmpty = true; for (let attr of entities[ename][etype]) { @@ -566,6 +548,15 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) { isEmpty = false; } + if (mustInsertTimeInstant) { + // Add TimeInstant to all attribute metadata of all entities + if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) { + if (!attr.metadata) { + attr.metadata = {}; + } + attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; + } + } e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata }; } } diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 1d876a783..5105be2d0 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2327,6 +2327,147 @@ const testCases = [ } ] }, + { + describeName: '0433 - Simple group with active attribute + several timestamp mappings defined', + provision: { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: globalEnv.apikey, + timestamp: true, + entity_type: globalEnv.entity_type, + commands: [], + lazy: [], + attributes: [ + { + object_id: 'a', + name: 'a', + type: 'Text' + }, + { + object_id: 'mydatetime1', + name: 'TimeInstant', + type: 'DateTime', + entity_name: 'TestType:TestDevice1', + entity_type: 'TestType' + }, + { + object_id: 'mydatetime2', + name: 'TimeInstant', + type: 'DateTime', + entity_name: 'TestType:TestDevice2', + entity_type: 'TestType' + }, + { + object_id: 'a1', + name: 'a1', + type: 'Text', + expression: 'a', + entity_name: 'TestType:TestDevice1', + entity_type: 'TestType' + }, + { + object_id: 'a2', + name: 'a2', + type: 'Text', + expression: 'a', + entity_name: 'TestType:TestDevice2', + entity_type: 'TestType' + } + ] + } + ] + }, + headers: { + 'fiware-service': globalEnv.service, + 'fiware-servicepath': globalEnv.servicePath + } + }, + should: [ + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attributes and use it for timestmap and other metadata attributes sent to Context Broker', + type: 'multientity', + isRegex: true, + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + a: 23, + mydatetime1: '2011-01-01T01:11:11.111Z', + mydatetime2: '2022-02-02T02:22:22.222Z' + } + }, + expectation: { + actionType: 'append', + entities: [ + { + id: globalEnv.entity_name, + type: globalEnv.entity_type, + a: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + } + }, + TimeInstant: { + value: _.isDateString, + type: 'DateTime' + } + }, + { + id: 'TestType:TestDevice1', + type: globalEnv.entity_type, + a1: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2011-01-01T01:11:11.111Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2011-01-01T01:11:11.111Z', + type: 'DateTime' + } + }, + { + id: 'TestType:TestDevice2', + type: globalEnv.entity_type, + a2: { + value: 23, + type: 'Text', + metadata: { + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + }, + TimeInstant: { + value: '2022-02-02T02:22:22.222Z', + type: 'DateTime' + } + } + ] + } + } + ] + }, + // 0500 - EXPLICIT ATTRIBUTES TESTS { describeName: '0500 - Group with explicit attrs:false (boolean) + active atributes', diff --git a/test/unit/ngsiv2/plugins/multientity-plugin_test.js b/test/unit/ngsiv2/plugins/multientity-plugin_test.js index 9e23f6fe6..a1c716e96 100644 --- a/test/unit/ngsiv2/plugins/multientity-plugin_test.js +++ b/test/unit/ngsiv2/plugins/multientity-plugin_test.js @@ -307,6 +307,38 @@ const iotAgentConfig = { } ] }, + WeatherStation10: { + commands: [], + type: 'WeatherStation', + lazy: [], + active: [ + { + object_id: 'p', + name: 'pressure', + type: 'Hgmm' + }, + { + object_id: 'h', + name: 'humidity', + type: 'Percentage', + entity_name: 'Higro2000', + entity_type: 'Higrometer', + metadata: { + unitCode: { + type: 'Text', + value: 'Hgmm' + } + } + }, + { + object_id: 'TimeInstant', + name: 'TimeInstant', + type: 'DateTime', + entity_name: 'Higro2000', + entity_type: 'Higrometer' + } + ] + }, Sensor001: { commands: [], type: 'Sensor', @@ -1488,7 +1520,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () { describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plugin', function () { beforeEach(function (done) { - logger.setLevel('FATAL'); + logger.setLevel('DEBUG'); iotAgentConfig.timestamp = true; iotAgentLib.activate(iotAgentConfig, function () { iotAgentLib.clearAll(function () { @@ -1635,7 +1667,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu value: '2018-06-13T13:28:34.611Z' } ]; - iotAgentLib.update('ws5', 'WeatherStation', '', tsValue, function (error) { + iotAgentLib.update('ws5', 'WeatherStation10', '', tsValue, function (error) { should.not.exist(error); contextBrokerMock.done(); done(); From b5143dcf9fb8216c9c4f1ac47a1369375eb791c1 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 9 Apr 2024 14:35:19 +0200 Subject: [PATCH 13/15] remove unnecessary check --- lib/services/ngsi/entities-NGSI-v2.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 8b2dfe007..7d89db6f4 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -545,9 +545,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call (item) => attr.object_id !== undefined && item.object_id === attr.object_id )))) ) { - if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) { - isEmpty = false; - } + isEmpty = false; if (mustInsertTimeInstant) { // Add TimeInstant to all attribute metadata of all entities if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) { From ca5cd359ebcd7de03abc5902f7158b0fa9337099 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 10 Apr 2024 09:41:01 +0200 Subject: [PATCH 14/15] add tests about empty multientity mapped timestamp entites --- test/functional/testCases.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/functional/testCases.js b/test/functional/testCases.js index 5105be2d0..3d85e08e3 100644 --- a/test/functional/testCases.js +++ b/test/functional/testCases.js @@ -2328,7 +2328,7 @@ const testCases = [ ] }, { - describeName: '0433 - Simple group with active attribute + several timestamp mappings defined', + describeName: '0433 - Simple group with active attribute + several timestamp mappings defined in multientity', provision: { url: 'http://localhost:' + config.iota.server.port + '/iot/services', method: 'POST', @@ -2464,6 +2464,36 @@ const testCases = [ } ] } + }, + { + shouldName: + 'A - WHEN sending a measure through http IT should map the measure to timestamp attributes for just mapped entity and sent to Context Broker', + type: 'multientity', + isRegex: true, + measure: { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + qs: { + i: globalEnv.deviceId, + k: globalEnv.apikey + }, + json: { + mydatetime1: '2011-01-01T01:11:11.111Z' + } + }, + expectation: { + actionType: 'append', + entities: [ + { + id: 'TestType:TestDevice1', + type: globalEnv.entity_type, + TimeInstant: { + value: '2011-01-01T01:11:11.111Z', + type: 'DateTime' + } + } + ] + } } ] }, From 4c8a62478a645ff66218e73012b3b52c0c9da833 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 10 Apr 2024 15:46:20 +0200 Subject: [PATCH 15/15] pre-calculate currentIsoDate --- lib/services/ngsi/entities-NGSI-v2.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 7d89db6f4..b888789ab 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -500,6 +500,8 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call payload.actionType = 'append'; payload.entities = []; + const currentIsoDate = new Date().toISOString(); + const currentMoment = moment(); for (let ename in entities) { for (let etype in entities[ename]) { let e = {}; @@ -522,11 +524,13 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call } } else { if (!typeInformation.timezone) { - timestamp.value = new Date().toISOString(); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; // nosense + timestamp.value = currentIsoDate; + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; } else { - timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); - jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; // nosense + timestamp.value = currentMoment + .tz(typeInformation.timezone) + .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; } } }