diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 8b048a426..f41cfb1cf 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -10,7 +10,7 @@ body:
- type: input
id: version
attributes:
- label: IoT Agent Node Libe version the issue has been seen with
+ label: IoT Agent Node Lib version the issue has been seen with
description: |
Do not submit bug reports about anything but the two most recently released *major* systemd versions upstream!
If there have been multiple stable releases for that major version, please consider updating to a recent one before reporting an issue.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f2d031af7..9e0172797 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,10 +14,10 @@ jobs:
steps:
- name: Git checkout
uses: actions/checkout@v2
- - name: Use Node.js 16.x
+ - name: Use Node.js 24.x
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 24.x
- name: Run Remark Markdown Linter
run: |
npm install
@@ -31,10 +31,10 @@ jobs:
steps:
- name: Git checkout
uses: actions/checkout@v2
- - name: Use Node.js 16.x
+ - name: Use Node.js 24.x
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 24.x
- name: Run EsLint Node.js Linter
run: |
npm install
@@ -45,15 +45,15 @@ jobs:
runs-on: ubuntu-latest
services:
mongodb:
- image: mongo:4.2
+ image: mongo:8.0
ports:
- 27017:27017
strategy:
matrix:
node-version:
- - 14.x
- - 16.x
- - 18.x
+ - 20.x
+ - 22.x
+ - 24.x
steps:
- name: Git checkout
uses: actions/checkout@v2
@@ -61,7 +61,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: '${{ matrix.node-version }}'
- - name: 'Unit Tests with Node.js ${{ matrix.node-version }}'
+ - name: 'Unit and Functional Tests with Node.js ${{ matrix.node-version }}'
run: |
npm install
npm test
@@ -72,16 +72,16 @@ jobs:
needs: unit-test
services:
mongodb:
- image: mongo:4.2
+ image: mongo:8.0
ports:
- 27017:27017
steps:
- name: Git checkout
uses: actions/checkout@v2
- - name: 'Test Coverage with Node.js 16.x'
+ - name: 'Test Coverage with Node.js 24.x'
uses: actions/setup-node@v1
with:
- node-version: 16.x
+ node-version: 24.x
- run: |
npm install
npm run test:coverage
diff --git a/.readthedocs.yml b/.readthedocs.yml
index b287e6e4b..75480d72a 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -3,7 +3,11 @@ version: 2
mkdocs:
configuration: mkdocs.yml
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.8"
+
python:
- version: 3.8
install:
- requirements: doc/requirements.txt
diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE
index 7c5b112a3..c35ba6174 100644
--- a/CHANGES_NEXT_RELEASE
+++ b/CHANGES_NEXT_RELEASE
@@ -1,2 +1,5 @@
-- Fix: appendMode at general level (config.js / env var) changes its default from false to true
-- Fix: remove sensitive MongoDB connection parameters from log traces (remove 'option' object from logs)
\ No newline at end of file
+- Add: info about status of CB, IotaM, Mongo and MQTT broker to endpoints /iot/about (and /version) and /metrics (#1763)
+- Add: endpoint /ready about if iotagent was started properly (#1763)
+- Add: new env vars about healthcheck IOTA_HEALTH_CHECK, IOTA_HEALTH_CHECK_INTERVAL, IOTA_HEALTH_CHECK_TIMEOUT, IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS and IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP (#1763)
+- Upgrade express dep from 4.21.2 to 4.22.1
+- Upgrade markdown dep from 3.3.4 to 3.8.1
diff --git a/Changelog b/Changelog
new file mode 100644
index 000000000..817ee78cc
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,830 @@
+4.13.0 (Feb 4th, 2026)
+
+- Add: hash jexl transformation
+- Fix: change log level to warn when error loading extra jexl transformation
+- Fix: meassure by name was not progressed if object_id is defined also in the same attribute (#1660)
+
+4.12.0 (Jan 28th, 2026)
+
+- Fix: set default value for defaultTransport to allow set polling and transport for autoprovisioned devices when no IOTA_DEFAULT_TRANSPORT en var is used (#1751)
+- Fix: allow match auto-provisioned devices and groups when the same type is used by several groups (which always uses different apikeys) (#1713)
+
+4.11.0 (Oct 23th, 2025)
+
+- Add: IOTA_MONGO_URI env var
+- Add: locale string number jexl function (#1737)
+- Deprecated: IOTA_MONGO_ different from IOTA_MONGO_URI (use IOTA_MONGO_URI instead)
+- Fix: broken notification command mode (it was not working without providing targetEntityId and targetEntityType in the notification, which belongs to another command mode: advancedNotification)
+- Fix: use normalized in attrsFormat for commands subscriptions (#1743)
+
+4.10.0 (Oct 6th, 2025)
+
+- Add: command modes (new field cmdMode): legacy, notification and advancedNotification (#1732)
+- Fix: update device with useCBflowControl
+- Set Nodejs 20 as minimum version in packages.json (effectively removing Nodev16 and Nodev18 from supported versions)
+
+4.9.0 (Aug 22nd, 2025)
+
+- Add: support NGSI-LD QueryEntities endpoint for lazy attributes (#1722)
+- Add: support for NGSI-LD valueType and more NGSI-LD sub-property types (new config server.ldSupport.datatype and associated env var IOTA_LD_SUPPORT_DATA_TYPE) (#1723)
+- Fix: store as lastMeasure just orignal measure when is provided (#1669)
+- Fix: init express trust proxy to proper fill from field of domain logger
+- Fix: fill from log field with real IP
+
+4.8.0 (May 23rd, 2025)
+
+- Add: notification-based commands (#1455)
+- Add: allow define headers in device commands (iotagent-json#873)
+- Add: index for Device model based on {service: 1, subservice: 1, id: 1, apikey: 1} (#1576)
+- Fix: Duplicated Devices when burst measures to non provisioned Device (iotagent-json#865)
+- Fix: modified JEXL transformations (toisostring, gettime, parseint, etc.) to return null instead of NaN when some unexpected situation occurs (#1701)
+
+4.7.0 (February 3rd, 2025)
+
+- Add: store (and recover) previous jexlctxt and make available in current jexlctxt (#1690)
+- Add: option to force to use CB flow control with new API field useCBflowControl at group and device device level (#1420)
+- Add: useCBflowControl config setting (IOTA_CB_FLOW_CONTROL env var) to set CB flow control behaviour at instance level (#1420)
+- Add: allow remove last measure in device
+- Add: store last measure in device (by id, apikey, service and subservice) and new API field storeLastMeasure at group and device levels (#1669)
+- Add: storeLastMeasure config setting (IOTA_STORE_LAST_MEASURE env var) to set default store last measure behaviour at instance level (#1669)
+- Fix: set polling and transport for autoprovisioned devices
+- Upgrade express dep from 4.19.2 to 4.21.2
+- Upgrade mongodb devdep from 4.17.1 to 4.17.2
+- Upgrade mongoose dep from 5.13.20 to 8.9.5 (solving vulnerabilies CVE-2024-53900 and CVE-2025-23061) (#1674)
+
+4.6.0 (September 18th, 2024)
+
+- Add: openmetrics-compatible /metrics endpoint in nortbound API (#1627)
+- Add: new JEXL transformations for including into an array keys that have a certain value: valuePicker and valuePickerMulti
+- Add: attribute metadata and static attributes metadata added to jexl context (#1630)
+- Add: /iot/groups API endpoints (as equivalent to /iot/services) (#752)
+- Fix: service header to use uppercase in case of update and delete (#1528)
+- Fix: Allow to send to CB batch update for multimeasures for NGSI-LD (#1623)
+- Upgrade body-parser dep from 1.20.0 to 1.20.3
+- Deprecated: /iot/services API routes
+- Remove: push-based stats (including stats section in config file)
+
+4.5.0 (June 11th, 2024)
+
+- Fix: do not propage TimeInstant when explicitAttrs is empty array (#1606)
+- Fix: update device using previous device apikey to avoid error when apikey is updated (iotagent-json#833)
+- Fix: allow send multiple measures to CB in a batch (POST /v2/op/update) and sorted by TimeInstant when possible, instead of using multiples single request (iotagent-json#825, #1612)
+- Fix: default express limit to 1Mb instead default 100Kb and allow change it throught a conf env var 'IOTA_EXPRESS_LIMIT' (iotagent-json#827)
+- Fix: accept 201 status code from context broker with NGSI-LD interface when first measure is sent and device is created in it (#1617)
+
+4.4.0 (April 26th, 2024)
+
+- Add: allow devices with the same device_id in the same service and subservice but different apikey (#1589)
+- Add: process JEXL expressions in metadata attributes (#1598)
+- Fix: TimeInstant mapped from attribute overrides default behaviours (#1557)
+- Fix: reduce information showed handling errors to just config flags (#1594)
+- Upgrade pymongo dep from 4.3.3 to 4.6.3
+- Upgrade express dep from 4.18.1 to 4.19.2
+
+4.3.0 (February 27th, 2024)
+
+- Add: POST /iot/op/delete operation to delete multiple devices at once (#1578)
+- Add: log and return device/group information when EntityGenericError, TypeNotFound, DeviceNotFound, BadTimestamp, BadGeocoordinates, CommandNotFound, GroupNotFound, MissingAttributes, DuplicateDeviceId and DuplicateGroup errors (iotagent-json#815)
+- Fix: store device subscriptions updates (#1086)
+- Fix: badtimestamp error should not progress to ContextBroker
+- Hardening: simplify implementation so typeInformation contains global config values (#1515)
+
+4.2.0 (January 30th, 2024)
+
+- Add: progress non expected id and type in measures under measure_ prefix
+
+4.1.0 (January 8th, 2024)
+
+- Add: include apikey in queries to memory registry for groups
+- Add: transport and endpoint to Group model (#1542)
+- Fix: store commands from Group at Device level at provision device time
+- Fix: log device id when BadTimestamp error (*)
+
+(*) Also in hotfix version 4.0.1
+
+4.0.0 (January 8th, 2024)
+
+- Large refactor of IOTA Lib code to make it simpler
+- Add: payloadType to device and groups model to support NGSI-v2 and NGSI-LD formats in southbound measures (iotagent-json#778)
+- Fix: use but not store timestamp and explicitAttrs from group with autoprovisioned devices (#1504, partially)
+- Fix: MongoDB connection authentication (user and password were not actually used) (#1510)
+- Fix: add static attributes when use explicitAttrs (#1506)
+- Fix: ensure service and subservice in context of error handlers using req headers
+- Fix: remove attribute of measures with name id or type to avoid collisions (#1485)
+- Fix: ensure entity id and type are string (#1476)
+- Fix: update ctxt allow nested expressions (#1493)
+- Fix: change log level contextAvailable expression exception (from WARN to INFO)
+- Fix: null values arithmetics in JEXL expressions (#1440)
+- Fix: remove mongo DeprecationWarning: current Server Discovery and Monitoring engine is deprecated by setting useUnifiedTopology = true
+- Fix: mongodb.authSource / IOTA_MONGO_AUTH_SOURCE was not correctly supported (#1526)
+- Upgrade mongodb dev dep from 4.17.0 to 4.17.1
+- Remove: single configuration mode (along with IOTA_SINGLE_MODE env var) (#1469)
+- Remove: extractVariables from jexl plugin (no needed anymore since the removal of bidireational plugin)
+- Remove: time compression support
+- Remove: autocast (including env var IOTA_AUTOCAST) (#1498)
+
+3.4.0 (September 20th, 2023)
+
+- Fix: for non evaluable expressions which should not be propagated to attrs values (#1440)
+- Fix: try to use apikey from measure/group to find, update, remove device in first attempt (#1426, #1435)
+- Fix: ensure device apikey in already provisioned device (#1430)
+- Upgrade mongodb dev dep from 4.7.0 to 4.17.0
+- Upgrade mongoose dep from 5.13.14 to 5.13.20
+- Remove: bidirectional plugin (#1413)
+- Remove: appendMode and creation of initial entity (#1413)
+
+3.3.0 (August 24th, 2023)
+
+- Add: do not create initial entity when a new device is provisioned and appendMode is false or NGSI-LD is used
+- Add: evaluate group entityNameExp with a context including measures (#1334)
+- Add: allow update timestamp and other config fields of device
+- Fix: check array access in extractVariables of jexlPlugin when bidirectionalPlugin is enabled
+- Fix: explicitAttrs of device was tainted even if not defined
+- Fix: do not include static, lazy and commands from group to device to avoid duplicate them in device (#1377)
+- Fix: use 'options=upsert' when update ngsiv2 CB entities and appendMode is enabled (#956)
+- Fix: do not propagate group config (timestamp and explicitAttrs) to autoprovisioned devices (at database level) (#1377)
+- Fix: appendMode at general level (config.js / env var) changes its default from false to true
+- Fix: remove sensitive MongoDB connection parameters from log traces (remove 'option' object from logs)
+- Deprecate: bidirectional plugin
+- Deprecate: appendMode
+- Remove: expressionLanguage field (as it it not longer needed, due to only one expression language is supported, after the removal of legacy expressions in 3.2.0) (#1384)
+
+3.2.0 (May 23rd, 2023)
+
+- Add: save result of apply expression attribute into current context for futher usages (#1305)
+- Add: skipValue for expression to allow skip attribute when match with result of apply expression (#1353)
+- Add: add hextostring transformation jexl function (#1367)
+- Add: JEXL support to bidirectinal plugin
+- Fix: avoid usage of newlines in logs (#1342)
+- Fix: explicitAttributes true case: should progress just active attributes which receives measures and all active attributes with expressions
+- Fix: add missed active attributes (with expressions) for ctxt for evaluate explicitAttrs expression (#1351)
+- Fix: allow use static attributes from group in entityNameExp of group
+- Fix: disable device attribute entity_name validation using pattern
+- Fix: re-enable invocation of custom plugins on update (#1348)
+- Fix: change level of log about not context available for apply expression from warn to info
+- Remove: remove legacy expression support (removing IOTA_DEFAULT_EXPRESSION_LANGUAGE env var and associated defaultExpressionLanguage config.js setting) (#1340)
+
+3.1.0 (April 25th, 2023)
+
+- Add: support to run tests with node 18
+- Fix: propagate TimeInstant to all metadata attributes when TimeInstant is provided as measure
+- Set Nodejs 16 as minimum version in packages.json (effectively removing Nodev14 from supported versions)
+
+3.0.0 (March 30th, 2023)
+
+- Refactor replace alias, multientity, expressions and timestamp plugins by a single processing (#1195, #1314)
+- Refactor NGSI-LD processing to align with alias, multientity, expressions and timestamp plugin changes (#1322)
+- Fix: simplify cast to native attributes type: use just JSON.parse (#1323)
+
+2.26.0 (March 15th, 2023)
+
+- Add NGSI-LD Merge-Patch Support Handling (#1283)
+- Update to offer NGSI-LD 1.6.1. Registrations (#1302)
+- Document removal of support for NGSI-LD 1.3.1 Interface
+
+2.25.0 (January 24th, 2023)
+
+- Add: missing JEXL default transformations: joinarrtostr, concatarr, floor, ceiling, round, tofixed, gettime, toisostring, localestring, now (#1308)
+- Fix: save group apikey in device when autoprovision device (#1245)
+- Fix: INVALID EXPRESSION policy at JEXL expression in attributes and entity_names (default values will be propagated and a warn will be logged instead of exception) (#1292)
+- Fix: empty device_id on device registration (#1298)
+- Hardening: export pluginUtils (so they can be used by IOTA code importing the library)
+- Set Nodejs 14 as minimum version in packages.json (effectively removing Nodev12 from supported versions)
+
+2.24.0 (September 2nd, 2022)
+
+- Add: metadata support to NGSI-v2 notifications (bidirectional attributes)
+- Add: update bidirectional plugin to reuse expressionPlugin (#1281)
+- Add: NGSI-LD Unsupported Endpoints return valid error responses (#1277)
+- Add: datasetId and metadata support for NGSI-LD PATCH+PUT operations (commands)
+- Add: datasetId and metadata support for NGSI-LD Notifications (bidirectional attributes)
+- Add: exception control for bad JEXL expression
+- Fix: support empty entityNameExp and exp
+- Fix: device id, type, service, subservice and entity name were not available to NGSI attrs JEXL expressions (#1282)
+- Fix: protect explicitAttrs evaluation of a bad formed expression (#1175)
+- Fix: ensure GET /ngsi-ld/v1/entities/ returns a valid response (#1276)
+- Fix: ensure circular error objects are logged correctly (#1280)
+
+2.23.0 (August 2nd, 2022)
+
+- Add: support of object_id as part of explicitAttrs list (#1267)
+- Fix: make available measures in JEXL context for explicitAttrs (#1269)
+
+2.22.0 (July 15th, 2022)
+
+- Add: add id, type, service and subservice to context expression of multientity plugin
+- Add: set jexl as default parser for generic plugin
+- Add: entityNameExp to Group Model to allow define device.name using expressions (#1222, #1145)
+- Add: identify each flow of mongo alarms about device group
+- Add: support for NGSI-LD LanguageProperty
+- Add: NGSI-LD PUT support
+- Add: NGSI-LD support for multiple attribute updates
+- Fix: apply expressions to pull commands (#1263)
+- Fix: return error to client if expression or multientity plugins get error
+- Fix: multientity just with entity_type but not entity_name, then use device.name (#1220)
+- Fix: allow use JEXL expresions in explicitAttrs for conditional propagation of measures (reopen #1179, for Devices)
+- Fix: avoid raising mongo alarm (DEVICE_GROUP_NOT_FOUND) before launch op against CB
+- Fix: fix issue with unrecognized NGSILD-Tenant and NGSILD-Path headers
+- Fix: mongodb dependency moved to devDependencies (as it is used only in test code)
+- Upgrade mongodb dep from 3.6.12 to 4.7.0
+- Upgrade body-parser dependency from 1.19.0 to 1.20.0
+- Upgrade express dependency from 4.16.4 to 4.18.1
+- Upgrade got dependency from 11.8.2 to 11.8.5
+- Upgrade moment-timezone dependency from 0.5.25 to 0.5.34
+- Upgrade mongoose dependency from 5.7.14 to 5.13.14
+- Upgrade query-string dependency from 6.5.0 to 7.1.1
+- Upgrade underscore dependency from 1.12.1 to 1.13.4
+- Upgrade uuid dependency from 3.3.2 to 8.3.2
+- Upgrade coveralls dev dependency from 3.1.0 to 3.1.1
+- Upgrade eslint dev dependency from 7.5.0 to 8.18.0
+- Upgrade eslint-config-tamia dev dependency from 7.2.5 to 8.0.0
+- Upgrade eslint-plugin-prettier dev dependency from 3.1.4 to 4.0.0
+- Upgrade lint-staged dev dependency from 10.2.11 to 12.3.8
+- Upgrade prettier dev dependency from 2.0.5 to 2.7.1
+- Upgrade mocha dev dependency from 8.0.1 to 10.0.0
+- Upgrade nock dev dependency from 13.0.3 to 13.2.7
+- Upgrade remark-cli dev dependency from 8.0.1 to 10.0.1
+- Upgrade remark-preset-lint-recommended dev dependency from 4.0.1 to 6.1.2
+- Upgrade sinon dev dependency from 9.0.2 to 14.0.0
+- Upgrade textlint dev dependency from 11.7.6 to 12.2.1
+- Upgrade textlint-rule-terminology dev dependency from 2.1.4 to 3.0.2
+- Upgrade textlint-rule-write-good dev dependency from 1.6.2 to 2.0.0
+
+2.21.0 (April 29th, 2022)
+
+- Add: new JEXL transformations for handle upper and low case: touppercase, tolowercase
+- Add: do not process attr expressions when current attr update is of type 'commandStatus' or 'commandResult'
+- Add: add expressions, payloadType and contentType to commands models
+- Fix: exclude all attrs when explicitAttrs is an empty array (#1235)
+- Fix: bad query searching for group using resource instead of type by executeUpdateSideEffects (commands) (#1216)
+- Fix: search device and group for a command using entity type when provided to proper match (#1211)
+- Upgrade mongodb dep from 3.6.8 to 3.6.12
+- Upgrade mongoose dep from 5.7.7 to 5.7.14
+- Upgrade moment dep from 2.24.0 to 2.29.2 due to security vulnerability (CVE-2022-24785)
+- Upgrade async dep from 2.6.2 to 2.6.4 due to security vulnerability (CWE-1321)
+
+2.20.0 (Marth 3rd, 2022)
+
+- Fix: clean service and subservice domain fields when clean domain so logs now show correct serv= and subserv= (#1201)
+- Remove: obsolete NGSIv1-based commandLine utility (along with command-shell-lib and mu2 dependencies)
+- Remove: long time unused agentConsole utility
+
+2.19.0 (February 7th, 2022)
+
+- Add: extend explicitAttrs to allow JEXL expression for conditional propagation of measures (#1179)
+- Add: new JEXL transformations for adding and removing elements to/from Array: addset, removeset
+- Add: allow to use static attributes into JEXL expressions (#1184)
+- Add: add underscore to legacy expression parser (#1191)
+- Add: support for adding and updating device handler (iota-json#602)
+- Add: support both WARN and WARNING log levels (#1146)
+- Add: allow update polling device field (iota-json#602)
+- Fix: fill serv/subserv properly in context log of plugins (#1189)
+- Fix: change severity from WARN to INFO when invalid context in contextAvailable of legacy expression plugin occcurs
+- Fix: try get group by type using current type if no type is provided (#1155)
+- Fix: JEXL NGSI-LD null processing extended to remove invalid calculated values (#1164)
+- Fix: Remove preprocess stripping of explicitAttrs (#1151)
+- Fix: NGSI-LD commands are invalid (#1185)
+- Fix: replace request obsolete library by got (#858)
+- Upgrade logops dep from 2.1.0 to 2.1.2 due to colors dependency corruption
+
+2.18.0 (November 12th, 2021)
+
+- Add: new JEXL get TimeZone Offset (valid for summer winter schedulles) and binary shifting for bitwise operator
+- Add: new JEXL transformation for Array.slice(init, end) in order to simplify binary-frame-string field extraction
+- Fix: IOTA sends illegal entity type or entity id to CB (#1124)
+- Fix: include attribute metadata in attribute device info for API requests (#1115)
+- Fix: set status code response from CB for EntityGenericError is raised (#1109)
+- Fix: export fillDomains function to iotagent-node-lib available functions (needed to fix logs in iotagent-json#587 and iotagent-ul#508)
+- Fix: keep internalAttributes stored in device mongo collection when update it (#1092)
+- Fix: accept the entry internal_attributes in a PUT /iot/devices/:deviceId (#1083)
+- Fix: amend null processing for NGSI-LD (#1118)
+- Fix: support of same entity_id and diferent entity_type in multientity plugin (#1127)
+- Fix: align the NGSI-v2 provisioning keywords Text and TextUnrestricted to map directly to NGSI-LD Property (#1131)
+_ Fix: JEXL undefined, null and falsy values support (#1132)
+- Updated JEXL dependecy from 2.1.1 to 2.3.0 to allow the usage of NGSI operators ($inc, etc.)
+- Remove: NGSI-v1 implementation (#966)
+
+2.17.0 (August 30th, 2021)
+
+- Add: new JEXL transformation for JSON parse/stringify (#1087)
+- Add: support for externalised JEXL transformations, at libraty level and inyected via API (#1056)
+- Fix: try to search for a device group service in base of not only by apikey but also type (#1076)
+- Fix: rename provided jexl function sumaarray to addreduce (#1073)
+
+2.16.0 (June 18, 2021)
+
+- Add: more functions to JEXL transformations (#1052)
+- Add: db uri and options in mongo connection log INFO trace
+- Fix: prevent to update an entity with an empty payload
+- Fix: use getDeviceSilently in checkDuplicates to avoid raise a false mongo alarm.
+- Fix: expose getConfigurationSilently to enable retrieve a configuration without raise a false mongo alarm (#1007)
+- Fix: add getTypeSilently for device group and use in device registration to avoid false mongo alarm
+- Fix: check isNaN value in JEXL extractContext to avoid misbehaviours (#1066)
+- Fix: simplify contextAvailable for JEXL perser allowing null and undefined substitutions (#1053)
+- Fix: do not transform attribute value using attribute type after apply expression plugin JEXL (#1036)
+- Fix: use explicitAttrs defined in group is not defined at device level (#1045, #1016)
+- Fix: autoprovision feature, removing the relationship with appendMode (#1003)
+- Fix: autoprovision functionality not working as expected when configured at device/group level in NGSIv1 API (#1003)
+- Fix: appendMode not working in NGSIv2
+- Fix: apply NGSIv2 timestamp properly to array of entities
+- Fix: check expression context before apply it in entity_name (#1040)
+- Fix: check entityId is valid after apply expression in multientity plugin (#1039)
+- Fix: check access to data in fillService facility
+- Fix: use expressionLanguage defined in group is not defined at device level (#1027)
+- Fix: ensure service of groups, device and commands is stored in mongo in lowercase (#1023)
+- Fix: group command is not provisioned in device when entity_type is different (#1011)
+- Fix: avoid automatic conversion from geo:xxxx ('xxxx' diferent from 'json') to geo:json (reverts the work done in PR #854) (*)
+- Fix: update MongoDB accesses to use lean() for faster retrieval
+- Fix: PUT /iot/devices/:deviceId operation to implement proper update of entity_name and entity_type (#770)
+- Fix: Include @context on NGSI-LD subscription (#1019)
+- Upgrade underscore dep from 1.9.1 to 1.12.1 due to security vulnerability
+- Upgrade mongodb dep from 3.2.3 to 3.6.8
+- Upgrade mongoose dep from 5.7.5 to 5.7.7
+- Set Nodejs 12 as minimum version in packages.json (effectively removing Nodev10 from supported versions)
+
+(*) Also included in 2.15.1
+
+2.15.0 (February 18th, 2021)
+
+- Add: return apikey in GET device if device was provisioned with explicit apikey (#977)
+- Add: allow update device apikey using Update (put) API (#978)
+- Add: defaultEntityNameConjunction config (env var IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION) and configuration group API field for default entity_name conjunction (#944)
+- Add: prettier code formatting
+- Add: husky and lint-staged
+- Add: basic NGSI-LD support as experimental feature (#842)
+ - Active measures
+ - GeoJSON and DateTime, unitCode and observedAt NGSI-LD support
+ The NGSI v2 TimeInstant element has been mapped onto the NGSI-LD observedAt property
+ The NGSI v2 metadata.unitCode attribute has been mapped onto the NGSI-LD unitCode property
+ - Multi-measures
+ - Lazy Attributes
+ - Commands
+ - Mixed mode (based in ngsiVersion field in the provisioning API)
+ - Support to linked data entities to enable the traversal of the nodes of the knowledge graph
+- Fix: ensure use proper expression parser in multientity plugin (#989)
+- Fix: ensure registrationId is updated when device with commands is updated
+- Fix: use null instead of ' ' as default attribute value in entity provisioned (#938)
+- Fix: Add support for lazy and internal_attributes in service notifications to Manager (#768)
+- Fix: combine multi-entity and expressions with duplicate attribute name, by enabling expression over object_id (which are not duplicated in a attribute mapping contrary to name) (#941)
+- Fix: bug in legacy and JEXL expression that was not converting "0" to 0
+- Fix: support for mapping different attributes to the same entity_name
+- Fix: Ensure GeoJSON is correctly encoded in NGSI-v2 requests (#961)
+- Update codebase to use ES6
+ - Remove JSHint and jshint overrides
+ - Add esLint using standard tamia presets
+ - Replace var with let/const
+ - Fix or disable eslint errors
+
+2.14.0 (Noviembre 16th, 2020)
+
+- Add: enable use group commands in device and register it in iotagent-manager
+- Add: extends commands definition to add mqtt options (qos, retain)
+- Add: include findTypeSilently for groups to log some false errors as debug instead of alarm
+- Add: include description field in group schema
+- Add: include from in log context (#918)
+- Add: lax validation mode using IOTA_RELAX_TEMPLATE_VALIDATION (#920)
+- Fix: add missed expressionLanguage to deviceProvisioningServer
+- Fix: bad type in check expressionLanguage at device provision
+- Fix: Update internal attributes in Group update (#917)
+- Fix: Static attributes from service not applied if device has static attributes (#757)
+- Fix: IOTA_EXPLICIT_ATTRS env var was not working
+- Move Docker secret support inside the Node Application - remove Entrypoint (#885)
+
+2.13.0 (September 14th, 2020)
+
+- Add: support for JEXL as expression language (#801, #687, #868)
+- Add: expressionLanguage to save/update device and group
+- Add: mongodb additional options (ssl and extraArgs) to be able to connect "MongoDB as a Cloud Service" (#859)
+- Add: template to allow metadata to be set via the Device API (#870)
+- Add: selectively ignore some of the measures (iotagent-json#416, iotagent-ul#372)
+- Fix: multientity when more than 2 attribute with same name and object_id (#877)
+- Fix: log always writing the same correlator id and transaction id (iota-json#426, iota-ul#326)
+- Fix: log device group error detail when group not found
+- Fix: fix and use fillService to fix logs in provision
+- Fix: some ngsi service log levels
+- Fix: listing service groups returns only a single service group (#894)
+- Fix: do not intercept error about DEVICE_GROUP_NOT_FOUND in checkApiKeyAndResource (#889)
+- Fix: do not intercept error about DEVICE_NOT_FOUND in findOrCreate and checkDUplicates device (#889)
+- Overall update of dev package dependencies
+- Set Nodejs 10 as minimum version in packages.json (effectively removing Nodev8 from supported versions)
+
+2.12.0 (April 7th, 2020)
+
+- Add: NGSIv2 metadata support to device provisioned attributes
+- Add NGSIv2 metadata support to attributeAlias plugin.
+- Add mongodb authentication: IOTA_MONGO_USER, IOTA_MONGO_PASSWORD and IOTA_MONGO_AUTH_SOURCE (#844)
+- Fix: Error message when sending measures with unknown/undefined attribute
+- Fix: add null check within executeWithSecurity() to avoid crash (#829)
+
+2.11.0 (November 4th, 2019)
+
+- Add: NGSIv2 context forwarding (lazy attributes and commands) (#527)
+- Add: autoprovision (appendMode) in device and group provision (#805)
+- Add: configure multithreading using cluster nodejs module (through multicore config.js parameter or IOTA_MULTI_CORE environment variable).
+- Fix: avoid using throttling in subscriptions (both in NGSIv1 and NGSIv2 configurations)
+- Fix: deviceRegistrationDuration ignored in NGSIv2 registrations and subscriptions creation so they are created non-expirable
+- Fix: possible startup fail using IOTA_CB_ and IOTA_NORTH_ env vars due to uninitization of config object
+- Fix: using "append" instead of "APPEND" (which is deprecated) in NGSIv2 operations
+- Fix: allow blank as an override for defaultResource
+- Upgrade mongoose dependency from 5.5.4 to 5.7.5 due to security warning
+
+2.10.0 (August 13th, 2019)
+
+- Set Nodejs 8 as minimum version in packages.json (effectively removing Nodev6 from supported versions)
+- Add: to use apikey to search group configuration at device registration time
+- Upgrade async dependency from 2.6.1 to 2.6.2
+- Upgrade body-parser dependency from ~1.18.3 to ~1.19.0
+- Upgrade moment dependency from ~2.22.2 to ~2.24.0
+- Upgrade moment-timezone dependency from ~0.5.21 to ~0.5.25
+- Upgrade mongodb dependency from 3.1.8 to 3.2.3
+- Upgrade mongoose dependency from 5.3.6 to 5.5.4
+- Upgrade query-string dependency from 6.2.0 to 6.5.0
+- Upgrade coveralls dev dependency from ~3.0.2 to ~3.0.3
+- Upgrade jshint dev dependency from ~2.9.6 to ~2.10.2
+- Upgrade mocha dev dependency from 5.2.0 to 6.1.4
+- Upgrade nock dev dependency from 10.0.1 to 10.0.6
+- Upgrade timekeeper dev dependency from 2.1.2 to 2.2.0
+
+2.9.0 (May 22nd, 2019)
+
+- Set Nodejs 6 version in packages.json (effectively removing Nodev4 as supported version)
+- Add: new functions to set callbacks for removal of devices and groups (#735)
+- Add: support for authentication to NGSI subscription requests (#592)
+- Add: IOTA_AUTH_URL, IOTA_AUTH_CLIENT_ID, IOTA_AUTH_CLIENT_SECRET and IOTA_AUTH_TOKEN_PATH env vars
+- Add: handlers defined with setConfigurationHandler receive as argument the service, subservice, resource and apikey (#769)
+- Fix: process dies if reconnection to DB fails instead of remain in a zombie state (#772)
+- Fix: incomplete HTTPS support for NGSI subscriptions (#593)
+- Fix: user provider timestamp correctly propagated to mapped entities when multientity pluging is used (#748)
+- Fix: add ?type parameter in CB request updates to avoid potential entity ambiguities (#733)
+- Upgrade from logops 1.0.8 to 2.1.0
+- Upgrade from node-uuid ~1.4.1 to uuid ~3.3.2.
+
+2.8.1 (December 19th, 2018)
+
+- Add: extends OAuth2 authentication to support integration with FIWARE Keyrock IDM (#663)
+- Add: implements OAuth2 authentication as part of device provisioning requests (#663)
+- Add: uses provisioned timezone to generate TimeInstant values (#672)
+- Add: missed conf env vars about authentication (#704)
+- Add: timestamp in device and group provision (#655)
+- Add: protocol in device provision if not provided (#652)
+- Fix: conf env var about contextbroker host
+- Fix: use trust and cbHost from deviceGroup (#685)
+- Fix: multientity multimeasure with the same att name
+- Fix: missing support for device=true in the delete service endpoint (see #596)
+- Fix: timestamp is now included just in non empty (more than id and type) multienty entitites
+- Fix: object_id fields are not introduced in CB requests by alias plugin (#660)
+- Fix: isDomain is not used anymore for context availability registration (#701)
+- Fix: checks ISO8601 timeinstants provided by devices (#679)
+- Fix: corrects linting and includes npm run lint in travis CI
+- Fix: mosquitto.conf.example file not found by iot/mosquitto Dockerfile (#554)
+- Upgrade: logops dependence from 1.0.0 to 1.0.8
+- Upgrade: async dependence from 1.5.2 to 2.6.1
+- Upgrade: body-parser dependence from ~1.11.0 to ~1.18.3
+- Upgrade: express dependence from ~4.11.2 to ~4.16.4
+- Upgrade: jison dependence from 0.4.17 to 0.4.18
+- Upgrade: moment dependence from ~2.19.4 to ~2.22.2
+- Upgrade: mongodb dependence from 2.2.35 to 3.1.8
+- Upgrade: mongoose dependence from 4.13.12 to 5.3.6
+- Upgrade: mustache dependence from 2.2.1 to 3.0.0
+- Upgrade: query-string dependence from 4.3.4 to 6.2.0
+- Upgrade: request dependence from 2.39.0 - 2.81.0 to 2.88.0
+- Upgrade: underscore dependence from ~1.7.0 to ~1.9.1
+- Ensure precise dependencies (~=) are used in packages.json
+- Remove: XML related code, dependencies and files (#691)
+- Remove: old unused dependencies (sax, grunt, closure-linter-wrapper)
+
+2.7.0 (August 6th, 2018)
+
+- Add: allow NGSIv2 for updating active attributes at CB, through configuration, supporting all plugins (Timestamp Compression, Timestamp Processing, Expression Translation, Attribute Alias, Event, Bidirectionality and Multientity) (#527)
+- Add: supports NGSIv2 for device provisioning (entity creation and context registration) at CB (#527)
+- Add: casting attribute's values to JSON native types (activable by config.autocast)
+- Add: support HTTPS requests toward Keystone (if url is used in the authorization configuration)
+- Add: support OAuth2 provider as authentication backend (#591)
+- Add: support for HTTPS requests toward CB (if IOTA_CB_URL is used) (#578)
+- Add: support HTTPS requests toward IOTAManager (if IOTA_IOTAM_URL is used) (#578)
+- Add: log mongo error before raise alarm (#577)
+- Add: configuration service is exposed as part of the module
+- Fix: Check boolean config fields against right boolean value
+- Fix: IOTA_SIGLE_MODE, IOTA_APPEND_MODE, IOTA_POLLING_EXPIRATION, IOTA_POLLING_DAEMON_FREQ env vars are now taken into account (previously there were ignored)
+- Fix: IOTA_MONGO_RETRIES and IOTA_MONGO_RETRY_TIME env vars are now taken into account (previously there were ignored)
+- Fix: automatic device provisioning by means of Configuration Group fails due to a bug in findConfigurationGroup function (#544)
+- Fix: static attributes seem not supported in Configuration Group provisioning (#550)
+- Fix: documentation and examples, using correctly "attributes" field in the Configuration API instead of "active" (#558)
+- Fix: correctly return 404 when asking to remove a not existing device (#585)
+- Fix: Context broker environment variables are correctly loaded
+- Fix: GET call to northbound group endpoint returns multiple services (#604)
+- Fix: timestampProcessPlugin supports processing an array of NGSI entities in NGSIv2 (#621)
+- Fix: undefined MONGO-ALARMS in logs due to a bad mongo query. (#630, #577)
+- Fix: multientity ngsiv1 duplicated attributes in entity (#627)
+- Fix: match attributes by object_id using multientity plugin and alias in ngsiv2 (#629)
+- Fix: check foundGroup count in generateDuplicateHandle (#633)
+- Fix: allow provision device entity with multientity attributes (#628)
+- Fix: multientity with multiple measures in ngsiv2 (#635)
+- Fix: aligns expression plugin and config.autocast casting (#643)
+- Fix: replace context by ctx name var to avoid weird collisions
+- Upgrade mongodb dependency version to 2.2.35
+- Upgrade mongoose dependency version to 4.13.12
+
+2.6.0 (February 26th, 2018)
+
+- Upgrade mongoose dependence version to 4.13.3 (*)
+- Fix command name in some commands logs at debug log level
+- Fix bug in mergeArrays function of deviceService, precluding updates to work when internalAttributes of type Array were used (#571)
+
+(*) Also in release 2.5.1
+
+2.5.0 (November 17th, 2017)
+
+- Fix mustache dependence version to 2.2.1 due to detected medium vulnerability
+- Alarm raises with null error (#521)
+- Capture Mongo DB connection errors
+- Update MongoDB driver in order to fix NODE-818 error (#545)
+
+2.4.1 (November 21th, 2016)
+
+- Alarm raises with null error (#521)
+- Capture mongo Connection Errors
+
+2.4.0 (November 10th, 2016)
+
+- Add an Attribute Expression Plugin (#440)
+- Add Multientity plugin (#468)
+- Add bidirectionality mechanisms for composed attributes (#474)
+- Add String composition functions for expressions
+- Bidirectional plugin doesn't apply to active attributes defined in a Configuration Group (#479)
+- FIX Multiple values not allowed in polling commands
+- FIX [Docu] Remove "Remove tag" from examples (#481)
+- FIX Notifications return 404 instead of a 200 OK (#484)
+- FIX Device creation with attribute named "TimeInstant" is not allowed (#486)
+- FIX Iota transformation plugins regex validation error (#489)
+- ADD Add single quotes as valid string marks
+- FIX Get device info does not include the plugins fields (#492)
+- FIX Transformation plugin for indexof (#493)
+- FIX Entities in multientity scenarios cannot be defined as expressions (#488)
+- FIX Cast needed for transformation plugin (#497)
+- FIX Delete service should return 204 instead 200 (#174)
+- FIX Bidirectional plugin provision fails (#503)
+- FIX Missing context in logger entries (#454)
+- FIX Transformation plugin variable names set (#506)
+- FIX Transformation plugin - More than one variable name with numbers (#508)
+- FIX Pool command expired but CB not updated (#509)
+- ADD an alarm management module (#512)
+- FIX Missing operations manual (#485)
+- ADD a mechanism to share constants and error definitions between IoTAs (#386)
+
+2.3.0 (October 5th, 2016)
+
+- Add polling command support for the Library #455
+- FIX Entity name used instead of id in command identification
+- Add expiration time for Polling commands (#460)
+- FIX It is not possible to create two devices with the same ID (#462)
+- ADD At service start-up retry if dependencies (mongo, orion, etc.) are not available #357
+
+2.2.0 (September 9th, 2016)
+
+- Fix precedence in merging device and configuration attributes.
+- Merge configuration information in the retrieveDevice() function (#405)
+- Expose the singleConfigurationMode attribute using environment variables (#404)
+- Devices provisioned with no configuration are not found in HTTP measure reporting (#124)
+- In MongoDB32 no group is found if undefined resource is passed (#410)
+- Extract common middleware to a new module (#415)
+- Add an administrative operation to get the log level (#417)
+- Add the 'comp' field to the IoTA's log (#419)
+- Fix Static attributes not shown in device group updates (#423)
+- Fix IoTAgent returns non-NGSI error as a response to an error in the Context Providers (#422)
+- Fix Documentation typos and aspects to fix (#426)
+- Add the names of the DB fields to the documentation (#427)
+- FIX IOTAM Registrations won't send static attributes in group information #430
+- Make the IoTA path in the IoTAM registration request configurable
+- Document TimeInstant entity attribute and metadata (#435)
+- FIX Update device operation ignores static attributes (#438)
+- FIX Logger modules not being singleton cause logging inconsistencies (#442)
+- Remove Device ID Index (#445)
+
+2.1.4 (September 8th, 2016)
+
+- Minor bugfixing
+
+2.1.3 (September 8th, 2016)
+
+- Minor bugfixing
+
+2.1.2 (July 4th, 2016)
+
+- Minor bugfixing
+
+2.1.1 (June 24th, 2016)
+
+- Minor bugfixing.
+
+2.1.0 (June 20th, 2016)
+
+- ADD migration tool to migrate data from C++ IoTAgents to Node.js based ones (#388)
+- FIX changing the log level with environment variables did not work
+- Add functions to allow the IoTAs to check or create devices (#392)
+- ADD version retrieval path (#395)
+- Use the attributes defined in the Configuration as default alias values (#397)
+
+2.0.0 (June 1st, 2016)
+
+- 'Mongodb' module not found when using the --production (#325)
+- Fix Domain objects not working properly (#331).
+- Enable IPv6 for HTTP protocol for the Northbound APIs (#333).
+- Fix Timestamp addition for static attributes generate multiple identical timestamp attributes (#335)
+- Fixes Error deleting service (#328)
+- Command line client raises a 404 when listing groups (#338)
+- When persistence is defined for stats but not for the registries, the IOTA crashes (#340)
+- Refactor the tests in order to ease the creation of new ones (#341)
+- Configurations should allow adding static attributes (#319)
+- Command updates not implemented (#308)
+- Add a new plugin for the creation of Event attributes (#297)
+- Change the way the Configurations work (#343)
+- Initial entity provisioning does not take configurations into account (#263)
+- Entity retrieval on incoming notifications or queries uses the entity name without the type (#260)
+- Change the Identification of the Devices. (#17)
+- Allow the getDevicesByAttribute() function of the In-Memory repository to search through all the services (#353)
+- Fix Pass service and subservice as arguments in command handlers (#355)
+- Update the documentation to reflect the changes in function signatures (#352)
+- Add defaultAPIKey and utils to retrieve the APIKey for a device (#360)
+- Fix Command handler doesn't work if no update handler is defined (#363)
+- Read configuration values from environment variables (#365)
+- Fix Endpoint attribute not stored in mongo (#369)
+- Fix Add a prefix for all the environment variables (#375)
+- Assess command_result field (#374)
+- Environment IOTA_MONGO_REPLICASET missing (#378)
+- Add TimeInstant processing plugin (#380)
+
+1.0.0 (May 3rd, 2016)
+
+- Change log level using the administration API (#289)
+- Use headers to track global transaction ID (#288)
+- Include service and subservice in log traces (#290)
+- Add endpoint attribute to the Device model
+- Update test shell to allow Stress test commands
+- Add support for MongoDB Replica Sets (#303)
+- Update commands to make them asynchronous under stress testing
+- Integrate with IoT Manager
+- Add new transport field (#309)
+- When a query comes to the IOTA the Static Attributes should not be returned (#312)
+- Add required files for integration with Sonar (#295)
+- Configuration API Should allow to modify API Key (#318)
+- Timestamp and level missing in logs (#320)
+
+0.10.0 (February 22nd, 2016)
+
+- Fix InternalAttributes type in create Device JSON Schema Template.
+- Updating device provisioning for new attributes was not working (#277)
+- Wrong references in logs, while creating new configurations (#279).
+- Create a config service to avoid multiple initializations (#167).
+- Use a single database configuration for all persistence services (#173).
+
+0.9.8 (February 12th, 2016)
+
+- Minor bugfixing.
+
+0.9.7 (February 10th, 2016)
+
+- Add a subscription mechanism in the NGSI service (#259).
+- Use a constants file to gather all the string constants in the system (#254).
+- Change timestamp type to 'ISO8601' (#265).
+- Fix documentation problems (#257) and (#262).
+- Update and fix dependencies with shrinkwrap.
+
+0.9.6 (February 10th, 2016)
+
+- FIX Getting the information of a provisioned device returns an spurious 'id' attribute in attribute descriptions (#245).
+
+0.9.5 (January 27th, 2016)
+
+- FIX Getting the information of a provisioned device returns an spurious 'id' attribute in attribute descriptions (#245).
+
+0.9.4 (January 26th, 2016)
+
+- FIX Library errors make HTTP Response invalid (#239).
+
+0.9.3 (January 14th, 2016)
+
+- FIX Alias plugin do not change attribute type.
+- ADD Extract the command line handlers to a single module (#178).
+- ADD global error handler (#231).
+- FIX Improve device provisioning documentation (#232).
+- FIX Crash with malformed PUT /devices/:id (#228).
+- ADD JSON Schema validation for the Device Provisioning API (#230).
+- FIX Attribute id in alias should be named 'object_id' (#233).
+
+0.9.2 (December 23rd, 2015)
+
+- FIX Typo dealing with attributes with metadata.
+
+0.9.1 (December 23rd, 2015)
+
+- Query middlewares won't work due to an extra parameter in callbacks (#220).
+
+0.9.0 (December 22nd, 2015)
+
+- Add groupRegistry.clear() to the clearAll function.
+- Add a plugin mechanism to perform translations in the entity data (#193).
+- Add /iot prefix in IOTA url for IOTAM integration.
+- Modify the answer for the GET devices and GET groups operations (#196).
+- Add APPEND Mode for Context Broker updates.
+- Add Timestamp compression plugin.
+- Add Metadata filtering to data filtering plugins (#204).
+- Fix Device DELETE should return 204 instead of 200 (#203).
+- Refactor the generic functions used in the time compression plugin to a common module (#205).
+- Modify the answer for the GET devices and GET groups operations (#196).
+- Fix List devices: the returned count value should be the total count of registered devices (#199).
+- Fix Asking for the list of configurations of a subservice return results for all services #(209).
+- Fix Integrate Node.js IOTAgents with the IOTA Manager (and the administration portal) (#159).
+- Fix Service based filtering in MongoDB registries won't work.
+- Add attribute aliases in updates (#182).
+- Fix bug when trying to stop an unstarted agent (#179).
+- Fix create device: the default value for entity_name should be [entity_type]:device_id.
+
+0.8.4 (November 24th, 2015)
+
+- Allow listDevices() to use a single param (#180).
+- Implement generic find function for the device registries.
+- Get info about iotA Version in IoTA-lib request (#184).
+
+0.8.3 (October 22nd, 2015)
+
+- FIX 500 error while removing devices (#151)
+- FIX Change Device Not Found to 404 instead of 500 (#161).
+- ADD First version of the stats registry (#160).
+- FIX Reestructure files and folders (#164).
+- ADD Mongodb action to persist stats.
+- FIX Use Express Methods to check content type (#170).
+
+0.8.2 (October 2nd, 2015)
+
+- ADD Command attributes to the initial entity
+- ADD Checking for multiple context responses (#153)
+- ADD Command option for the IoT Agent Tester to provision lists of devices.
+
+0.6.1 (September 4th, 2015)
+
+- FIX Return meaningful JSON error in ENTITY_GENERIC_ERROR.
+- FIX Add information to generic transport errors (#139).
+
+0.6.0 (September 4th, 2015)
+
+- ADD Authentication mechanisms for the IoT Agent tester.
+- ADD Examples of service and device provisioning for Thinking Things IOTAs.
+- FIX Add error information to generic NGSI errors in updates and queries (#125).
+- FIX Improve general maintainability (#126).
+- ADD Continuous integration using Travis (#131).
+- FIX Change the old temporary fiware-node-logger to use logops (#95).
+- ADD Device provisioning handler (#86).
+- ADD Support for MongoDB 3.0 (#119).
+- FIX Unstability in MongoDB tests (#128).
+
+0.5.1 (June 22nd, 2015)
+
+This minor release modifies the package.json file in order to make the included commands globally executable in a global installation of the module.
+
+0.5.0 (June 21st, 2015)
+
+- ADD Documentation about the release process.
+- FIX Crash when an update arrives and commands attribute is not defined.
+- ADD exit commands.
+- FIX missing headers in group operations.
+- ADD command to show and change IoTAgent console configuration.
+- FIX missing device in 'store()' function of the Memory Registry.
+
+0.4.0 (May 22nd, 2015)
+
+- Fix signature problems in agent console (#69 and #70).
+- Add implementation for the command attributes (#67).
+- Add missing headers in device provisioning with the IoT Agent Tester (#73).
+- Support for string quotes in IoT Agent Tester (#75)
+- Remove commandlineUtils files and substitute with command-node module.
+- Add InternalAttributes attribute for Configuration groups (#85).
+- Add configuration checks at startup (#84 and #83).
+
+0.3.0 (April 10th, 2015)
+
+- ADD Keywords to the package.json.
+- ADD Configuration API for dynamically creating configuration groups.
+- ADD Device Provisioning API and Device Configuration API commands in the testing clients.
+- ADD Pagination in the Device List operations.
+- FIX Remove mandatory constraint for service, servicepath and commands in Device Provisioning (#46).
+- FIX List devices based on the service and servicepath (#45).
+- FIX Lazy attributes mandatory in Device Provisioning (#51).
+- FIX Should allow to provision devices with unconfigured type (#50)
+- ADD Handler for configuration changes from the Configuration API.
+- ADD Constraint to forbid multiple devices with the same ID (#48).
+- ADD Constraint to ensure uniqueness of device configurations (#53).
+
+0.2.0 (February 11th, 2015)
+
+- Complete the Provisioning API with CRUD Operations.
+- Change signature of unregister() funciton to remove unneeded type.
+- Support XML in Context Provider updates and queries.
+- Fix multiple errors found during preparation of CPBR8 Workshop.
+- Improve the documentation to add: Configuration, Security, and IoT Library testing sections.
+- Fix package.json information to prepare the package for publishing.
+
+
+0.1.0 (February 11th, 2015)
+
+- First draft of the IoT Agent library.
diff --git a/README.md b/README.md
index 4c663787c..c0ca95bd9 100644
--- a/README.md
+++ b/README.md
@@ -1,306 +1,104 @@
-# FIWARE IoT Agent Node.js Library
+# FIWARE IoT Agent Node Library
-[](https://www.fiware.org/developers/catalogue/)
+[](https://www.fiware.org/developers/catalogue/)
[](https://opensource.org/licenses/AGPL-3.0)
[](https://stackoverflow.com/questions/tagged/fiware+iot)
[](http://iotagent-node-lib.readthedocs.org/en/latest/?badge=latest)
[](https://github.com/telefonicaid/iotagent-node-lib/actions?query=workflow%3ACI)
[](https://coveralls.io/github/telefonicaid/iotagent-node-lib?branch=master)
-
+
[](https://bestpractices.coreinfrastructure.org/projects/4671)
-This project aims to provide a Node.js module to enable IoT Agent developers to build custom agents for their devices
-that can easily connect to NGSI Context Brokers (such as [Orion](https://github.com/telefonicaid/fiware-orion)).
+An IoT Agent is a component that lets groups of IoT devices send their data to a NGSI Context Broker (such as
+[Orion Context Broker](https://github.com/telefonicaid/fiware-orion)) using their own native protocols and translating
+them into NGSI.
-An IoT Agent is a component that lets groups of devices send their data to and be managed from a FIWARE NGSI Context
-Broker using their own native protocols. IoT Agents should also be able to deal with security aspects of the FIWARE
-platform (authentication and authorization of the channel) and provide other common services to the device programmer.
+The **IoT Agent Node Lib** provides simple standardized REST API for registering, provisioning, discovering and managing
+devices and groups of devices. It also ensures that each IoT Agent can configure its device communications using a
+common vocabulary regardless of the payload, syntax or transport protocol used by the device itself
-This project is part of [FIWARE](https://www.fiware.org/). For more information check the FIWARE Catalogue entry for the
-[IoT Agents](https://github.com/Fiware/catalogue/tree/master/iot-agents).
-
-| :books: [Documentation](https://iotagent-node-lib.rtfd.io) | :mortar_board: [Academy](https://fiware-academy.readthedocs.io/en/latest/iot-agents/idas) | :dart: [Roadmap](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/roadmap.md) |
-| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
-
-
-## Index
+It also provides a common configuration framework driven by a `config.js` configuration file and a series of common ENV
+variables, configuring some flags for common functionality provided by the **IoT Agent node lib** (e.g. for contecting
+to a Conext Broker or for authenticating with an Identity Manager).Some of common utility functions provided by the
+**IoT Agent node lib** include:
-- [Background](#background)
-- [Install](#install)
-- [API](#api)
-- [Usage](#usage)
-- [Testing](#testing)
- - [Agent Console](#agent-console)
- - [Agent tester](#agent-tester)
-- [Licence](#licence)
-
-## Background
-
-The main concept of the **IoT Agent node library** is to provide a common framework for provisioning IoT devices,
-allowing each individual IoT Agent to access standardized mapping data for devices and to offer a series common utility
-functions.
-
-- For southbound communications, the library listens to changes in context entities and raises callbacks for the IoT
- Agent to process.
-- For northbound communications, the library offers an interface which accepts structured input data so that all NGSI
- communications are left to the library.
+- For the device communication (southbound), the library listens to changes in context entities and raises callbacks
+ for the IoT Agent to process. It also handles the commands sent by the Context Broker to the devices.
+- For the context broker communications (northbound), the library offers an interface which persists data from the
+ device in the Context Broker and accepts NGSI data from the Context Broker to be sent to the device.
- Standardized OAuth2-based security is available to enable each IoT Agent to connect to several common Identity
Managers (e.g. Keystone and Keyrock) so that communications can be restricted to trusted components.
-- A series of additional plugins are offered where necessary to allow for expression parsing, attribute aliasing and
- the processing of timestamp metadata.
-
-Each individual IoT Agent offers is driven by a `config.js` configuration file contains explicit custom settings based
-on the protocol and payload the IoT Agent is translating. It will also contain some common flags for common
-functionality provided by the IoT Agent node lin (e.g. for contecting to a conext broker or for authentication). The
-**IoT Agent node library** offers a standard API for provisioning devices and ensures that each IoT Agent can configure
-its device communications using a common vocabulary regardless of the payload, syntax or transport protocol used by the
-device itself.
-
-## Install
-
-The **IoT Agent node library** is not a standalone product and should be added as a dependency to `package.json` of the
-IoT Agent
-
-```json
-...
-"dependencies": {
- "iotagent-node-lib": "*",
-}
-```
-
-In order to use the library within your own IoT Agent, you must first you require it before use:
-
-```javascript
-const iotagentLib = require('iotagent-node-lib');
-```
-
-Information about how to configure the Library can be found at the corresponding section of the
-[Installation & Administration Guide](doc/installationguide.md).
-
-## Usage
-
-This library has no packaging or build processes. The [Getting Started](doc/getting-started.md) is a good place to
-start. Usage of the library is explained in the [User & Programmers Manual](doc/usermanual.md).
-
-- Details of the architecture of an IoT Agent be found [here](doc/architecture.md).
-- Further Advanced topics can be found [here](doc/advanced-topics.md).
-- The following features are listed as [deprecated](doc/deprecated.md).
-
-## API
-
-The **IoT Agent node library** offers a simple REST API which provides common functionality to access, provision and
-decommission devices. [API](doc/api.md).
-
-## Testing
-
-Contributions to development can be found [here](doc/development.md) - additional contributions are welcome.
-
-### Agent Console
-
-A command-line client to experiment with the library is packed with it. The command-line client can be started using the
-following command:
-
-```console
-bin/agentConsole.js
-```
-
-The client offers an API similar to the one offered by the library: it can start and stop an IoT agent, register and
-unregister devices, send measures mimicking the device and receive updates of the device data. Take into account that,
-by default, the console uses the same `config.js` file than the IoT Agent.
-
-The command-line client creates a console that offers the following options:
-
-```text
-stressInit
-
- Start recording a stress batch.
-
-stressCommit
-
- Executes the recorded batch as many times as requested, with delay (ms) between commands.
- The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
- parameter indicates the mean of the random initial waiting times for each agent.
-
-exit
-
- Exit from the command-line.
-
-start
-
- Start the IoT Agent
-
-stop
-
- Stop the IoT Agent
-
-register
-
- Register a new device in the IoT Agent. The attributes to register will be extracted from the
- type configuration
-
-unregister
-
- Unregister the selected device
-
-showConfig
-
- Show the current configuration file
-
-config
-
- Change the configuration file to a new one
-
-updatevalue
-
- Update a device value in the Context Broker. The attributes should be triads with the following
- format: "name/type/value" sepparated by commas.
-listdevices
-
- List all the devices that have been registered in this IoT Agent session
-```
-
-### Agent tester
-
-#### Command-line testing
-
-The library also offers a Context Broker and IoT Agent client that can be used to:
-
-- Simulate operations to the Context Broker used by the IoT Agent, triggering Context Provider forwardings for lazy
- attributes and checking the appropriate values for active ones.
-- Simulate operations to the Device Provisioning API and Configuration API of the IoT Agent.
-
-The tester can be started with the following command, from the root folder of the project:
-
-```console
-bin/iotAgentTester.js
-```
-
-From the command-line, the `help` command can be used to show a description of the currently supported features. These
-are the following:
-
-```text
-stressInit
-
- Start recording a stress batch.
-
-stressCommit
-
- Executes the recorded batch as many times as requested, with delay (ms) between commands.
- The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
- parameter indicates the mean of the random initial waiting times for each agent.
-
-exit
-
- Exit from the command-line.
-
-update
-
- Update the values of the defined set of attributes, using the following format: name#type=value(|name#type=value)*
-
-append
-
- Append a new Entity with the defined set of attributes, using the following format: name:type=value(,name:type=value)*
-
-query
-
- Get all the information on the selected object.
-
-queryAttr
-
- Get information on the selected object for the selected attributes.
-
-discover
-
- Get all the context providers for a entity and type.
-
-configCb
-
- Config a new host and port for the remote Context Broker.
-
-showConfigCb
-
- Show the current configuration of the client for the Context Broker.
-
-configIot
-
- Config a new host and port for the remote IoT Agent.
-
-showConfigIot
-
- Show the current configuration of the client for the IoT Agent.
-
-provision
-
- Provision a new device using the Device Provisioning API. The device configuration is
- read from the file specified in the "filename" parameter.
-
-provisionGroup
-
- Provision a group of devices with the selected template, taking the information needed to
- fill the template from a CSV with two columns, DEVICE_ID and DEVICE_NAME. The third parameter, type
- will be used to replace the DEVICE_TYPE field in the template. All the devices will be provisioned
- to the same IoT Agent, once the templates have been fulfilled.
-
-listProvisioned
-
- List all the provisioned devices in an IoT Agent.
-
-removeProvisioned
-
- Remove the selected provisioned device from the IoT Agent, specified by its Device ID.
-
-addGroup
-
- Add a new device group to the specified IoT Agent through the Configuration API. The
- body is taken from the file specified in the "filename" parameter.
-
-listGroups
-
- List all the device groups created in the selected IoT Agent for the configured service
-
-removeGroup
+This project is part of [FIWARE](https://www.fiware.org/). For more information check the FIWARE Catalogue entry for the
+[IoT Agents](https://github.com/Fiware/catalogue/tree/master/iot-agents).
- Remove the device group corresponding to the current configured subservice.
+| :books: [Documentation](https://iotagent-node-lib.rtfd.io) | :mortar_board: [Academy](https://fiware-academy.readthedocs.io/en/latest/iot-agents/idas) | :dart: [Roadmap](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/roadmap.md) |
+| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
-authenticate
+## Content
- Authenticates to the given authentication server, and use the token in subsequent requests.
+- [Documentation](#documentation)
+ - [User documentation](#user-documentation)
+ - [Development documentation](#development-documentation)
+- [IoT Agents available](#iot-agents-available)
+- [Install and usage](#install-and-usage)
+- [License](#license)
-setProtocol
+## Documentation
- Sets the protocol to use in the requests (http or https). Defaults to http.
+This repository contains the common user documentation across all IoT Agents. For particular documentation, you can
+check the specific documentation for each IoT Agent. You can see the a list of available IoT Agents in the
+[IoT Agents available](#iot-agents-available) section.
-configMigration
+### User documentation
- Sets the configuration for a migration between a C++ IoTA and a Node.js one.
+- [Getting started](doc/getting-started.md)
+- [IoT Agent API](doc/api.md)
+- [Administration manual](doc/admin.md)
+- [Deprecated features](doc/deprecated.md)
+- [Roadmap](doc/roadmap.md)
-showConfigMigration
+### Development documentation
- Shows the current migration configuration.
+- [Development manual](doc/devel/development.md)
+- [Contributing guide](doc/devel/contribution-guidelines.md)
+- [Architecture](doc/devel/architecture.md)
+- [North Port - NGSI Interactions](doc/devel/northboundinteractions.md)
-addProtocols
+## IoT Agents available
- Add a protocol translation table, in the following format:
- protocolOrigin1=protocolTarget1;protocolOrigin2=protocolTarget2...
+The following IoT Agents using the **IoT Agent Node Lib** are available:
+- [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json) - a bridge between HTTP/MQTT messaging (with a JSON
+ payload) and NGSI
+- [IoT Agent Ultralight](https://github.com/telefonicaid/iotagent-ul) - a bridge between HTTP/MQTT messaging
+- [IoT Agent LWM2M](https://github.com/telefonicaid/lightweightm2m-iotagent) - a bridge between the
+ [Lightweight M2M](https://www.omaspecworks.org/what-is-oma-specworks/iot/lightweight-m2m-lwm2m/) protocol and NGSI
+ (with an UltraLight2.0 payload) and NGSI
+- [IoT Agent for Sigfox](https://github.com/telefonicaid/sigfox-iotagent) - a bridge between the
+ [Sigfox](https://www.sigfox.com/en) protocol and NGSI
+- [IoT Agent for LoRaWAN](https://github.com/Atos-Research-and-Innovation/IoTagent-LoRaWAN) - a bridge between the
+ [LoRaWAN](https://www.thethingsnetwork.org/docs/lorawan/) protocol and NGSI
+- [IoT Agent for OPC-UA](https://github.com/Engineering-Research-and-Development/iotagent-opcua) - a bridge between
+ the [OPC Unified Architecture](http://www.opcua.us/) protocol and NGSI
+- [IoT Agent for ISOXML](https://github.com/FIWARE/iotagent-isoxml) - a bridge between the ISOXML/ADAPT protocol for
+ agricultural machinery and NGSI
-migrate
+## Install and usage
- Migrate all the devices and services for the selected service and subservice into the
- specified Mongo database. To perform the migration for all the services or all the
- subservices, use the "*" value.
-```
+The **IoT Agent node library** is not a standalone product. If you plan to install and use any of the IoT Agents
+available, you should follow the installation instructions for each IoT Agent (find the link in the
+[previous section](#iot-agents-available)). You can find the common API provided by the **IoT Agent node library** under
+[API](doc/api.md) documentation.
-The agent session stores transient configuration data about the target Context Broker and the target IoT Agent. This
-configuration is independent, and can be checked with the `showConfigCb` and `showConfigIot` commands, respectively.
-Their values can be changed with the `configCb` and `configIot` commands respectively. The new configurations will be
-deleted upon startup.
+If you plan to use the IoT Agent node library in your own project or IoT Agent, you should follow the
+[Developer manual](doc/devel/development.md), which includes the installation instructions and the usage of the library.
----
+Information about how to configure the IoT agent or the library can be found at the corresponding section of the
+[Administration manual](doc/admin.md).
-## Licence
+## License
The IoT Agent Node Library is licensed under [Affero General Public License (GPL) version 3](./LICENSE).
diff --git a/config.js b/config.js
index 9b93b86ad..f551dc2f9 100644
--- a/config.js
+++ b/config.js
@@ -77,7 +77,10 @@ var config = {
providerUrl: 'http://192.168.56.1:4041',
deviceRegistrationDuration: 'P1M',
defaultType: 'Thing',
- appendMode: true
+ expressLimit: '1Mb',
+ useCBflowControl: false,
+ storeLastMeasure: false,
+ cmdMode: 'legacy'
};
module.exports = config;
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 000000000..387219f2c
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,17 @@
+# IoT Agent documentation
+
+## User documentation
+
+- [Getting started](getting-started.md)
+- [IoT Agent API](api.md)
+- [Administration manual](admin.md)
+- [Deprecated features](deprecated.md)
+- [Roadmap](roadmap.md)
+- [Data models](models/models.md)
+
+## Development documentation
+
+- [Development manual](devel/development.md)
+- [Contributing guide](devel/contribution-guidelines.md)
+- [Architecture](devel/architecture.md)
+- [North Port - NGSI Interactions](devel/northboundinteractions.md)
diff --git a/doc/admin.md b/doc/admin.md
new file mode 100644
index 000000000..5e5bb80c3
--- /dev/null
+++ b/doc/admin.md
@@ -0,0 +1,613 @@
+# Administration
+
+- [Configuration](#configuration)
+ - [Configuration parameters:](#configuration-parameters)
+ - [loglevel](#loglevel)
+ - [contextBroker](#contextbroker)
+ - [server](#server)
+ - [authentication](#authentication)
+ - [deviceRegistry](#deviceregistry)
+ - [mongodb](#mongodb)
+ - [iotManager](#iotmanager)
+ - [types](#types)
+ - [service](#service)
+ - [subservice](#subservice)
+ - [providerUrl](#providerurl)
+ - [iotaVersion](#iotaversion)
+ - [dieOnUnexpectedError](#dieonunexpectederror)
+ - [timestamp](#timestamp)
+ - [defaultResource](#defaultresource)
+ - [defaultKey](#defaultkey)
+ - [componentName](#componentname)
+ - [pollingExpiration](#pollingexpiration)
+ - [pollingDaemonFrequency](#pollingdaemonfrequency)
+ - [multiCore](#multicore)
+ - [fallbackTenant](#fallbacktenant)
+ - [fallbackPath](#fallbackpath)
+ - [explicitAttrs](#explicitattrs)
+ - [defaultEntityNameConjunction](#defaultentitynameconjunction)
+ - [relaxTemplateValidation](#relaxtemplatevalidation)
+ - [Configuration using environment variables](#configuration-using-environment-variables)
+- [Logs](#logs)
+ - [Errors](#errors)
+- [Alarms](#alarms)
+
+The **IoT Agent node library** is not a standalone product and should be added as a dependency to `package.json` of the
+project is going to be used in. The library is published in the npm repository, so it can be added to the project by
+adding the following line to the `package.json` file:
+
+```json
+...
+"dependencies": {
+ "iotagent-node-lib": "x.y.z",
+}
+```
+
+wher `x.y.z` is an actual version number.
+
+As alternative, you can use the master branch as dependency. In this case, you will be using the latest version of the
+code but note that some instability could exist (as the code in master is work in progress until the next version is
+closed).
+
+```json
+...
+"dependencies": {
+ "iotagent-node-lib": "https://github.com/telefonicaid/iotagent-node-lib.git#master",
+}
+```
+
+In order to use the library within your own IoT Agent, you must first you require it before use:
+
+```javascript
+const iotagentLib = require('iotagent-node-lib');
+```
+
+## Configuration
+
+The `activate()` function that starts the IoT Agent receives as single parameter with the configuration for the IoT
+Agent. The Agent Console reads the same configuration from the `config.js` file or from the environment variables. The
+configuration parameters work in the same way in both cases but the environment variables have precedence over the
+`config.js` file. The following sections describe the configuration parameters that can be used in the IoT Agent.
+
+### Configuration parameters:
+
+These are the parameters that can be configured in the global section:
+
+#### `loglevel`
+
+It is the minimum log level to log. May take one of the following values: DEBUG, INFO, ERROR, FATAL. E.g.: 'DEBUG'.
+
+#### `contextBroker`
+
+It configures the connection parameters to stablish a connection to the Context Broker (host and port). E.g.:
+
+```javascript
+{
+ host: '192.168.56.101',
+ port: '1026'
+}
+```
+
+- If you want to use **NGSI v2**:
+
+```javascript
+{
+ host: '192.168.56.101',
+ port: '1026',
+ ngsiVersion: 'v2'
+}
+```
+
+- If you want to use **NGSI-LD** (experimental):
+
+```javascript
+{
+ host: '192.168.56.101',
+ port: '1026',
+ ngsiVersion: 'ld',
+ jsonLdContext: 'http://context.json-ld' // or ['http://context1.json-ld','http://context2.json-ld'] if you need more than one
+}
+```
+
+Where `http://context.json-ld` is the location of the NGSI-LD `@context` element which provides additional information
+allowing the computer to interpret the rest of the data with more clarity and depth. Read the
+[JSON-LD specification](https://w3c.github.io/json-ld-syntax/#the-context) for more information.
+
+- If you want to support a "mixed" mode with both **NGSI-v2** and **NGSI-LD** (experimental):
+
+```javascript
+{
+ host: '192.168.56.101',
+ port: '1026',
+ ngsiVersion: 'mixed',
+ jsonLdContext: 'http://context.json-ld' // or ['http://context1.json-ld','http://context2.json-ld'] if you need more than one
+}
+```
+
+Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also
+be switched to **NGSI LD** at group or device provisioning time using the `ngsiVersion` field in the provisioning API.
+The `ngsiVersion` field switch may be added at either group or device level, with the device level overriding the group
+setting.
+
+#### `server`
+
+This parameter is used to create the Context Server (port where the IoT Agent will be listening as a Context Provider
+and base root to prefix all the paths). The `port` attribute is required. If no `baseRoot` attribute is used, '/' is
+used by default. E.g.:
+
+```javascript
+{
+ baseRoot: '/',
+ port: 4041
+}
+```
+
+When connected to an **NGSI-LD** context broker, an IoT Agent is able to indicate whether it is willing to accept `null`
+values and also whether it is able to process the **NGSI-LD** `datasetId` metadata element. Setting these values to
+`false` will cause the IoT Agent to return a 400 **Bad Request** HTTP status code explaining that the IoT Agent does not
+support nulls or multi-attribute requests if they are encountered. It is also possible to pass on attribute datatypes
+using `@type` or `valueType` if desired.
+
+```javascript
+{
+ baseRoot: '/',
+ port: 4041,
+ ldSupport : {
+ null: true,
+ datasetId: true,
+ datatype: 'valueType'
+ }
+}
+```
+
+#### `authentication`
+
+Stores the authentication data, for use in retrieving tokens for devices with a trust token (required in scenarios with
+security enabled in the Context Broker side). Currently, two authentication provider are supported: `keystone` and
+`oauth2`. Authentication need to be enabled by setting the field `enabled` to `true`. In `keystone` based
+authentication, the `trust` associated to the `device` or `deviceGroup` is a token representing a specific user and his
+rights on a given domain (i.e. combination of `fiware-service` and `fiware-servicepath`). The authentication process use
+the trust delegation workflow to check if the trust provided is valid, in which case return a `x-subject-token` that can
+be used to authenticate the request to the Context Broker. Required parameters are: the `url` of the keystone to be used
+(alternatively `host` and `port` but if you use this combination, the IoT Agent will assume that the protocol is HTTP),
+the `user` and `password` to which it is delegated the `trust` verification. E.g.:
+
+```javascript
+{
+ enabled: true,
+ url: 'https://localhost:5000',
+ type: 'keystone',
+ user: 'iotagent',
+ password: 'iotagent'
+}
+```
+
+In `oauth2` based authentication, two types of tokens can be used depending on the availability in the IDM to be used.
+On one hand, the `trust` associated to the `device` or `deviceGroup` is a `refresh_token` issued by a specific user for
+the Context Broker client. The authentication process uses the
+[`refresh_token` grant type](https://tools.ietf.org/html/rfc6749#section-1.5) to obtain an `access_token` that can be
+used to authenticate the request to the Context Broker. At the time being the assumption is that the `refresh_token` is
+a not expiring `offline_token` (we believe this is the best solution in the case of IoT Devices, since injecting a
+refresh token look may slow down communication. Still, the developer would be able to invalidate the refresh token on
+the provider side in case of security issues connected to a token). The code was tested using
+[Keycloak](http://www.keycloak.org), [Auth0](https://auth0.com) and [FIWARE Keyrock](https://github.com/ging/fiware-idm)
+(it may require customisation for other providers - while OAuth2 is a standard, not all implementations behave in the
+same way, especially as regards status codes and error messages). Required parameters are: the `url` of the OAuth 2
+provider to be used (alternatively `host` and `port` but if you use this combination, the IoT Agent will assume that the
+protocol is HTTP), the `tokenPath` to which the validation request should be sent
+(`/auth/realms/default/protocol/openid-connect/token` for Keycloak and Auth0, `/oauth2/token` for Keyrock), the
+`clientId` and `clientSecret` that identify the Context Broker, and the `header` field that should be used to send the
+authentication request (that will be sent in the form `Authorization: Bearer `). E.g.:
+
+```javascript
+{
+ enabled: true,
+ type: 'oauth2',
+ url: 'http://localhost:3000',
+ header: 'Authorization',
+ clientId: 'context-broker',
+ clientSecret: 'c8d58d16-0a42-400e-9765-f32e154a5a9e',
+ tokenPath: '/auth/realms/default/protocol/openid-connect/token'
+}
+```
+
+Nevertheless, this kind of authentication relying on `refresh_token` grant type implies that when the acces_token
+expires, it is needed to request a new one from the IDM, causing some overhead in the communication with the Context
+Broker. To mitigate this issue, FIWARE KeyRock IDM implements `permanent tokens` that can be retrieved using
+`scope=permanent`. With this approach, the IOTA does not need to interact with the IDM and directly include the
+`permanent token` in the header. In order to use this type of token, an additional parameter `permanentToken` must be
+set to `true` in the `authentication` configuration. An environment variable `IOTA_AUTH_PERMANENT_TOKEN` can be also
+used for the same purpose. For instance:
+
+```javascript
+{
+ type: 'oauth2',
+ url: 'http://localhost:3000',
+ header: 'Authorization',
+ clientId: 'context-broker',
+ clientSecret: '0c2492e1-3ce3-4cca-9723-e6075b89c244',
+ tokenPath: '/oauth2/token',
+ enabled: true,
+ permanentToken: true
+}
+```
+
+#### `deviceRegistry`
+
+Stores type of Device Registry to create. Currently, two values are supported: `memory` and `mongodb`. If the former is
+configured, a transient memory-based device registry will be used to register all the devices. This registry will be
+emptied whenever the process is restarted. If the latter is selected, a MongoDB database will be used to store all the
+device information, so it will be persistent from one execution to the other. Mongodb databases must be configured in
+the `mongob` section (as described bellow). E.g.:
+
+```javascript
+{
+ type: 'mongodb';
+}
+```
+
+#### `mongodb`
+
+It configures the MongoDB driver for those repositories with 'mongodb' type, using the `uri` parameter (which format is
+available in [this reference](http://mongodb.github.io/node-mongodb-native/driver-articles/mongoclient.html)).
+
+```javascript
+{
+ host: 'localhost',
+ port: '27017',
+ db: 'iotagent',
+ retries: 5,
+ retryTime: 5
+}
+```
+
+```javascript
+{
+ host: 'mongodb-0,mongodb-1,mongodb-2',
+ port: '27017',
+ db: 'iotagent',
+ replicaSet: 'rs0',
+ user: 'rootuser',
+ password: 'password',
+ authSource: 'admin',
+ ssl: true,
+ extraArgs: {
+ retryWrites: true,
+ readPreference: 'nearest',
+ w: 'majority'
+ },
+ retries: 5,
+ retryTime: 5
+}
+```
+
+#### `iotManager`
+
+This parameter configures all the information needed to register the IoT Agent in the IoTManager. If this section is
+present, the IoTA will try to register to a IoTAM in the `host`, `port` and `path` indicated, with the information
+configured in the object. The IoTAgent URL that will be reported will be the `providedUrl` (described below) with the
+added `agentPath`:
+
+```javascript
+{
+ host: 'mockediotam.com',
+ port: 9876,
+ path: '/protocols',
+ protocol: 'GENERIC_PROTOCOL',
+ description: 'A generic protocol',
+ agentPath: '/iot'
+}
+```
+
+#### `types`
+
+This parameter includes additional groups configuration as described into the
+[Config group API](api.md#config-group-api) section.
+
+#### `service`
+
+Default service for the IoT Agent. If a device is being registered, and no service information comes with the device
+data, and no service information is configured for the given type, the default IoT agent service will be used instead.
+E.g.: 'smartGondor'.
+
+#### `subservice`
+
+default subservice for the IoT Agent. If a device is being registered, and no subservice information comes with the
+device data, and no subservice information is configured for the given type, the default IoT agent subservice will be
+used instead. E.g.: '/gardens'.
+
+#### `providerUrl`
+
+URL to send in the Context Provider registration requests. Should represent the external IP of the deployed IoT Agent
+(the IP where the Context Broker will redirect the NGSI requests). E.g.: 'http://192.168.56.1:4041'.
+
+#### `iotaVersion`
+
+indicates the version of the IoTA that will be displayed in the about method (it should be filled automatically by each
+IoTA).
+
+#### `dieOnUnexpectedError`
+
+if this flag is activated, the IoTAgent will not capture global exception, thus dying upon any unexpected error.
+
+#### `timestamp`
+
+if this flag is activated:
+
+- For NGSI-v2, the IoT Agent will add a `TimeInstant` metadata attribute to all the attributes updated from device
+ information. This flag is overwritten by `timestamp` flag in group or device
+- With NGSI-LD, the standard `observedAt` property-of-a-property is created instead.
+
+#### `defaultResource`
+
+default string to use as resource for the registration of new config groups (if no resource is provided).
+
+#### `defaultKey`
+
+default string to use as API Key for devices that do not belong to a particular Configuration.
+
+#### `componentName`
+
+default string identifying the component name for this IoT Agent in the logs.
+
+#### `pollingExpiration`
+
+expiration time for commands waiting in the polling queue in miliseconds. If a command has been in the queue for this
+amount of time without being collected by the device, the expiration daemon will reclaim it. This attribute is optional
+(if it doesn't exist, commands won't expire).
+
+#### `pollingDaemonFrequency`
+
+time between collection of expired commands in milliseconds. This attribute is optional (if this parameter doesn't exist
+the polling daemon won't be started).
+
+#### `multiCore`
+
+When enabled, the IoT Agents runs in multi-thread environment to take advantage of multi-core systems. It allows two
+values `true` or `false`. This attribute is optional with default to false, which means that the IoTAgent runs in a
+single thread. For more details about multi-core functionality, please refer to the
+[Cluster](https://nodejs.org/api/cluster.html) module in Node.js and
+[this section](devel/development.md#iot-agent-in-multi-thread-mode) of the library documentation.
+
+#### `fallbackTenant`
+
+For Linked Data Context Brokers which do not support multi-tenancy, this provides an alternative mechanism for supplying
+the `NGSILD-Tenant` header. Note that NGSILD-Tenant has not yet been included in the NGSI-LD standard (it has been
+proposed for the next update of the standard, but the final decision has yet been confirmed), take into account it could
+change. Note that for backwards compatibility with NGSI v2, the `fiware-service` header is already used as alternative
+if the `NGSILD-Tenant` header is not supplied.
+
+#### `fallbackPath`
+
+For Linked Data Context Brokers which do not support a service path, this provides an alternative mechanism for suppling
+the `NGSILD-Path` header. Note that for backwards compatibility with NGSI v2, the `fiware-servicepath` header is already
+used as alternative if the `NGSILD-Path` header is not supplied. Note that NGSILD-Path has not yet been included in the
+NGSI-LD standard (it has been proposed for the next update of the standard, but the final decision has yet been
+confirmed), take into account it could change
+
+#### `explicitAttrs`
+
+if this flag is activated, only provisioned attributes will be processed to Context Broker. This flag is overwritten by
+`explicitAttrs` flag in group or device provision. Additionally `explicitAttrs` can be used to define which meassures
+defined in JSON/JEXL array will be propagated to NGSI interface.
+
+#### `defaultEntityNameConjunction`
+
+the default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time;
+in that case `entity_name` is composed by `type` + `:` + `device_id`. Default value is `:`. This value is overwritten by
+`defaultEntityNameConjunction` in group provision.
+
+#### `relaxTemplateValidation`
+
+if this flag is activated, `objectId` attributes for incoming devices are not validated, and may exceptionally include
+characters (such as semi-colons) which are
+[forbidden](https://fiware-orion.readthedocs.io/en/master/user/forbidden_characters/index.html) according to the NGSI
+specification. When provisioning devices, it is necessary that the developer provides valid `objectId`-`name` mappings
+whenever relaxed mode is used, to prevent the consumption of forbidden characters.
+
+#### `expressLimit`
+
+IotAgents, as all Express applications that use the body-parser middleware, have a default limit to the request body
+size that the application will handle. This default limit for ioiotagnets are 1Mb. So, if your IotAgent receives a
+request with a body that exceeds this limit, the application will throw a “Error: Request entity too large”.
+
+The 1Mb default can be changed setting the `expressLimit` configuration parameter (or equivalente `IOTA_EXPRESS_LIMIT`
+environment variable).
+
+#### `storeLastMeasure`
+
+If this flag is activated, last measure arrived to Device IoTAgent without be processed will be stored in Device under
+`lastMeasure` field (composed of sub-fields `timestamp` and `measure` for the measure itself, in multi-measure format).
+This flag is overwritten by `storeLastMeasure` flag in group or device. This flag is disabled by default.
+
+For example in a device document stored in MongoDB will be extended with a subdocument named lastMeasure like this:
+
+```json
+{
+ "lastMeasure": {
+ "timestamp": "2025-01-09T10:35:33.079Z",
+ "measure": [
+ [
+ {
+ "name": "level",
+ "type": "Text",
+ "value": 33
+ }
+ ]
+ ]
+ }
+}
+```
+
+#### `useCBflowControl`
+
+If this flag is activated, when iotAgent invokes Context Broker will use
+[flowControl option](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/admin/perf_tuning.md#updates-flow-control-mechanism).
+This flag is overwritten by `useCBflowControl` flag in group or device. This flag is disabled by default.
+
+#### `cmdMode`
+
+Set command mode for the IoTAgent instance (it can be overriden by the `cmdMode` at group or device level). Possible
+values are:
+
+- `legacy` (used as default if this setting is not defined): IoTAgent commands will use Context Broker registers
+ mechanims.
+- `notification`: IoTAgent commands will use subscriptions to be notified for Context Broker commands.
+- `advancedNotification`: IoTAgent commands will use subscriptions to be notified for Context Broker commands (but in
+ a different way as in `notification` mode)
+
+Have a look to [this document](devel/northboundinteractions.md) for more detail on how this modes work.
+
+#### `healthCheck`
+
+Enable or not internal health check. Default false.
+
+#### `healthCheckInterval`
+
+Time interval (in milliseconds) between consecutive health checks. Default: 20000 (20 seconds)
+
+#### `healthCheckTimeout`
+
+Maximum time (in milliseconds) to wait for a single health check operation (HTTP request, MongoDB ping, MQTT connect)
+before considering it failed. Default: 1500 (1.5 seconds)
+
+#### `healthCheckDownAfterFails`
+
+Number of consecutive failed checks required before marking a connection as DOWN (ok: false). Until this threshold is
+reached, transient failures are tolerated. Default: 3
+
+#### `healthCheckConsiderHttpResponseUp`
+
+Consider an Http response code minor than 500 as endpoint is working. Default false, an http response code minor
+than 400.
+
+### Configuration using environment variables
+
+Some of the configuration parameters can be overriden with environment variables, to ease the use of those parameters
+with container-based technologies, like Docker, Heroku, etc...
+
+The following table shows the accepted environment variables, as well as the configuration parameter the variable
+overrides.
+
+| Environment variable | Configuration attribute |
+| :----------------------------------------------- | :---------------------------------- |
+| IOTA_CB_URL | `contextBroker.url` |
+| IOTA_CB_HOST | `contextBroker.host` |
+| IOTA_CB_PORT | `contextBroker.port` |
+| IOTA_CB_NGSI_VERSION | `contextBroker.ngsiVersion` |
+| IOTA_NORTH_HOST | `server.host` |
+| IOTA_NORTH_PORT | `server.port` |
+| IOTA_LD_SUPPORT_DATA_TYPE | `server.ldSupport.datatype` |
+| IOTA_LD_SUPPORT_NULL | `server.ldSupport.null` |
+| IOTA_LD_SUPPORT_DATASET_ID | `server.ldSupport.datasetId` |
+| IOTA_PROVIDER_URL | `providerUrl` |
+| IOTA_AUTH_ENABLED | `authentication.enabled` |
+| IOTA_AUTH_TYPE | `authentication.type` |
+| IOTA_AUTH_HEADER | `authentication.header` |
+| IOTA_AUTH_URL | `authentication.url` |
+| IOTA_AUTH_HOST | `authentication.host` |
+| IOTA_AUTH_PORT | `authentication.port` |
+| IOTA_AUTH_USER | `authentication.user` |
+| IOTA_AUTH_PASSWORD | `authentication.password` |
+| IOTA_AUTH_CLIENT_ID | `authentication.clientId` |
+| IOTA_AUTH_CLIENT_SECRET | `authentication.clientSecret` |
+| IOTA_AUTH_TOKEN_PATH | `authentication.tokenPath` |
+| IOTA_AUTH_PERMANENT_TOKEN | `authentication.permanentToken` |
+| IOTA_REGISTRY_TYPE | `deviceRegistry.type` |
+| IOTA_LOG_LEVEL | `logLevel` |
+| IOTA_TIMESTAMP | `timestamp` |
+| IOTA_IOTAM_URL | `iotManager.url` |
+| IOTA_IOTAM_HOST | `iotManager.host` |
+| IOTA_IOTAM_PORT | `iotManager.port` |
+| IOTA_IOTAM_PATH | `iotManager.path` |
+| IOTA_IOTAM_AGENTPATH | `iotManager.agentPath` |
+| IOTA_IOTAM_PROTOCOL | `iotManager.protocol` |
+| IOTA_IOTAM_DESCRIPTION | `iotManager.description` |
+| IOTA_MONGO_URI | `mongodb.uri` |
+| IOTA_POLLING_EXPIRATION | `pollingExpiration` |
+| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` |
+| IOTA_MULTI_CORE | `multiCore` |
+| IOTA_JSON_LD_CONTEXT | `jsonLdContext` |
+| IOTA_FALLBACK_TENANT | `fallbackTenant` |
+| IOTA_FALLBACK_PATH | `fallbackPath` |
+| IOTA_EXPLICIT_ATTRS | `explicitAttrs` |
+| IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` |
+| IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` |
+| IOTA_EXPRESS_LIMIT | `expressLimit` |
+| IOTA_STORE_LAST_MEASURE | `storeLastMeasure` |
+| IOTA_CB_FLOW_CONTROL | `useCBflowControl` |
+| IOTA_CMD_MODE | `cmdMode` |
+| IOTA_HEALTH_CHECK | `healthCheck` |
+| IOTA_HEALTH_CHECK_INTERVAL | `healthCheckInterval` |
+| IOTA_HEALTH_CHECK_TIMEOUT | `healthCheckTimeout` |
+| IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS | `healthCheckDownAfterFails` |
+| IOTA_IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP | `healthCheckConsiderHttpResponseUp` |
+
+Note:
+
+- If you need to pass more than one JSON-LD context, you can define the IOTA_JSON_LD_CONTEXT environment variable as a
+ comma separated list of contexts (e.g. `'http://context1.json-ld,http://context2.json-ld'`)
+
+## Logs
+
+This section describes the logs that can be generated by the IoT Agent library. The IoT Agent library uses the following
+log levels:
+
+| Level | Description |
+| :------ | :-------------------------------------------------------------------------------- |
+| `DEBUG` | Used to log information useful for debugging. |
+| `INFO` | Used to log information about the normal operation of the IoT Agent library. |
+| `ERROR` | Used to log information about errors that may affect the IoT Agent library. |
+| `FATAL` | Used to log information about fatal errors that may affect the IoT Agent library. |
+
+Additionally, every error log has an associated error code that can be used to identify the error. The error codes are
+composed by a prefix and a number. The following table shows the prefixes used in the IoT Agent library:
+
+| Prefix | Type of operation |
+| :----------------- | :--------------------------------------------------------- |
+| `MONGODB` | Errors related with the MongoDB repository |
+| `IOTAM` | Errors related with the IoTA Manager |
+| `KEYSTONE` | Errors related with trust token retrieval |
+| `ORION` | Errors in Context Broker access |
+| `VALIDATION-FATAL` | Errors related with management of the Validation templates |
+
+### Errors
+
+| Error code | Error name | Description |
+| :--------------------- | :----------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `GENERAL-001` | Couldn't find callback in listDevices call. | Implies that the callback function was not found in the listDevices call. This error is thrown when the callback function is not provided in the listDevices call. |
+| `MONGODB-001` | Error trying to connect to MongoDB: %s | Implies there has been an error connecting with the DB. The component will automatically retry from this error, but it may be a sign of connectivity problems between the DB and the component. If the connection cannot be restablished from this error, a MONGODB-002 error will be raised. |
+| `MONGODB-002` | Error found after [%d] attempts: %s | Indicates that it was impossible to establish a connection to the MongoDB cluster, even after retrying N times. This could be caused by a connectivity problem with the MongoDB machine, a problem in the MongoDB cluster, or a misconfiguration of the IoTA Manager. Check the conectivity, the state of the MongoDB cluster and the Mongo configuration data. |
+| `MONGODB-003` | No host found for MongoDB driver. | This error will thrown if MongoDB is selected as the configured repository for data but some information is missing in the configuration file. Check the configuration file and add all the required information. |
+| `MONGODB-004` | MongoDB connection was lost. | Indicates that it was impossible to reestablish the connection with the MongoDB server after retrying N times. This could be caused by a connectivity problem with the MongoDB machine or by changes on the configuration of the MongoDB server done while the IoT Agent was running. This error is only thrown when using a single MongoDB instance or when using sharding but just a single mongos proxy. When using MongoDB instances using replica sets or multiple mongos servers, the IoT Agent will retry connecting forever alternating between the different nodes. |
+| `IOTAM-001` | Error updating information in the IOTAM. Status Code [%d] | The IoT Agent could not contact the IoT Agent manager to update its information. This condition may indicate a lack of connectivity between machines or a problem in the IoT Agent Manager. The IoT Agent information in the IoT Agent Manager will be out-of-date until this problem is solved. |
+| `KEYSTONE-001` | Error retrieving token from Keystone: %s | There was connection error connecting with Keystone to retrieve a token. This condition may indicate a lack of connectivity between both machines or a problem with Keystone. |
+| `KEYSTONE-002` | Unexpected status code: %d | There was a problem retrieving a token from keystone that was not caused by connectivity errors. Check the Keystone log for errors and the security configuration in the IoTAgent. This may also be caused by a wrong trust token used by the user. |
+| `KEYSTONE-003` | Token missing in the response headers. | Authentication flow worked correctly, but the response headers did not include the expected header `x-subject-token`. Check the Keystone logs and configuration. |
+| `OAUTH2-001` | Error retrieving token from OAuth2 provider: %s | There was connection error connecting with OAuth2 provider to retrieve a token. This condition may indicate a lack of connectivity between both machines or a problem with OAuth2 provider. |
+| `OAUTH2-002` | Unexpected status code: %d | There was a problem retrieving a token from OAuth2 provider that was not caused by connectivity errors. Check the OAuth2 provider log for errors and the security configuration in the IoTAgent. This may also be caused by an invalid `refresh_token` used by the user. |
+| `OAUTH2-003` | Token missing in the response body | The JSON response body returned by the OAuth2 provider does not include a field `access_token`. Check the OAuth2 logs and configuration. |
+| `ORION-001` | Connection error creating initial entity in the Context Broker: %s | There was a connectivity error accessing Context Broker to create an initial entity (or the Context Broker was down). Check connectivity between the machines, the status of the remote Context Broker and the configuration of the IoTAgent. |
+| `ORION-002` | Connection error sending registrations to the Context Broker: %s | There was a connectivity error accessing Context Broker to register the IoTA as a Context Provider (or the Context Broker was down). Check connectivity between the machines, the status of the remote Context Broker and the configuration of the IoT Agent. |
+| `VALIDATION-FATAL-001` | Validation Request templates not found | Validation templates were not found. Check all the validation templates are properly located in the IoTAgent Library folder and that the file permissions are correct. |
+
+## Alarms
+
+The following table shows the alarms that can be raised in the IoTAgent library. All the alarms are signaled by a error
+log starting with the prefix "Raising [%s]:" (where %s is the alarm name). All the alarms are released by an info log
+with the prefix "Releasing [%s]". These texts appear in the `msg=` field of the generic log record format.
+
+| Alarm name | Severity | Description |
+| :--------------- | :----------- | :------------------------------------------------------- |
+| `MONGO-ALARM_XX` | **Critical** | Indicates an error in the MongoDB connectivity |
+| `ORION-ALARM` | **Critical** | Indicates a persistent error accesing the Context Broker |
+| `IOTAM-ALARM` | **Critical** | Indicates a persistent error accessing the IoTAM |
+
+while the 'Severity' criterium is as follows:
+
+- **Critical** - The system is not working
+- **Major** - The system has a problem that degrades the service and must be addressed
+- **Warning** - It is happening something that must be notified
+
+In order to identify the internal flow which origins a mongo alarm, there is a suffix `_XX` which identifies from `01`
+to `11` each flow.
diff --git a/doc/api.md b/doc/api.md
index 768c77a21..59d8eb3dd 100644
--- a/doc/api.md
+++ b/doc/api.md
@@ -8,8 +8,13 @@
- [IoT Agent information model](#iot-agent-information-model)
- [Config groups](#config-groups)
- [Devices](#devices)
- - [Entity attributes](#entity-attributes)
- - [Multientity support)](#multientity-support)
+ - [Uniqueness of groups and devices](#uniqueness-of-groups-and-devices)
+ - [Special measures and attributes names](#special-measures-and-attributes-names)
+ - [Device to NGSI Mapping](#device-to-ngsi-mapping)
+ - [Device autoprovision and entity creation](#device-autoprovision-and-entity-creation)
+ - [Entity creation when `cmdMode` is `notification`](#entity-creation-when-cmdmode-is-notification)
+ - [Entity Name expression support](#entity-name-expression-support)
+ - [Multientity support](#multientity-support)
- [Metadata support](#metadata-support)
- [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations)
- [Advice on Attribute definitions](#advice-on-attribute-definitions)
@@ -19,19 +24,23 @@
- [Measurement persistence options](#measurement-persistence-options)
- [Autoprovision configuration (autoprovision)](#autoprovision-configuration-autoprovision)
- [Explicitly defined attributes (explicitAttrs)](#explicitly-defined-attributes-explicitattrs)
- - [Configuring operation to persist the data in Context Broker (appendMode)](#configuring-operation-to-persist-the-data-in-context-broker-appendmode)
- - [Differences between `autoprovision`, `explicitAttrs` and `appendMode`](#differences-between-autoprovision-explicitattrs-and-appendmode)
+ - [Differences between `autoprovision`, `explicitAttrs`](#differences-between-autoprovision-explicitattrs)
- [Expression language support](#expression-language-support)
- [Examples of JEXL expressions](#examples-of-jexl-expressions)
- [Available functions](#available-functions)
- [Expressions with multiple transformations](#expressions-with-multiple-transformations)
+ - [Expression support in metadata](#expression-support-in-metadata)
- [Measurement transformation](#measurement-transformation)
- [Measurement transformation definition](#measurement-transformation-definition)
- [Measurement transformation execution](#measurement-transformation-execution)
+ - [Measurement transformation order](#measurement-transformation-order)
- [Multientity measurement transformation support (`object_id`)](#multientity-measurement-transformation-support-object_id)
- - [Timestamp Compression](#timestamp-compression)
- - [Timestamp Processing](#timestamp-processing)
- - [Bidirectionality plugin (bidirectional)](#bidirectionality-plugin-bidirectional)
+ - [Command execution](#command-execution)
+ - [Triggering commands](#triggering-commands)
+ - [Command reception](#command-reception)
+ - [Command confirmation](#command-confirmation)
+ - [TimeInstant and Timestamp flag](#timeinstant-and-timestamp-flag)
+ - [Multimeasure support](#multimeasure-support)
- [Overriding global Context Broker host](#overriding-global-context-broker-host)
- [Multitenancy, FIWARE Service and FIWARE ServicePath](#multitenancy-fiware-service-and-fiware-servicepath)
- [Secured access to the Context Broker](#secured-access-to-the-context-broker)
@@ -43,10 +52,10 @@
- [Config group API](#config-group-api)
- [Config group datamodel](#config-group-datamodel)
- [Config group operations](#config-group-operations)
- - [Retrieve config groups `GET /iot/services`](#retrieve-config-groups-get-iotservices)
- - [Create config group `POST /iot/services`](#create-config-group-post-iotservices)
- - [Modify config group `PUT /iot/services`](#modify-config-group-put-iotservices)
- - [Remove config group `DELETE /iot/services`](#remove-config-group-delete-iotservices)
+ - [Retrieve config groups `GET /iot/groups`](#retrieve-config-groups-get-iotgroups)
+ - [Create config group `POST /iot/groups`](#create-config-group-post-iotgroups)
+ - [Modify config group `PUT /iot/groups`](#modify-config-group-put-iotgroups)
+ - [Remove config group `DELETE /iot/groups`](#remove-config-group-delete-iotgroups)
- [Device API](#device-api)
- [Device datamodel](#device-datamodel)
- [Device operations](#device-operations)
@@ -55,12 +64,18 @@
- [Get device details `GET /iot/devices/:deviceId`](#get-device-details-get-iotdevicesdeviceid)
- [Modify device `PUT /iot/devices/:deviceId`](#modify-device-put-iotdevicesdeviceid)
- [Remove device `DELETE /iot/devices/:deviceId`](#remove-device-delete-iotdevicesdeviceid)
+ - [Batch Operations](#batch-operations)
+ - [Remove devices `POST /iot/op/delete`](#remove-devices-post-iotopdelete)
- [Miscellaneous API](#miscellaneous-api)
- [Log operations](#log-operations)
- [Modify Loglevel `PUT /admin/log`](#modify-loglevel-put-adminlog)
- - [Retrieve log level `PUT /admin/log`](#retrieve-log-level-put-adminlog)
+ - [Retrieve log level `GET /admin/log`](#retrieve-log-level-get-adminlog)
- [About operations](#about-operations)
- [List IoTA Information `GET /iot/about`](#list-iota-information-get-iotabout)
+ - [Metrics](#metrics)
+ - [Retrieve metrics `GET /metrics`](#retrieve-metrics-get-metrics)
+ - [Ready](#ready)
+ - [Retrieve ready `GET /ready`](#retrieve-ready-get-ready)
@@ -125,72 +140,116 @@ For every config group, the pair (resource, apikey) _must_ be unique (as it is u
which device). Those operations of the API targeting specific resources will need the use of the `resource` and `apikey`
parameters to select the appropriate instance.
-Config groups can be created with preconfigured sets of attributes, service information, security information and other
-parameters. The specific parameters that can be configured for a given config group are described in the
-[Config group datamodel](#config-group-datamodel) section.
+Config groups can be created with preconfigured sets of attributes, service and subservice information, security
+information and other parameters. The specific parameters that can be configured for a given config group are described
+in the [Config group datamodel](#config-group-datamodel) section.
### 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
relaying on the config group configuration. The specific parameters that can be configured for a given device are
described in the [Device datamodel](#device-datamodel) section.
-## Entity attributes
+If devices are not pre-registered, they will be automatically created when a measure arrives to the IoT Agent - this
+process is known as autoprovisioning. The IoT Agent will create an empty device with the group `apikey` and `type` - the
+associated document created in database doesn't include config group parameters (in particular, `timestamp`,
+`explicitAttrs`, `active` or `attributes`, `static` and `lazy` attributes and commands). The IoT Agent will also create
+the entity in the Context Broker if it does not exist yet.
-In the config group/device model there are four list of attributes with different purpose to configure how the
-information coming from the device is mapped to the Context Broker attributes:
+This behavior allows that autoprovisioned parameters can freely established modifying the device information after
+creation using the provisioning API. However, note that if a device (autoprovisioned or not) doesn't have these
+parameters defined at device level in database, the parameters are inherit from config group parameters.
+
+### Uniqueness of groups and devices
+
+Group uniqueness is defined by the combination of: resource and apikey. This is so because given a measure (identified
+by an apikey) there is no way to identify group related if apikey is not unique for all services and subservices.
+
+Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
+with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
+
+## Special measures and attributes names
+
+In case of arriving measures with name `id` or `type`, they are automatically transformed to `measure_id` and
+`measure_type` attributes at Context Broker update. The reason behind this is to avoid to collide with the original
+entity ID and type, as mechanism that enable store measure values from parameters called with the same name. It only
+applies to autoprovisioned attributes and is also available at JEXL context with the same name (`measure_id` or
+`measure_type`).
+
+In case of provisioning attributes using `id` or `type` as names (please don't do that ;), they are ignored.
+
+## Device to NGSI Mapping
+
+The way to map the information coming or going to the device to the NGSI attributes is defined in the group or device.
+It is possible to define the entity type and the entity ID that a device will use in the Context Broker. It can be
+configured for a single device in the device provisioning, or it can be defined for all the devices in a group.
+
+The entity type should be defined both in the group and in the device, but the entity name (entity ID) is not defined in
+the group. In that case, if there is no a existing device the same device ID, the entity name of the device generated
+will be a concatenation of the entity type and the device ID (I.E: `entityType:device_id`). If you need to generate the
+entity name differently, it is possible to define an expression to generate it, using the parameter `entityNameExp` in
+the group as described in the [Entity Name expression support](#entity-name-expression-support) section.
+
+It is also possible to configure how each of the measures obtained from the device is mapped to different attributes.
+The name and type of the attribute is configured by the user (globally for all the devices in the group or in a per
+device preprovisioning). Device measures can have four different behaviors:
- **`attributes`**: Are measures that are pushed from the device to the IoT agent. This measure changes will be sent
to the Context Broker as updateContext requests over the device entity. NGSI queries to the context broker will be
resolved in the Broker database. For each attribute, its `name` and `type` must be provided. Additional `metadata`
- is optional.
+ is optional. They are called internally as _active attributes_.
- **`lazy`**: Passive measures that are pulled from the device to the IoT agent. When a request for data from a lazy
- attribute arrives to the Context Broker, it forwards the request to the Context Provider of that entity, in this
- case the IoT Agent. The IoT Agent will then ask the device for the information needed, transform that information to
- a NGSI format and return it to the Context Broker. This operation will be synchronous from the customer perspective:
- the Context Broker won't return a response until the device has returned its response to the IoT Agent. For each
- attribute, its `name` and `type` must be provided.
+ attribute arrives to the Context Broker, it forwards the request to the IoT Agent (that behaves as NGSI Context
+ Provider for all the lazy attributes or commands). The IoT Agent will then ask the device for the information
+ needed, transform that information to a NGSI format and return it to the Context Broker. This operation will be
+ synchronous from the customer perspective: the Context Broker won't return a response until the device has returned
+ its response to the IoT Agent. For each attribute, its `name` and `type` must be provided. They are called
+ internally as _lazy attributes_.
- **`static`**: It is static attributes that are persisted in the Context Broker. They are not updated by the device,
but they can be modified by the user. They are useful to store information about the device that is not updated by
the device itself. For instance, a `location` static attribute is can be used to store the location of a fixed
device.
-- **`commands`**: Commands are actions that can be invoked in the device. They are similar to attributes, but they are
- not updated by the device. They are updated by the Context Broker, and the IoT Agent will be in charge of
- translating the updateContext request to the proper action in the device. Two additional attributes are created for
- each command: `status` and `info`. For each command, its `name` and `type` must be provided.
+- **`commands`**: Commands are actions that can be invoked in the device. They are entity attributes, but they are not
+ updated by the device, they are updated by the Context Broker. In this case, the interaction will begin by setting
+ an attribute in the device's entity, for which the IoT Agent will be regitered as Context Provider. The IoT Agent
+ will return an immediate response to the Context Broker, and will be held responsible of contacting the device to
+ perform the command itself using the device specific protocol. Special `status` and `info` attributes should be
+ update. For each command, its `name` and `type` must be provided. For further information, please refer to
+ [Command execution](#command-execution) section.
All of them have the same syntax, a list of objects with the following attributes:
- **object_id** (optional): name of the attribute as coming from the device.
-- **name** (mandatory): ID of the attribute in the target entity in the Context Broker.
+- **name** (mandatory): ID of the attribute in the target entity in the Context Broker. Note that `id` and `type` are
+ not valid attribute names at Context Broker. Thus, although a measure named `id` or `type` will not break the IoT
+ Agent, they are silently ignored and never progress toward Context Broker entities.
- **type** (mandatory): name of the type of the attribute in the target entity.
- **metadata** (optional): additional static metadata for the attribute in the target entity. (e.g. `unitCode`)
-Some transformation plugins also allow the use of the following optional fields:
+Some advanced features also allow the use of the following optional fields:
- **expression**: indicates that the value of the target attribute will not be the plain value or the measurement, but
an expression based on a combination of the reported values. See the
[Expression Language definition](#expression-language-support) for details
- **skipValue**: indicates that if the result of applying `expression` to a measure is equal to the value of
- `skipValue` then the attribute corresponding to the measure is not sent to CB. By default if `skipValue` is not
- defined then is considered as `null` (i.e. if the result of apply `expression` results in `null` then corresponding
- attribute is not sent to CB). It is only used if `expression` is provided (otherwise is ignored).
+ `skipValue` then the attribute corresponding to the measure is not sent to CB. In other words, this field **is not
+ an expression**, it is a value that is compared with the result of applying `expression` to a measure. By default if
+ `skipValue` is not defined then is considered as `null` (i.e. if the result of apply `expression` results in `null`
+ then corresponding attribute is not sent to CB). It is only used if `expression` is provided (otherwise is ignored).
- **entity_name**: the presence of this attribute indicates that the value will not be stored in the original device
entity but in a new entity with an ID given by this attribute. The type of this additional entity can be configured
with the `entity_type` attribute. If no type is configured, the device entity type is used instead. Entity names can
be defined as expressions, using the [Expression Language definition](#expression-language-support).
- **entity_type**: configures the type of an alternative entity.
-- **reverse**: add bidirectionality expressions to the attribute. See the **bidirectionality** transformation plugin
- in the [Data Mapping Plugins section](development.md#bidirectionality-plugin-bidirectional) for details.
Additionally for commands (which are attributes of type `command`) the following fields are optional:
@@ -201,6 +260,107 @@ Additionally for commands (which are attributes of type `command`) the following
particular IOTAs documentation for allowed values of this field in each case.
- **contentType**: `content-type` header used when send command by HTTP transport (ignored in other kinds of
transports)
+- **headers**: extra customer headers used when send command by HTTP transport (ignored in other kinds of transports)
+ Check full detail of these fields in
+ [comand-transformations](https://github.com/telefonicaid/iotagent-json/blob/master/docs/usermanual.md#commands-transformations)
+
+Note that, when information coming from devices, this means measures, are not defined neither in the group, nor in the
+device, the IoT agent will store that information into the destination entity using the same attribute name than the
+measure name, unless `explicitAttrs` is defined. Measures `id` or `type` names are invalid, and will be ignored.
+
+## Device autoprovision and entity creation
+
+For those agents that uses IoTA Node LIB version 3.4.0 or higher, you should consider that the entity is not created
+automatically when a device is created. This means that all entities into the Context Broker are created when data
+arrives from a device, no matter if the device is explicitly provisioned (via
+[device provisioning API](#create-device-post-iotdevices)) or autoprovisioned.
+
+If for any reason you need the entity at CB before the first measure of the corresponding device arrives to the
+IOTAgent, you can create it in advance using the Context Broker
+[NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md).
+
+## Entity creation when `cmdMode` is `notification`
+
+Even when an entity should not be created (see [above section](#device-autoprovision-and-entity-creation)), when the
+device uses `cmdMode` set to `notification` an entity is created. In particular:
+
+- An entity is created at device provision time
+- That entity is created with an attribute corresponding to each commands. The value of that attribute at creation
+ time is `null` (specifying in some way that the command has not been triggered yet). Note that if the attribute
+ doesn't have any command, the entity is not created (even if `cmdMode` is `notification`).
+
+For instance, if device has commands `ping` and `switch` the entity corresponding to that device will be created at
+provising time with the following attributes:
+
+```
+...
+{
+ "ping": {
+ "type": "command",
+ "value": null
+ },
+ "switch": {
+ "type": "command",
+ "value": null
+ },
+}
+```
+
+**NOTE:** `command` is the usual type for attributes associated to commands, but the one used at provisioning time
+(`"command": [ ...]` field) will be actually used.
+
+## Entity Name expression support
+
+By default, the entity name used to persist the device measures in the Context Broker can be defined in the device
+provisioning or, if the device is autoprovisioned, it is generated by the IoT Agent as a concatenation of the entity
+type and the device ID. If you need to generate the entity name differently, it is possible to define an expression to
+generate it, using the [Expression Language](#expression-language-support) through the `entityNameExp` field in the
+group.
+
+With this feature, the entity name can be generated dynamically based not only on the device ID and entity type, but
+also on the measures reported by the device or any other context information. The `entityNameExp` field is only
+available at the group level. **Important**: when using `entityNameExp`, the `entity_name` field in the device
+provisioning is ignored. This means that the entity name used to store the device information in the Context Broker is
+always generated by the `entityNameExp` expression. If you need to explicitly define the entity name for a particular
+device, you can include a particular condition in the `entityNameExp` expression to handle that case (e.g.
+`id == 'myDevice' ? 'myEntity' : entityType + ':' + id`).
+
+The following example shows how to define an entity name expression:
+
+```json
+{
+ "services": [
+ {
+ "resource": "/json",
+ "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
+ "entity_type": "TemperatureSensor",
+ "entityNameExp": "id + '__' + sn",
+ "attributes": [
+ {
+ "object_id": "t",
+ "name": "temperature",
+ "type": "Number"
+ },
+ {
+ "object_id": "sn",
+ "name": "serialNumber",
+ "type": "Text"
+ }
+ ]
+ }
+ ]
+}
+```
+
+As defined above, the `entityNameExp` is `id + '__' + sn` and it will generate the entity name by concatenating the
+device ID and the serial number reported by the device. For example, for a given measure with `id` equal to `dev123` and
+`sn` equal to `ABCDEF`, the resulting entity name will be `dev123__ABCDEF`.
+
+Note that, when using `entityNameExp`, the `entity_name` of the device provisioning is set to the result of the
+expression the first time the device is created. If the expression is modified later, the `entity_name` of the device
+provisioning will not be updated, but the value used to persist the device measures in the Context Broker will be the
+result of the new expression. This can lead to a situation where the `entity_name` of the device provisioning and the
+entity name used in the Context Broker are different.
## Multientity support
@@ -252,32 +412,37 @@ e.g.:
```json
{
- "entity_type": "Lamp",
- "resource": "/iot/d",
- "protocol": "PDI-IoTA-UltraLight",
-..etc
- "commands": [
- {"name": "on","type": "command"},
- {"name": "off","type": "command"}
- ],
- "attributes": [
- {"object_id": "s", "name": "state", "type":"Text"},
- {"object_id": "l", "name": "luminosity", "type":"Integer",
- "metadata":{
- "unitCode":{"type": "Text", "value" :"CAL"}
- }
+ "entity_type": "Lamp",
+ "resource": "/iot/d",
+ "protocol": "PDI-IoTA-UltraLight",
+ "commands": [
+ { "name": "on", "type": "command" },
+ { "name": "off", "type": "command" }
+ ],
+ "attributes": [
+ { "object_id": "s", "name": "state", "type": "Text" },
+ {
+ "object_id": "l",
+ "name": "luminosity",
+ "type": "Integer",
+ "metadata": {
+ "unitCode": { "type": "Text", "value": "CAL" }
+ }
}
- ],
- "static_attributes": [
- {"name": "category", "type":"Text", "value": ["actuator","sensor"]},
- {"name": "controlledProperty", "type": "Text", "value": ["light"],
- "metadata":{
- "includes":{"type": "Text", "value" :["state", "luminosity"]},
- "alias":{"type": "Text", "value" :"lamp"}
+ ],
+ "static_attributes": [
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
+ {
+ "name": "controlledProperty",
+ "type": "Text",
+ "value": ["light"],
+ "metadata": {
+ "includes": { "type": "Text", "value": ["state", "luminosity"] },
+ "alias": { "type": "Text", "value": "lamp" }
}
- },
- ]
- }
+ }
+ ]
+}
```
### NGSI-LD data and metadata considerations
@@ -360,15 +525,13 @@ used should be taken from those defined by
## Measurement persistence options
-There are 3 different options to configure how the IoTAgent stores the measures received from the devices, depending on
+There are 2 different options to configure how the IoTAgent stores the measures received from the devices, depending on
the following parameters:
- `autoprovision`: If the device is not provisioned, the IoTAgent will create a new device and entity for it.
- `explicitAttrs`: If the measure element (object_id) is not defined in the mappings of the device or config group
provision, the measure is stored in the Context Broker by adding a new attribute to the entity with the same name of
the undefined measure element.
-- `appendMode`: It configures the request to the Context Broker to update the entity every time a new measure arrives.
- It have implications depending if the entity is already created or not in the Context Broker.
### Autoprovision configuration (autoprovision)
@@ -376,7 +539,8 @@ By default, when a measure arrives to the IoTAgent, if the `device_id` does not
IoTA creates a new device and a new entity according to the config group. Defining the field `autoprovision` to `false`
when provisioning the config group, the IoTA to reject the measure at the southbound, allowing only to persist the data
to devices that are already provisioned. It makes no sense to use this field in device provisioning since it is intended
-to avoid provisioning devices (and for it to be effective, it would have to be provisional).
+to avoid provisioning devices (and for it to be effective, it would have to be provisional). Further information can be
+found in section [Devices](#devices)
### Explicitly defined attributes (explicitAttrs)
@@ -388,6 +552,9 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con
precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not
by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
+Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then
+`TimeInstant` is not propaged to CB.
+
The different possibilities are summarized below:
Case 1 (default):
@@ -396,7 +563,7 @@ Case 1 (default):
"explicitAttrs": false
```
-every measure will be propagated to NGSI interface.
+every measure will be propagated to NGSI interface, including all static attributes.
Case 2:
@@ -404,7 +571,14 @@ Case 2:
"explicitAttrs": true
```
-just measures defined in active, static (plus conditionally TimeInstant) will be propagated to NGSI interface.
+In this case, should only progress active and static attributes defined in the device or group provision (`TimeInstant`
+attribute will be also included if enabled), including also all static attributes. In other words, having
+`"explicitAttrs":true` would prevent the IoTA creating attributes into the related entity within the context broker from
+measures that are not explicitly defined in the device or group provision.
+
+Note that attributes defined in the provision that are not receiving a measure (or having a expression defined that is
+resulting `null`) will not progress (this means, the NGSI request to update the entity in the context broker is not
+going to include that attribute) unless `skipValue` is defined to other value than `null`
Case 3:
@@ -414,7 +588,11 @@ Case 3:
just NGSI attributes defined in the array (identified by their attribute names, not by their object_id, plus
conditionally TimeInstant) will be propagated to NGSI interface (note that in this case the value of `explicitAttrs` is
-not a JSON but a JEXL Array that looks likes a JSON).
+not a JSON but a JEXL Array that looks likes a JSON). Only static attributes included in that array will be propagated
+to NGSI interface.
+
+All attributes contained in the array must be defined as `attributes` or `static_attributes`. Not defined measures
+(`object_id`) will be dropped, even if they are defined in the `explicitAttrs` array.
Case 4:
@@ -424,7 +602,11 @@ Case 4:
just NGSI attributes defined in the array (identified by their attribute names and/or by their object_id) will be
propagated to NGSI interface (note that in this case the value of `explicitAttrs` is not a JSON but a JEXL Array/Object
-that looks likes a JSON). This is necessary when same attribute names are used within multiple entities.
+that looks likes a JSON). This is necessary when same attribute names are used within multiple entities. Only static
+attributes included in that array will be propagated to NGSI interface.
+
+Note that in the previous case show above, when selecting the object_id (with `{object_id:'active_id'}`), the attribute
+must be defined. In other words, it would not work if the attribute with the corresponding `object_id`, is not defined.
Case 5:
@@ -434,35 +616,13 @@ Case 5:
depending on the JEXL expression evaluation:
-- If it evaluates to `true` every measure will be propagated to NGSI interface (as in case 1)
-- If it evaluates to `false` just measures defined in active, static (plus conditionally TimeInstant) will be
+- If it evaluates to `false` every measure will be propagated to NGSI interface (as in case 1)
+- If it evaluates to `true` just measures defined in active, static (plus conditionally TimeInstant) will be
propagated to NGSI interface (as in case 2)
- If it evaluates to an array just measures defined in the array (identified by their attribute names, not by their
object_id) will be will be propagated to NGSI interface (as in case 3)
-### Configuring operation to persist the data in Context Broker (appendMode)
-
-This is a flag that can be enabled by activating the parameter `appendMode` in the configuration file or by using the
-`IOTA_APPEND_MODE` environment variable (more info
-[here](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/installationguide.md)). If this flag is
-activated (its default behaviour), the update requests to the Context Broker will be performed always with APPEND type,
-instead of the default UPDATE. This have implications in the use of attributes with Context Providers, so this flag
-should be used with care.
-
-When running the agent using `appendMode=false`, if sending measures that are not included in the config group (as
-active measures), the IoT Agent returns a `422 Unprocessable Entity` code with the following message:
-
-```json
-{
- "name": "ENTITY_GENERIC_ERROR",
- "message": "Error accesing entity data for device: deviceType:dev of type: deviceType"
-}
-```
-
-Additionally, the agent creates the device and the corresponding entity in the broker if `autoprovision==true` (default
-behaviour).
-
-### Differences between `autoprovision`, `explicitAttrs` and `appendMode`
+### Differences between `autoprovision`, `explicitAttrs`
Since those configuration parameters are quite similar, this section is intended to clarify the relation between them.
@@ -474,16 +634,6 @@ related to the **southbound**.
What `explicitAttrs` does is to filter from the southbound the parameters that are not explicitly defined in the device
provision or config group. That also would avoid propagating the measures to the Context Broker.
-The default way the agent updates the information into the Context Broker is by using an update request. If
-`appendMode=true` (the default behaviour), the IoTA will use an append request instead of an update one. This means it
-will store the attributes even if they are not present in the entity. This seems the same functionality that the one
-provided by `autoprovision`, but it is a different concept since the scope of this config is to setup how the IoT
-interacts with the context broker, this is something related to the **northbound**.
-
-Note that, even creating a config group with `autoprovision=true` and `explicitAttrs=true`, if you do not provision
-previously the entity in the Context Broker (having all attributes to be updated), it would fail if `appendMode=false`.
-For further information check the issue [#1301](https://github.com/telefonicaid/iotagent-node-lib/issues/1301).
-
## Expression language support
The IoTAgent Library provides an expression language for measurement transformation and other purposes. This expression
@@ -493,9 +643,11 @@ really useful when you need to adapt measure (for example, to change the units,
of expression in the IoT Agent are:
- [Measurement transformation](#measurement-transformation).
+- [Metadata](#expression-support-in-metadata)
- Commands payload transformation (push and pull).
- Auto provisioned devices entity name. It is configured at config Group level by setting the `entityNameExp`
- parameter. It defines an expression to generate the Entity Name for autoprovisioned devices.
+ parameter. It defines an expression to generate the Entity Name for autoprovisioned devices. More information in the
+ [Entity Name expression support](#entity-name-expression-support) section.
- Dynamic `endpoint` definition. Configured at device level, it defines where the device listen for push http
commands. It can be either a static value or an expression.
@@ -505,9 +657,19 @@ expression. In all cases the following data is available to all expressions:
- `id`: device ID
- `entity_name`: NGSI entity Name (principal)
- `type`: NGSI entity type (principal)
-- `service`: device service
-- `subservice`: device subservice
+- `service`: device service (`Fiware-Service`)
+- `subservice`: device subservice (`Fiware-ServicePath`)
- `staticAttributes`: static attributes defined in the device or config group
+- `oldCtxt`: previous JEXL context (related to last processed measure)
+
+Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
+(`expression`) the following is available in the **context** used to evalute:
+
+- measures, as ``
+- metadata (both for attribute measurement in the case of NGSI-v2 measurements and static attribute) are available in
+ the **context** under the following convention: `metadata..` or
+ `metadata..` in a similar way of defined for
+ [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support)
### Examples of JEXL expressions
@@ -553,50 +715,54 @@ to incorporate new transformations from the IoT Agent configuration file in a fa
Current common transformation set:
-| JEXL Transformation | Equivalent JavaScript Function |
-| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
-| jsonparse: (str) | `JSON.parse(str);` |
-| jsonstringify: (obj) | `JSON.stringify(obj);` |
-| indexOf: (val, char) | `String(val).indexOf(char);` |
-| length: (val) | `String(val).length;` |
-| trim: (val) | `String(val).trim();` |
-| substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
-| addreduce: (arr) | arr.reduce((i, v) | i + v)); |
-| lengtharray: (arr) | `arr.length;` |
-| typeof: (val) | `typeof val;` |
-| isarray: (arr) | `Array.isArray(arr);` |
-| isnan: (val) | `isNaN(val);` |
-| parseint: (val) | `parseInt(val);` |
-| parsefloat: (val) | `parseFloat(val);` |
-| toisodate: (val) | `new Date(val).toISOString();` |
-| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
-| tostring: (val) | `val.toString();` |
-| urlencode: (val) | `encodeURI(val);` |
-| urldecode: (val) | `decodeURI(val);` |
-| replacestr: (str, from, to) | `str.replace(from, to);` |
-| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
-| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
-| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
-| split: (str, ch) | `str.split(ch);` |
-| joinarrtostr: (arr, ch) | `arr.join(ch);` |
-| concatarr: (arr, arr2) | `arr.concat(arr2);` |
-| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); |
-| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; |
-| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; |
-| slice: (arr, init, end) | `arr.slice(init,end);` |
-| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } |
-| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } |
-| touppercase: (val) | `String(val).toUpperCase()` |
-| tolowercase: (val) | `String(val).toLowerCase()` |
-| round: (val) | `Math.round(val)` |
-| floor: (val) | `Math.floor(val)` |
-| ceil: (val) | `Math.ceil(val)` |
-| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
-| gettime: (d) | `new Date(d).getTime()` |
-| toisostring: (d) | `new Date(d).toISOString()` |
-| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
-| now: () | `Date.now()` |
-| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
+| JEXL Transformation | Equivalent JavaScript Function |
+| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| jsonparse: (str) | `JSON.parse(str);` |
+| jsonstringify: (obj) | `JSON.stringify(obj);` |
+| indexOf: (val, char) | `String(val).indexOf(char);` |
+| length: (val) | `String(val).length;` |
+| trim: (val) | `String(val).trim();` |
+| substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
+| addreduce: (arr) | arr.reduce((i, v) | i + v)); |
+| lengtharray: (arr) | `arr.length;` |
+| typeof: (val) | `typeof val;` |
+| isarray: (arr) | `Array.isArray(arr);` |
+| isnan: (val) | `isNaN(val);` |
+| parseint: (val) | `parseInt(val);` |
+| parsefloat: (val) | `parseFloat(val);` |
+| toisodate: (val) | `new Date(val).toISOString();` |
+| timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
+| tostring: (val) | `val.toString();` |
+| urlencode: (val) | `encodeURI(val);` |
+| urldecode: (val) | `decodeURI(val);` |
+| replacestr: (str, from, to) | `str.replace(from, to);` |
+| replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
+| replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
+| replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
+| split: (str, ch) | `str.split(ch);` |
+| joinarrtostr: (arr, ch) | `arr.join(ch);` |
+| concatarr: (arr, arr2) | `arr.concat(arr2);` |
+| mapper: (val, values, choices) | choices[values.findIndex((target) | target == val)]); |
+| thmapper: (val, values, choices) | choices[values.reduce((acc,curr,i,arr) | (acc==0)||acc?acc:val<=curr?acc=i:acc=null,null)]; |
+| bitwisemask: (i,mask,op,shf) | (op==="&"?parseInt(i)&mask: op==="|"?parseInt(i)|mask: op==="^"?parseInt(i)^mask:i)>>shf; |
+| slice: (arr, init, end) | `arr.slice(init,end);` |
+| addset: (arr, x) | { return Array.from((new Set(arr)).add(x)) } |
+| removeset: (arr, x) | { let s = new Set(arr); s.delete(x); return Array.from(s) } |
+| touppercase: (val) | `String(val).toUpperCase()` |
+| tolowercase: (val) | `String(val).toLowerCase()` |
+| round: (val) | `Math.round(val)` |
+| floor: (val) | `Math.floor(val)` |
+| ceil: (val) | `Math.ceil(val)` |
+| tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
+| gettime: (d) | `new Date(d).getTime()` |
+| toisostring: (d) | `new Date(d).toISOString()` |
+| localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
+| localestringnumber: (n, locale, options) | `n.toLocaleStringNumber(locale, options)` |
+| now: () | `Date.now()` |
+| hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
+| valuePicker: (val,pick) | valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k) |
+| valuePickerMulti: (val,pick) | valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k) |
+| hash: (val) | Math.abs(String(val).split('').reduce((h, c) => ((h << 5) - h) + c.charCodeAt(0), 0)).toString(16).toUpperCase().padStart(8, '0') |
You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
test all the functions described above.
@@ -625,6 +791,50 @@ Another example using functions that return more than one value is the following
For a location value `"40.4165, -3.70256"`, the result of the previous expression will be `-3.70256`.
+### Expression support in metadata
+
+Metadata could also has `expression` like attributes in order to expand it:
+
+e.g.:
+
+```json
+{
+ "entity_type": "Lamp",
+ "resource": "/iot/d",
+ "protocol": "PDI-IoTA-UltraLight",
+ "commands": [
+ { "name": "on", "type": "command" },
+ { "name": "off", "type": "command" }
+ ],
+ "attributes": [
+ { "object_id": "s", "name": "state", "type": "Text" },
+ {
+ "object_id": "l",
+ "name": "luminosity",
+ "type": "Integer",
+ "metadata": {
+ "unitCode": { "type": "Text", "value": "CAL" }
+ }
+ }
+ ],
+ "static_attributes": [
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
+ {
+ "name": "controlledProperty",
+ "type": "Text",
+ "value": ["light"],
+ "metadata": {
+ "includes": { "type": "Text", "value": ["state", "luminosity"], "expression": "level / 100" },
+ "alias": { "type": "Text", "value": "lamp" }
+ }
+ }
+ ]
+}
+```
+
+Note that there is no order into metadata structure and there is no warranty about which metadata attribute expression
+will be evaluated first.
+
## Measurement transformation
The IoTAgent Library provides support for measurement transformation using a
@@ -697,6 +907,10 @@ will check which ones contain expressions whose variables are present in the rec
whose variables are covered, their expressions will be executed with the received values, and their values updated in
the Context Broker.
+If as a result of apply expression `undefined` or `null` value is obtained then any of them will not progress to entity
+attribute. Have into acount that some numeric operations results (like nonexistent \* 2) are a kind of null with a
+number type but NaN value, which will also not progress to entity attribute.
+
E.g.: if a device with the following provisioning information is created in the IoT Agent:
```json
@@ -772,9 +986,97 @@ following to CB:
[Interactive expression `spaces | trim`][5]
+### Measurement transformation order
+
+The IoTA executes the transformaion looping over the `attributes` provision field. Every time a new expression is
+evaluated, the JEXL context is updated with the expression result. The order defined in the `attributes` array is taken
+for expression evaluation. This should be considered when using **nested expressions**, that uses values calculated in
+other attributes.
+
+For example, let's consider the following provision for a device which send a measure named `level`:
+
+```json
+"attributes": [
+ {
+ "name": "correctedLevel",
+ "type": "Number",
+ "expression": "level * 0.897"
+ },
+ {
+ "name": "normalizedLevel",
+ "type": "Number",
+ "expression": "correctedLevel / 100"
+ }
+]
+```
+
+The expression for `correctedLevel` is evaluated first (using `level` measure as input). Next, the `normalizedLevel` is
+evaluated (using `correctedLevel` calculated attribute, just calculated before).
+
+Note that if we reserve the order, this way:
+
+```json
+"attributes": [
+ {
+ "name": "normalizedLevel",
+ "type": "Number",
+ "expression": "correctedLevel / 100"
+ },
+ {
+ "name": "correctedLevel",
+ "type": "Number",
+ "expression": "level * 0.897"
+ },
+]
+```
+
+It is not going to work. The first expression expects a `correctedLevel` which is neither a measure (remember the only
+measure sent by the device is named `level`) nor a previously calculated attribute. Thus, `correctedLevel` will end with
+a `null` value, so will not be part of the update request send to the Context Broker unless `skipValue` (check
+[Devices](#devices) section avobe) is defined with a different value thant the default one (`null`).
+
+In conclusion: **the order of attributes in the `attributes` arrays at provising time matters with regards to nested
+expression evaluation**.
+
+Let's consider the following example. It is an anti-pattern but it's quite illustrative on how ordering works:
+
+```json
+"attributes": [
+ {
+ "name": "a",
+ "type": "Number",
+ "expression": "b*10"
+ },
+ {
+ "name": "b",
+ "type": "Number",
+ "expression": "a*10"
+ }
+]
+```
+
+When receiving a measure with the following values:
+
+```json
+{
+ "a": 10,
+ "b": 20
+}
+```
+
+Then, as they are executed sequentially, the first attribute expression to be evaluated will be `a`, taking the value of
+the attribute `b` multiplied by 10, in this case, `200`. After that, the second attribute expression to be evaluated is
+the one holded by `b`. In this case, that attribute would take 10 times the value of `a`. In that case, since the JEXL
+context was updated with the lastest execution, the value of `b` will be `2000`, being update at Context Broker entity:
+
+```json
+ "a": {"value": 200, "type": "Number"},
+ "b": {"value": 2000, "type": "Number"}
+```
+
### Multientity measurement transformation support (`object_id`)
-To allow support for measurement transformation in combination with multi entity plugin, where the same attribute is
+To allow support for measurement transformation in combination with multi entity feature, where the same attribute is
generated for different entities out of different incoming attribute values (i.e. `object_id`), we introduced support
for `object_id` in the expression context.
@@ -859,66 +1161,401 @@ Will now generate the following NGSI v2 payload:
}
```
-## Timestamp Compression
+## TimeInstant and Timestamp flag
+
+As part of the device to entity mapping process, the IoT Agent creates and updates automatically a special timestamp
+attribute called `TimeInstant`. This timestamp is represented as two different properties of the mapped entity:
+
+- An attribute `TimeInstant` is added to updated entities in the case of NGSI-v2, which captures as an ISO8601
+ timestamp when the associated measurement was observed. With NGSI-LD, the Standard `observedAt` property is used
+ instead
+
+- With NGSI-v2, an attribute metadata named `TimeInstant` per active or lazy attribute mapped, which captures as an
+ ISO8601 timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the
+ Standard `observedAt` property-of-a-property is used instead.
+
+If timestamp is not explicily defined when sending the measures through the IoT Agent (for further details on how to
+attach timestamp information to the measures, please refer to the particular IoT Agent implementation documentation),
+the arrival time on the server when receiving the measurement will be used to generate a `TimeInstant` for both the
+entity attribute and the attribute metadata.
+
+This functionality can be turned on and off globaly through the use of the `timestamp` configuration flag or
+`IOTA_TIMESTAMP` variable as well as `timestamp` flag in device or group provision (in this case, the device or group
+level flag takes precedence over the global one). The default value is `true`.
-This functionality changes all the timestamp attributes found in the entity, and all the timestamp metadata found in any
-attribute, from the basic complete calendar timestamp of the ISO8601 (e.g.: 20071103T131805) to the extended complete
-calendar timestamp (e.g.: +002007-11-03T13:18). The middleware expects to receive the basic format in updates and return
-it in queries (and viceversa, receive the extended one in queries and return it in updates).
+The `timestamp` configuration value used, according to the priority:
-## Timestamp Processing
+1. The one defined at device level
+2. The one defined at group level (if not defined at device level)
+3. The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
+ group level or device level)
-The IOTA processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSI v2, the plugin
-adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the Standard
-`observedAt` property-of-a-property is used instead.
+Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
+the IoTA behaviour is described in the following table:
-## Bidirectionality plugin (bidirectional)
+| `timestamp` conf value | measure contains `TimeInstant` | Behaviour |
+| ---------------------- | ------------------------------ | ------------------------------------------------------ |
+| true | Yes | TimeInstant and metadata updated with measure value |
+| true | No | TimeInstant and metadata updated with server timestamp |
+| false | Yes | TimeInstant and metadata updated with measure value |
+| false | No | TimeInstant and metadata updated with server timestamp |
+| Not defined | Yes | TimeInstant and metadata updated with measure value |
+| Not defined | No | TimeInstant and metadata updated with server timestamp |
-This plugin allows the devices with composite values an expression to update the original values in the devices when the
-composite expressions are updated in the Context Broker. This behavior is achieved through the use of subscriptions.
+Some additional considerations to take into account:
-IoTAs using this plugins should also define a notification handler to handle incoming values. This handler will be
-intercepted by the plugin, so the mapped values are included in the updated notification.
+- If there is an attribute which maps a measure to `TimeInstant` attribute (after
+ [expression evaluation](#expression-language-support) if any is defined), then that value will be used as
+ `TimeInstant, overwriting the above rules specified in "Behaviour" column. Note that an expression in the could be
+ used in that mapping.
+- If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
+ measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
+ timestamp will take place).
+- the timestamp of different attributes belonging to the same measurement record may not be equal.
+- the arrival time and the measurement timestamp will not be the same in the general case (when explicitly defining
+ the timestamp in the measurement)
+- if `timezone` field is defined as part of the provisioning of the device or group, timestamp fields will be
+ generated using it. For instance, if `timezone` is set to `America/Los_Angeles`, a possible timestamp could be
+ `2025-08-05T00:35:01.468-07:00`. If `timezone` field is not defined, by default Zulu Time Zone (UTC +0) will be
+ used. Following the previous example, timestamp could be `2015-08-05T07:35:01.468Z`.
-When a device is provisioned with bidirectional attributes, the IoTAgent subscribes to changes in that attribute. When a
-change notification for that attribute arrives to the IoTA, it applies the transformation defined in the device
-provisioning payload to the notification, and calls the underlying notification handler with the transformed entity
-including the `value` along with any `metadata`, and in the case of an NGSI-LD bidirectional attribute a `datasetId` if
-provided.
+E.g.: in the case of a device that can take measurements every hour of both temperature and humidity and sends the data
+once every day, at midnight, the `TimeInstant` reported for each measurement will be the hour when that measurement was
+observed (e.g. 4:00 PM), while all the measurements will have an arrival time around midnight. If no timestamps were
+reported with such measurements, the `TimeInstant` attribute would take those values around midnight.
+
+## Multimeasure support
+
+A device could receive several measures at the same time.
+
+For example:
+
+```json
+[
+ {
+ "vol": 0
+ },
+ {
+ "vol": 1
+ },
+ {
+ "vol": 2
+ }
+]
+```
-The following `attributes` section shows an example of the plugin configuration (using `IOTA_AUTOCAST=false` to avoid
-translation from geo:point to geo:json)
+In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
```json
- "attributes": [
+{
+ "actionType": "append",
+ "entities": [
+ {
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 0
+ }
+ },
{
- "name":"location",
- "type":"geo:point",
- "expression": "latitude, longitude",
- "reverse": [
- {
- "object_id":"longitude",
- "type": "Number",
- "expression": "location | split(', ')[0] | parsefloat()"
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 1
+ }
+ },
+ {
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 1
+ }
+ }
+ ]
+}
+```
+
+Moreover if a multimeasure contains TimeInstant attribute, then CB update is sorted by attribute TimeInstant:
+
+For example:
+
+```json
+[
+ {
+ "vol": 0,
+ "TimeInstant": "2024-04-10T10:15:00Z"
+ },
+ {
+ "vol": 1,
+ "TimeInstant": "2024-04-10T10:10:00Z"
+ },
+ {
+ "vol": 2,
+ "TimeInstant": "2024-04-10T10:05:00Z"
+ }
+]
+```
+
+In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
+
+```json
+{
+ "actionType": "append",
+ "entities": [
+ {
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 2
},
- {
- "object_id":"latitude",
- "type": "Number",
- "expression": "location | split(', ')[1] | parsefloat()"
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-04-10T10:05:00Z"
+ }
+ },
+ {
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 1
+ },
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-04-10T10:10:00Z"
+ }
+ },
+ {
+ "id": "ws",
+ "type": "WeatherStation",
+ "vol": {
+ "type": "Number",
+ "value": 0
+ },
+ "TimeInstant": {
+ "type": "DateTime",
+ "value": "2024-04-10T10:15:00Z"
}
- ]
}
- ],
+ ]
+}
+```
+
+## Command execution
+
+This section reviews the end-to-end process to trigger and receive commands into devices. The URL paths and messages
+format is based on the [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json). It may differ in the case of
+using any other IoT Agent. In that case, please refer to the specific IoTA documentation.
+
+### Triggering commands
+
+This starts the process of sending data to devices. It starts by updating an attribute into the Context Broker defined
+as `command` in the [config group](#config-group-datamodel) or in the [device provision](#device-datamodel). Commands
+attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be
+sent (HTTP/MQTT) with the `transport` parameter at the provisioning process.
+
+**NOTE**: in `advancedNotification` mode the command is not triggered updating an attribute, but creating a command
+execution entity. However, this mode has not been implemented yet so details are to be clarified.
+
+For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in
+the Context Broker will send a command to your device. For instance, to send the `ping` command with value
+`Ping request` you could use the following operation in the Context Broker API:
+
+```
+PUT /v2/entities//attrs/ping?type=
+
+{
+ "value": "Ping request",
+ "type": "command"
+}
+
+```
+
+It is important to note that parameter `type`, with the entity type must be included.
+
+Context Broker API is quite flexible and allows to update an attribute in several ways. Please have a look to the
+[Orion API](<[http://telefonicaid.github.io/fiware-orion/api/v2/stable](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md)>)
+for details.
+
+**Important note**: don't use operations in the NGSI API with creation semantics. Otherwise, the entity/attribute will
+be created locally to Context Broker and the command will not progress to the device (and you will need to delete the
+created entity/attribute if you want to make it to work again). Thus, the following operations _must not_ be used:
+
+- `POST /v2/entities`
+- `POST /v2/entities//attrs`
+- `PUT /v2/entities//attrs`
+- `POST /v2/op/entites` with `actionType` `append`, `appendStrict` or `replace`
+
+### Command reception
+
+Once the command is triggered, it is send to the device. Depending on transport protocol, it is going to be sent to the
+device in a different way. After sending the command, the IoT Agent will have updated the value of `ping_status` to
+`PENDING` for entity into the Context Broker. Neither `ping_info` nor `ping` will be updated.
+
+#### HTTP devices
+
+**Push commands**
+
+Push commands are those that are sent to the device once the IoT Agent receives the request from the Context Broker. In
+order to send push commands it is needed to set the `"endpoint": "http://[DEVICE_IP]:[PORT]/"` in the device or group
+provision. The device is supposed to be listening for commands at that URL in a synchronous way. Make sure the device
+endpoint is reachable by the IoT Agent. Push commands are only valid for HTTP devices. For MQTT devices it is not needed
+to set the `endpoint` parameter.
+
+Considering using the IoTA-JSON Agent, and given the previous example, the device should receive a POST request to
+`http://[DEVICE_IP]:[PORT]` with the following payload:
+
+```
+POST /
+Content-Type: application/json
+
+{"ping":"Ping request"}
+```
+
+**Poll commands**
+
+Poll commands are those that are stored in the IoT Agent waiting to be retrieved by the devices. This kind of commands
+are typically used for devices that doesn't have a public IP or the IP cannot be reached because of power or netkork
+constrictions. The device connects to the IoT Agent periodically to retrieve commands. In order to configure the device
+as poll commands you just need to avoid the usage of `endpoint` parameter in the device provision.
+
+Once the command request is issued to the IoT agent, the command is stored waiting to be retrieved by the device. In
+that moment, the status of the command is `"_status": "PENDING"`.
+
+For HTTP devices, in order to retrieve a poll command, the need to make a GET request to the IoT Agent to the path used
+as `resource` in the provisioned group (`/iot/json` by default in IoTA-JSON if no `resource` is used) with the following
+parameters:
+
+**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
+one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
+present moment, but it will when this issue gets solved.
+
+- `k`: API key of the device.
+- `i`: Device ID.
+- `getCmd`: This parameter is used to indicate the IoT Agent that the device is requesting a command. It is needed to
+ set it to `1`
+
+Taking the previous example, and considering the usage of the IoTA-JSON Agent, the device should make the following
+request, being the response to this request a JSON object with the command name as key and the command value as value:
+
+**Request:**
+
+```
+GET /iot/json?k=&i=&getCmd=1
+Accept: application/json
+
+```
+
+**Response:**:
+
+```
+200 OK
+Content-type: application/json
+
+{"ping":"Ping request"}
+```
+
+For IoT Agents different from IoTA-JSON it is exactly the same just changing in the request the resource by the
+corresponding resource employed by the agent (i.e., IoTA-UL uses `/iot/d` as default resource instead of `/iot/json`)
+and setting the correct `` and ``. The response will be also different depending on the IoT Agent
+employed.
+
+**FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
+one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
+present moment, but it will when this issue gets solved.
+
+**Request**
+
+```
+POST /iot/json?k=&i=&getCmd=1
+Content-Type: application/json
+
+{"t":25,"h":42,"l":"1299"}
+```
+
+**Response**
+
+```
+200 OK
+Content-type: application/json
+
+{"ping":"Ping request"}
+```
+
+This is also possible for IoTA-UL Agent changing in the request the resource, setting the correct ``,
+``, payload and headers.
+
+Once the command is retrieved by the device the status is updated to `"_status": "DELIVERED"`. Note that status
+`DELIVERED` only make sense in the case of poll commands. In the case of push command it cannot happen.
+
+#### MQTT devices
+
+For MQTT devices, it is not needed to declare an endpoint (i.e. if included in the provisioning request, it is not
+used). The device is supposed to be subscribed to the following MQTT topic where the IoT Agent will publish the command:
+
+```
+///cmd
+```
+
+In the case of using the IoTA-JSON Agent, the device should subscribe to the previous topic where it is going to receive
+a message like the following one when a command is triggered in the Context Broker like the previous step:
+
+```json
+{ "ping": "Ping request" }
+```
+
+Please note that the device should subscribe to the broker using the disabled clean session mode (enabled using
+`--disable-clean-session` option CLI parameter in `mosquitto_sub`). This option means that all of the subscriptions for
+the device will be maintained after it disconnects, along with subsequent QoS 1 and QoS 2 commands that arrive. When the
+device reconnects, it will receive all of the queued commands.
+
+### Command confirmation
+
+Once the command is completely processed by the device, it should return the result of the command to the IoT Agent.
+This result will be progressed to the Context Broker where it will be stored in the `_info` attribute. The
+status of the command will be stored in the `_status` attribute (`OK` if everything goes right).
+
+For the IoTA-JSON, the payload of the confirmation message must be a JSON object with name of the command as key and the
+result of the command as value. For other IoT Agents, the payload must follow the corresponding protocol. For a given
+`ping` command, with a command result `status_ok`, the response payload should be:
+
+```JSON
+{"ping":"status_ok"}
+```
+
+Eventually, once the device makes the response request the IoTA would update the attributes `ping_status` to `OK` and
+`ping_info` to `status_ok` for the previous example.
+
+#### HTTP
+
+In order confirm the command execution, the device must make a POST request to the IoT Agent with the result of the
+command as payload, no matter if it is a push or a poll command. Following with the IoTAgent JSON case, the request must
+be made to the `/iot/json/commands`, this way:
+
+```
+POST /iot/json/commands?k=&i=
+Content-Type: application/json
+Accept: application/json
+
+{"ping":"status_ok"}
```
-For each attribute that would have bidirectionality, a new field `reverse` must be configured. This field will contain
-an array of fields that will be created based on the notifications content. The expression notification can contain any
-attribute of the same entity as the bidirectional attribute; declaring them in the expressions will add them to the
-subscription payload.
+#### MQTT
-For each attribute in the `reverse` array, an expression must be defined to calculate its value based on the
-notification attributes. This value will be passed to the underlying protocol with the `object_id` name. Details about
-how the value is then progressed to the device are protocol-specific.
+The device should publish the result of the command (`{"ping":"status_ok"}` in the previous example) to a topic
+following the next pattern:
+
+```
+////cmdexe
+```
+
+The IoTA is subscribed to that topic, so it gets the result of the command. When this happens, the status is updated
+to`"_status": "OK"`. Also the result of the command delivered by the device is stored in the `_info`
+attribute.
## Overriding global Context Broker host
@@ -928,8 +1565,7 @@ of devices.
## Multitenancy, FIWARE Service and FIWARE ServicePath
Every operation in the API require the `fiware-service` and `fiware-servicepath` to be defined; the operations are
-performed in the scope of those headers. For the list case, the special wildcard servicepath can be specified, `/*`. In
-this case, the operation applies to all the subservices of the service given by the `fiware-service` header.
+performed in the scope of those headers.
## Secured access to the Context Broker
@@ -1161,36 +1797,41 @@ A `datasetId` is also maintained for each new attribute defined in the `reverse`
Config group is represented by a JSON object with the following fields:
-| Field | Optional | Type | Expression | Definitiom |
-| ------------------------------ | -------- | -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `service` | | string | | `FIWARE-Service` header to be used |
-| `subservice` | | string | | Subservice of the devices of this type. |
-| `resource` | | string | | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). |
-| `apikey` | | string | | API Key string. |
-| `timestamp` | ✓ | bool | | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. |
-| `entity_type` | | string | | name of the Entity `type` to assign to the group. |
-| `trust` | ✓ | string | | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). |
-| `cbHost` | ✓ | string | | Context Broker connection information. This options can be used to override the global ones for specific types of devices. |
-| `lazy` | ✓ | | | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. |
-| `commands` | ✓ | | | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
-| `attributes` | ✓ | | | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
-| `static_attributes` | ✓ | | | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
-| `internal_attributes` | ✓ | | | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
-| `explicitAttrs` | ✓ | string or bool | ✓ | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
-| `entityNameExp` | ✓ | string | | optional field to allow use expressions to define entity name, instead default `id` and `type` |
-| `ngsiVersion` | ✓ | string | | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. |
-| `defaultEntityNameConjunction` | ✓ | string | | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
-| `autoprovision` | ✓ | bool | ✓? | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. |
+| Field | Optional | Type | Expression | Definitiom |
+| ------------------------------ | -------- | -------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `service` | | string | | `FIWARE-Service` header to be used |
+| `subservice` | | string | | Subservice of the devices of this type. |
+| `resource` | | string | | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). |
+| `apikey` | | string | | API Key string. |
+| `timestamp` | ✓ | bool | | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current server timestamp or provided `TimeInstant` as measure when follows ISO 8601 format (see [timestamp processing](#timestamp-processing) section for aditional detail). With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. |
+| `entity_type` | | string | | name of the Entity `type` to assign to the group. |
+| `trust` | ✓ | string | | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). |
+| `cbHost` | ✓ | string | | Context Broker connection information. This options can be used to override the global ones for specific types of devices. |
+| `lazy` | ✓ | | | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. |
+| `commands` | ✓ | | | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
+| `attributes` | ✓ | | | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
+| `static_attributes` | ✓ | | | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
+| `internal_attributes` | ✓ | | | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
+| `explicitAttrs` | ✓ | string or bool | ✓ | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
+| `entityNameExp` | ✓ | string | | optional field to allow use expressions to define entity name, instead default name, based on the device ID and the entity type (i.e. `:`). More information in [specific section](#entity-name-expression-entitynameexp) |
+| `ngsiVersion` | ✓ | string | | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. |
+| `defaultEntityNameConjunction` | ✓ | string | | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
+| `autoprovision` | ✓ | bool | ✓? | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. |
+| `payloadType` | ✓ | string | | optional string value used to switch between **IoTAgent**, **NGSI-v2** and **NGSI-LD** measure payloads types. Possible values are: `iotagent`, `ngsiv2` or `ngsild`. The default is `iotagent`. |
+| `transport` | ✓ | `string` | | Transport protocol used by the group of devices to send updates, for the IoT Agents with multiple transport protocols. |
+| `endpoint` | ✓ | `string` | | Endpoint where the group of device is going to receive commands, if any. |
+| `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. See more info [in this section](admin.md#storelastmeasure). False by default |
+| `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default |
+| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). |
### Config group operations
The following actions are available under the config group endpoint:
-#### Retrieve config groups `GET /iot/services`
+#### Retrieve config groups `GET /iot/groups`
-List all the config groups for the given `fiware-service` and `fiware-servicepath`. If the `fiware-servicepath` header
-has the wildcard expression, `/*`, all the config groups for that `fiware-service` are returned. The config groups that
-match the `fiware-servicepath` are returned in any other case.
+List all the config groups for the given `fiware-service` and `fiware-servicepath`. The config groups that match the
+`fiware-servicepath` are returned in any other case.
_**Request headers**_
@@ -1211,14 +1852,14 @@ Successful operations return `Content-Type` header with `application/json` value
_**Response payload**_
-A JSON object with a services field that contains an array of services that match the request. See the
-[config group datamodel](#service-group-datamodel) for more information.
+A JSON object with a `groups` field that contains an array of groups that match the request. See the
+[config group datamodel](#config-group-datamodel) for more information.
Example:
```json
{
- "services": [
+ "groups": [
{
"resource": "/deviceTest",
"apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1255,7 +1896,7 @@ Example:
}
```
-#### Create config group `POST /iot/services`
+#### Create config group `POST /iot/groups`
Creates a set of config groups for the given service and service path. The service and subservice information will taken
from the headers, overwritting any preexisting values.
@@ -1269,14 +1910,14 @@ _**Request headers**_
_**Request payload**_
-A JSON object with a `services` field. The value is an array of config groups objects to create. See the
-[config group datamodel](#service-group-datamodel) for more information.
+A JSON object with a `groups` field. The value is an array of config groups objects to create. See the
+[config group datamodel](#config-group-datamodel) for more information.
Example:
```json
{
- "services": [
+ "groups": [
{
"resource": "/deviceTest",
"apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1310,7 +1951,7 @@ _**Response headers**_
Successful operations return `Content-Type` header with `application/json` value.
-#### Modify config group `PUT /iot/services`
+#### Modify config group `PUT /iot/groups`
Modifies the information of a config group, identified by the `resource` and `apikey` query parameters. Takes a service
group body as the payload. The body does not have to be complete: for incomplete bodies, just the attributes included in
@@ -1332,8 +1973,8 @@ _**Request headers**_
_**Request payload**_
-A JSON object with the config group information to be modified. See the
-[config group datamodel](#service-group-datamodel) for more information.
+A JSON object with the config group information to be modified. See the [config group datamodel](config-group-datamodel)
+for more information.
Example:
@@ -1350,7 +1991,7 @@ _**Response code**_
- 400 MISSING_HEADERS if any of the mandatory headers is not present.
- 500 SERVER ERROR if there was any error not contemplated above.:
-#### Remove config group `DELETE /iot/services`
+#### Remove config group `DELETE /iot/groups`
Removes a config group, identified by the `resource` and `apikey` query parameters.
@@ -1381,26 +2022,31 @@ _**Response code**_
The table below shows the information held in the Device resource. The table also contains the correspondence between
the API resource fields and the same fields in the database model.
-| Field | Optional | Type | Expression | Definitiom |
-| --------------------- | -------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `device_id` | | `string` | | Device ID that will be used to identify the device. |
-| `service` | | `string` | | Name of the service the device belongs to (will be used in the fiware-service header). |
-| `service_path` | | `string` | | Name of the subservice the device belongs to (used in the fiware-servicepath header). |
-| `entity_name` | | `string` | | Name of the entity representing the device in the Context Broker |
-| `entity_type` | | `string` | | Type of the entity in the Context Broker |
-| `timezone` | ✓ | `string` | | Timezone of the device if that has any |
-| `timestamp` | ✓ | `string` | | Flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. |
-| `apikey` | ✓ | `string` | | Apikey key string to use instead of group apikey |
-| `endpoint` | ✓ | `string` | | Endpoint where the device is going to receive commands, if any. |
-| `protocol` | ✓ | `string` | | Pame of the device protocol, for its use with an IoT Manager |
-| `transport` | ✓ | `string` | | Transport protocol used by the device to send updates, for the IoT Agents with multiple transport protocols. |
-| `attributes` | ✓ | `array` | | List of attributes that will be stored in the Context Broker. |
-| `commands` | ✓ | `array` | | List of commands that will be stored in the Context Broker. |
-| `lazy` | ✓ | `array` | | List of lazy attributes that will be stored in the Context Broker. |
-| `static_attributes` | ✓ | `array` | | List of static attributes that will be stored in the Context Broker. |
-| `internal_attributes` | ✓ | `array` | | List of internal attributes with free format for specific IoT Agent configuration. |
-| `explicitAttrs` | ✓ | `boolean` | ✓ | Field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
-| `ngsiVersion` | ✓ | `string` | | string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. |
+| Field | Optional | Type | Expression | Definitiom |
+| --------------------- | -------- | --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `device_id` | | `string` | | Device ID that will be used to identify the device. |
+| `service` | | `string` | | Name of the service the device belongs to (will be used in the fiware-service header). |
+| `service_path` | | `string` | | Name of the subservice the device belongs to (used in the fiware-servicepath header). |
+| `entity_name` | | `string` | | Name of the entity representing the device in the Context Broker |
+| `entity_type` | | `string` | | Type of the entity in the Context Broker |
+| `timezone` | ✓ | `string` | | Timezone of the device if that has any |
+| `timestamp` | ✓ | `string` | | Flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current server timestamp or provided `TimeInstant` as measure when follows ISO 8601 format (see [timestamp processing](#timestamp-processing) section for aditional detail). With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. |
+| `apikey` | ✓ | `string` | | Apikey key string to use instead of group apikey |
+| `endpoint` | ✓ | `string` | | Endpoint where the device is going to receive commands, if any. |
+| `protocol` | ✓ | `string` | | Pame of the device protocol, for its use with an IoT Manager |
+| `transport` | ✓ | `string` | | Transport protocol used by the device to send updates, for the IoT Agents with multiple transport protocols. |
+| `attributes` | ✓ | `array` | | List of attributes that will be stored in the Context Broker. |
+| `commands` | ✓ | `array` | | List of commands that will be stored in the Context Broker. |
+| `lazy` | ✓ | `array` | | List of lazy attributes that will be stored in the Context Broker. |
+| `static_attributes` | ✓ | `array` | | List of static attributes that will be stored in the Context Broker. |
+| `internal_attributes` | ✓ | `array` | | List of internal attributes with free format for specific IoT Agent configuration. |
+| `explicitAttrs` | ✓ | `boolean` | ✓ | Field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
+| `ngsiVersion` | ✓ | `string` | | string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. |
+| `payloadType` | ✓ | `string` | | optional string value used to switch between **IoTAgent**, **NGSI-v2** and **NGSI-LD** measure payloads types. Possible values are: `iotagent`, `ngsiv2` or `ngsild`. The default is `iotagent`. |
+| `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. Useful just for debugging purpose. See more info [in this section](admin.md#storelastmeasure). False by default. |
+| `lastMeasure` | ✓ | `object` | | last measure stored on device when `storeLastMeasure` is enabled. See more info [in this section](admin.md#storelastmeasure). This field can be cleared using `{}` in a device update request. In that case, `lastMeasure` is removed from device (until a next measure is received and `lastMesuare` gets created again). |
+| `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default. |
+| `cmdMode` | ✓ | `string` | | Command mode that will use iotagent with CB: **legacy**, **notification** and **advancedNotification**. **Legacy** is based on registers. **notification** based on simplified schema of subscriptions. **Legacy** by default. More information on how the different modes work can be found in [the northbound interactions documents](devel/northboundinteractions.md). |
### Device operations
@@ -1651,10 +2297,52 @@ _**Request headers**_
_**Response code**_
-- `200` `OK` if successful.
+- `204` `OK` if successful.
- `404` `NOT FOUND` if the device was not found in the database.
- `500` `SERVER ERROR` if there was any error not contemplated above.
+### Batch Operations
+
+#### Remove devices `POST /iot/op/delete`
+
+Remove a set of devices from the device registry. The devices is identified by the Device ID and apikey in payload body.
+
+_**Request headers**_
+
+| Header | Optional | Description | Example |
+| -------------------- | -------- | ---------------------------------------------------------------------------------------------- | ---------- |
+| `Fiware-Service` | ✓ | Tenant or service. See subsection [Multi tenancy](#multi-tenancy) for more information. | `acme` |
+| `Fiware-ServicePath` | ✓ | Service path or subservice. See subsection [Service Path](#service-path) for more information. | `/project` |
+
+_**Request body**_
+
+The request body contains an Array of objects which the values which identifies the devices, deviceId and apikey. For
+more information, see the [Device datamodel](#device-datamodel) section.
+
+Example:
+
+```json
+{
+ "devices": [
+ {
+ "deviceId": "myDevice1",
+ "apikey": "apikey1"
+ },
+ {
+ "deviceId": "myDevice2",
+ "apikey": "apikey2"
+ }
+ ]
+}
+```
+
+_**Response code**_
+
+- `204` `OK` if successful.
+- `404` `NOT FOUND` if one or several devices were not found in the database. Note that although a 404 error is
+ returned the existing devices are deleted.
+- `500` `SERVER ERROR` if there was any error not considered above.
+
## Miscellaneous API
### Log operations
@@ -1680,7 +2368,7 @@ _**Response code**_
- `200` `OK` if successful.
- `500` `SERVER ERROR` if there was any error not contemplated above.
-#### Retrieve log level `PUT /admin/log`
+#### Retrieve log level `GET /admin/log`
_**Response code**_
@@ -1703,8 +2391,16 @@ Example:
#### List IoTA Information `GET /iot/about`
+*NOTE*: this endpoint has `GET /version` as alias.
+
Returns a useful information about component version and deployment information. It can be used as a heartbeat operation
-to check the health of the IoT Agent if required.
+to check the health of the IoT Agent if required since includes a health-check system for some dependencies: the Context
+Broker, the IoT Agent Manager, MongoDB, and the MQTT broker. Each dependency is checked using the most appropriate
+mechanism (HTTP requests for the Context Broker and IoT Agent Manager, a native database ping via Mongoose for MongoDB,
+and a real connection handshake for MQTT). The checks run at a configurable interval, use short timeouts, and tolerate
+transient errors by requiring several consecutive failures before marking a dependency as DOWN. Results are cached in
+memory and exposed through the /iot/about endpoint, providing real-time visibility into both configuration status and
+runtime connectivity without impacting normal agent operation.
_**Response payload**_
@@ -1723,10 +2419,125 @@ Example:
"libVersion": "2.7.0",
"port": "4041",
"baseRoot": "/",
- "version": "1.7.0"
+ "version": "1.7.0",
+ "connections":{
+ "contextBroker":{
+ "ok":true,
+ "configured":true,
+ "url":"http://iot-orion:1026",
+ "lastOk":"2026-02-27T13:28:53.484Z",
+ "lastError":null,
+ "latencyMs":7,
+ "consecutiveFails":0
+ },
+ "iotagentManager":{
+ ...
+ }
+ "mongodb":{
+ ...
+ },
+ "mqtt":{
+ ...
+ }
+ }
}
```
+### Metrics
+
+The IoT Agent Library exposes a [openmetrics-compatible](https://github.com/OpenObservability/OpenMetrics) endpoint for
+telemetry collectors to gather application statistics.
+
+#### Retrieve metrics `GET /metrics`
+
+_**Response code**_
+
+- `200` `OK` if successful.
+- `406` `Wrong Accept Header` If accept format is not supported.
+- `500` `SERVER ERROR` if there was any error not contemplated above.
+
+_**Response body**_
+
+Returns the current value of the server stats,
+
+- If `Accept` header contains `application/openmetrics-text; version=(1.0.0|0.0.1)`, the response has content-type
+ `application/openmetrics-text; version=; charset=utf-8`
+- Else, If `Accept` header is missing or supports `text/plain` (explicitly or by `*/*`) , the response has
+ content-type `text/plain; version=0.0.4; charset=utf-8` (legacy format for [prometheus](https://prometheus.io))
+- In any other case, returns an error message with `406` status.
+
+For the kind of metrics exposed by the application, the actual payload itself is completely the same for both
+content-types, and follows the openmetrics specification, e.g:
+
+```
+# HELP deviceCreationRequests global metric for deviceCreationRequests
+# TYPE deviceCreationRequests counter
+deviceCreationRequests 0
+# HELP deviceRemovalRequests global metric for deviceRemovalRequests
+# TYPE deviceRemovalRequests counter
+deviceRemovalRequests 0
+# HELP measureRequests global metric for measureRequests
+# TYPE measureRequests counter
+measureRequests 0
+# HELP raiseAlarm global metric for raiseAlarm
+# TYPE raiseAlarm counter
+raiseAlarm 0
+# HELP releaseAlarm global metric for releaseAlarm
+# TYPE releaseAlarm counter
+releaseAlarm 0
+# HELP updateEntityRequestsOk global metric for updateEntityRequestsOk
+# TYPE updateEntityRequestsOk counter
+updateEntityRequestsOk 2
+# HELP updateEntityRequestsError global metric for updateEntityRequestsError
+# TYPE updateEntityRequestsError counter
+updateEntityRequestsError 5
+# EOF
+```
+
+Also are included health check metrics for endpoints used by iotagent (CB, IotaM, MongoDb, ..) like:
+
+```
+mqttOK true
+mqttLastOk 2026-03-06T07:19:39.923Z
+mqttLastError null
+mqttLatencyMs 5
+mqttConsecutiveFails 0
+contextBrokerOK true
+contextBrokerLastOk 2026-03-06T07:19:39.924Z
+contextBrokerLastError null
+contextBrokerLatencyMs 8
+contextBrokerConsecutiveFails 0
+mongodbOK true
+mongodbLastOK 2026-03-06T07:19:39.922Z
+mongodbLastError null
+mongodbLatencyMs 4
+mongodbConsecutiveFails 0
+iotagentManagerOK true
+iotagentManagerLastOk 2026-03-06T07:19:39.927Z
+iotagentManagerLastError null
+iotagentManagerLatencyMs 10
+iotagentManagerConsecutiveFails 0
+ready 1
+
+```
+
+### Ready
+
+The IoT Agent Library exposes a [openmetrics-compatible](https://github.com/OpenObservability/OpenMetrics) endpoint for
+telemetry collectors to gather application statistics.
+
+#### Retrieve ready `GET /ready`
+
+_**Response code**_
+
+- `200` `OK` if iotagent was successful started (specially iotagent-manager registration).
+- `503` `SERVICE UNAVAILABLE` if there was any error when iotagent was startged (i.e. iotagent was not register into
+ iotagent-manager).
+
+_**Response body**_
+
+- Empty body is returned in any case.
+
[1]:
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22longitude%22%3A%205%2C%0A%20%20%22latitude%22%3A%2037%2C%0A%20%20%22level%22%3A223%0A%7D&input=%7Bcoordinates%3A%20%5Blongitude%2Clatitude%5D%2C%20type%3A%20'Point'%7D&transforms=%7B%0A%7D
[2]:
@@ -1764,4 +2575,4 @@ Example:
[18]:
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22value%22%20%3A%206%2C%0A%20%20%22ts%22%3A%201637245214901%2C%0A%20%22name%22%3A%20%22DevId629%22%2C%0A%20%22object%22%3A%7Bname%3A%20%22John%22%2C%20surname%3A%20%22Doe%22%7D%2C%0A%20%20%22array%22%3A%5B1%2C3%5D%0A%7D&input=name%7Csubstr(0%2Cname%7CindexOf(%22e%22)%2B1)&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
[99]:
- https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
+ https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%20%7B%0A%20%20%20%20jsonparse%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.parse)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20jsonstringify%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(JSON.stringify)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v))(arr)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20Array.isArray%2C%0A%20%20%20%20isnan%3A%20isNaN%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber((val)%20%3D%3E%20parseInt(val%2C%2010))(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeParseNumber%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20result%20%3D%20fn(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(result)%20%3F%20null%20%3A%20result%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeParseNumber(parseFloat)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20timeoffset%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTimezoneOffset())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20val.toString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20urlencode%3A%20encodeURI%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation(decodeURI)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg%2C%20'g')%2C%20to))(str%2C%20reg%2C%20to)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5Bvalues.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20!%3D%3D%20null%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20i%20%3A%20null)%2C%20null)%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20Array.from(new%20Set(arr).add(x))%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20Math.floor%2C%0A%20%20%20%20ceil%3A%20Math.ceil%2C%0A%20%20%20%20round%3A%20Math.round%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20num%20%3D%20Number.parseFloat(val)%3B%0A%20%20%20%20%20%20%20%20const%20dec%20%3D%20Number.parseInt(decimals)%3B%0A%20%20%20%20%20%20%20%20return%20isNaN(num)%20%7C%7C%20isNaN(dec)%20%3F%20null%20%3A%20num.toFixed(dec)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20gettime%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.getTime())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20toisostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeDateOperation%20%3D%20(fn)%20%3D%3E%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20const%20date%20%3D%20new%20Date(val)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20isNaN(date.getTime())%20%3F%20null%20%3A%20fn(date)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeDateOperation((date)%20%3D%3E%20date.toISOString())(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options))(d%2C%20timezone%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20localestringnumber%3A%20(n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20(fn)%20%3D%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((n%2C%20locale%2C%20options)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20n%20!%3D%3D%20'number'%20%7C%7C%20isNaN(n))%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20n.toLocaleString(locale%2C%20options)%3B%0A%20%20%20%20%20%20%20%20%7D)(n%2C%20locale%2C%20options)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20now%3A%20Date.now%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20safeOperation%20%3D%20(fn)%20%3D%3E%20(...args)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20fn(...args)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20return%20safeOperation((val)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(typeof%20val%20!%3D%3D%20'string'%20%7C%7C%20!%2F%5E%5B0-9a-fA-F%5D%2B%24%2F.test(val)%20%7C%7C%20val.length%20%25%202%20!%3D%3D%200)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%3B%0A%20%20%20%20%20%20%20%20%7D)(val)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20valuePicker%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick)%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2C%20pick)%20%3D%3E%0A%20%20%20%20%20%20%20%20Object.entries(val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v))%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((%5Bk%5D)%20%3D%3E%20k)%0A%7D
diff --git a/doc/config-basic-example.js b/doc/config-basic-example.js
deleted file mode 100644
index 61e81f236..000000000
--- a/doc/config-basic-example.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var config = {
- logLevel: 'DEBUG',
- contextBroker: {
- host: 'localhost',
- port: '1026'
- },
- server: {
- port: 4041
- },
- deviceRegistry: {
- type: 'memory'
- },
- types: {},
- service: 'howtoService',
- subservice: '/howto',
- providerUrl: 'http://localhost:4041',
- defaultType: 'Thing'
-};
-
-module.exports = config;
diff --git a/doc/deprecated.md b/doc/deprecated.md
index c95324c4e..0c4aba92b 100644
--- a/doc/deprecated.md
+++ b/doc/deprecated.md
@@ -12,16 +12,27 @@ longer. In particular:
A list of deprecated features and the version in which they were deprecated follows:
+- Config `mongodb.*` different from `mongodb.uri` (and associated env vars `IOTA_MONGO_*` except `IOTA_MONGO_URI`) in
+ 4.11.0
- Support to NGSI v1 (finally removed in 2.18.0)
- Support to Node.js v4 in iotagent-node-lib 2.8.1 (finally removed in 2.9.0)
- Support to Node.js v6 in iotagent-node-lib 2.9.0 (finally removed in 2.10.0)
- Support to Node.js v8 in iotagent-node-lib 2.12.0 (finally removed in 2.13.0)
- Support to Node.js v10 in iotagent-node-lib 2.15.0 (finally removed in 2.16.0)
- Support to Node.js v12 in iotagent-node-lib 2.24.0 (finally removed in 2.25.0)
+- Support to Node.js v14 in iotagent-node-lib 3.0.0 (finally removed in 3.1.0)
+- Support to Node.js v16 in iotagent-node-lib 4.9.0 (finally removed in 4.10.0)
+- Support to Node.js v18 in iotagent-node-lib 4.9.0 (finally removed in 4.10.0)
- Support to NGSI-LD v1.3 in iotagent-node-lib 2.25.0 (finally removed in 2.26.0)
- Support groups (provision) statically defined by configuration
- Support to in-memory registry (i.e.`deviceRegistry.type=memory`)
+- eventType configuration (finally removed in 3.0.0)
- Support to legacy expressions (finally removed in 3.2.0)
+- Bidirectinal pluging (finally removed in 3.4.0)
+- appendMode configuration (`IOTA_APPEND_MODE` env var) (finally removed in 3.4.0)
+- `config.stats` section, and push-mode statistics.
+- Services API routes (`/iot/services`) in favor of the `/iot/groups`. Both are still supported, but the former is
+ deprecated.
The use of Node.js v14 is highly recommended.
@@ -41,13 +52,31 @@ information in the case you want to use old versions:
The following table provides information about the last iotagent-node-lib version supporting currently removed features:
-| **Removed feature** | **Last iotagent-node-lib version supporting feature** | **That version release date** |
-| ---------------------- | ----------------------------------------------------- | ----------------------------- |
-| NGSI v1 API | 2.17.0 | August 30th, 2021 |
-| Support to Node.js v4 | 2.8.1 | December 19th, 2018 |
-| Support to Node.js v6 | 2.9.0 | May 22nd, 2019 |
-| Support to Node.js v8 | 2.12.0 | April 7th, 2020 |
-| Support to Node.js v10 | 2.15.0 | February 18th, 2021 |
-| Support to Node.js v12 | 2.24.0 | September 2nd, 2022 |
-| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023 |
-| Support to Legacy Expressions | 3.1.0 | April 25th, 2023 |
+| **Removed feature** | **Last iotagent-node-lib version supporting feature** | **That version release date** |
+| -------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------- |
+| NGSI v1 API | 2.17.0 | August 30th, 2021 |
+| Support to Node.js v4 | 2.8.1 | December 19th, 2018 |
+| Support to Node.js v6 | 2.9.0 | May 22nd, 2019 |
+| Support to Node.js v8 | 2.12.0 | April 7th, 2020 |
+| Support to Node.js v10 | 2.15.0 | February 18th, 2021 |
+| Support to Node.js v12 | 2.24.0 | September 2nd, 2022 |
+| Support to Node.js v14 | 3.0.0 | March 30th, 2023 |
+| Support to Node.js v16 | 4.9.0 | August 22nd, 2025 |
+| Support to Node.js v18 | 4.9.0 | August 22nd, 2025 |
+| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023 |
+| eventType configuration | 2.26.0 | March 15th, 2023 |
+| Support to Legacy Expressions | 3.1.0 | April 25th, 2023 |
+| bidirectional plugin | 3.3.0 | August 24th, 2023 |
+| appendMode configuration (`IOTA_APPEND_MODE` env var) | 3.3.0 | August 24th, 2023 |
+| push-mode stats | 4.5.0 | June 11th, 2024 |
+| config `mongodb.host` (env var `IOTA_MONGO_HOST`) | Not yet | Not yet |
+| config `mongodb.port` (env var `IOTA_MONGO_PORT`) | Not yet | Not yet |
+| config `mongodb.db` (env var `IOTA_MONGO_DB`) | Not yet | Not yet |
+| config `mongodb.replicaSet` (env var `IOTA_MONGO_REPLICASET`) | Not yet | Not yet |
+| config `mongodb.user` (env var `IOTA_MONGO_USER`) | Not yet | Not yet |
+| config `mongodb.password` (env var `IOTA_MONGO_PASSWORD`) | Not yet | Not yet |
+| config `mongodb.authSource` (env var `IOTA_MONGO_AUTH_SOURCE`) | Not yet | Not yet |
+| config `mongodb.retries` (env var `IOTA_MONGO_RETRIES`) | Not yet | Not yet |
+| config `mongodb.retryTime` (env var `IOTA_MONGO_RETRY_TIME`) | Not yet | Not yet |
+| config `mongodb.ssl` (env var `IOTA_MONGO_SSL`) | Not yet | Not yet |
+| config `mongodb.extraArgs` (env var `IOTA_MONGO_EXTRAARGS`) | Not yet | Not yet |
diff --git a/doc/NorthboundInteractions.postman_collection b/doc/devel/NorthboundInteractions.postman_collection
similarity index 100%
rename from doc/NorthboundInteractions.postman_collection
rename to doc/devel/NorthboundInteractions.postman_collection
diff --git a/doc/architecture.md b/doc/devel/architecture.md
similarity index 52%
rename from doc/architecture.md
rename to doc/devel/architecture.md
index cbc2b5620..75b3dd1c4 100644
--- a/doc/architecture.md
+++ b/doc/devel/architecture.md
@@ -1,44 +1,19 @@
## Architecture
-The following section defines the archtecture and message flow which is common to all IoT Agents which use the library.
+The following section defines the architecture and message flow which is common to all IoT Agents which use the library.
### Device to NGSI Mapping
-Each Device will be mapped as an Entity associated to a Context Provider: the Device ID will be mapped by default to the
-entity ID and the type of the entity will be selected by the IoT Agent in a protocol-dependent way (e.g: with different
-URLs for different types). Both the name and type will be configurable by the user, either by type configuration or with
-the device preprovisioning.
-
-Each of the measures obtained from the device should be mapped to a different attribute. The name and type of the
-attribute will be configured by the user (globally for all the types in the IoT Agent configuration or in a per device
-basis preprovisioning the devices). Device measures can have three different behaviors:
-
-- **Active attributes**: are measures that are pushed from the device to the IoT agent. This measure changes will be
- sent to the Context Broker as updateContext requests over the device entity. NGSI queries to the context broker will
- be resolved in the Broker database.
-
-- **Lazy attributes**: some sensors will be passive, and will wait for the IoT Agent to request for data. For those
- measures, the IoT Agent will register itself in the Context Broker as a Context Provider (for all the lazy measures
- of that device), so if any component asks the Context Broker for the value of that sensor, its request will be
- redirected to the IoT Agent (that behaves as a NGSI10 Context Provider). This operation will be synchronous from the
- customer perspective: the Context Broker won't return a response until the device has returned its response to the
- IoT Agent.
-
-- **Commands**: in this case, the interaction will begin by setting an attribute in the device's entity, for which the
- IoT Agent will be regitered as CP. The IoT Agent will return an immediate response to the Context Broker, and will
- be held responsible of contacting the device to perform the command itself, updating special `status` and `info`
- attributes in the entity as soon as it has any information of the command progress.
-
The following sequence diagram shows the different NGSI interactions an IoT Agent makes with the Context Broker,
explained in the following subsections (using the example of a OMA Lightweight M2M device).
-
+
Be aware that the IoT Agents are only required to support NGSI10 operations `updateContext` and `queryContext` in their
standard formats (currently in JSON format; XML deprecated) but will not answer to NGSI9 operations (or NGSI convenience
operations of any kind).
-#### Configurations and Device provisioning information
+#### Config groups and Device provisioning information
In order for a device to connect to the IoT Agent, the device should be provisioned (although there may be occasions
where this registration is not needed). The provision process is meant to provide the IoT Agent with the following
@@ -58,39 +33,15 @@ information:
In order to provide this information, the IoT Agent Northbound API provides two resources: Device and Configuration
provisioning.
-**Configurations** may be used when a set of similar devices will be connected to the IoT Agent, to avoid provisioning
-the same set of information for every device. Custom APIKeys can be only provided with the use of Configurations for
+**Config groups** may be used when a set of similar devices will be connected to the IoT Agent, to avoid provisioning
+the same set of information for every device. Custom APIKeys can be only provided with the use of config group for
device groups. When a device is provisioned, it is assigned to a configuration _if there is one that matches its type,
its service and its subservice_. In that case, all the default information in the Configuration is merged with the
device information to create the definitive Device object that will be stored in the system.
-Particular IoT Agents _may_ support autoregistration of devices into configurations, if enough information is given from
+Particular IoT Agents _may_ support autoregistration of devices into config groups, if enough information is given from
the Southbound.
-#### Configurations and subservices
-
-Configurations are meant to be a mean of simplifying the device provisioning for groups of very similar devices.
-Considering that different groups of devices may be created in the same subservice that may require different
-configurations, multiple configurations are allowed for each subservice. Considering the key in the association between
-Device and Configuration was the triplet (service, subservice, type), all of these elements are considered mandatory.
-
-This statement doesn't hold true for older IoT Agents, though. In older versions of the IoT Agents, each device
-configuration was assigned to a particular subservice and just one configuration was allowed per subservice, so the
-relation between a Device and a Configuration didn't need the type to discriminate between Configurations. That's why
-for those agents, type was not a mandatory parameter.
-
-In order to allow backward-compatibility with those agents, the IoT Agent Library now implement a compatibility mode:
-the **Single Configuration Mode**, that makes the agent behave like the old agents. In this mode:
-
-- Each Subservice can contain just one Configuration. If a second Configuration is created for a Subservice, an error
- is raised.
-
-- Each Device provisioned for a Subservice is automatically assigned to the Subservice one Configuration if there is
- any.
-
-This compatibility has to be set for the whole IoT Agent, and there is no option of having both modes simultaneously
-running. Transitions from one mode to the other should be made with care, and may involve data migration.
-
#### Registration
Whenever a device is registered, the IoT Agent reads the device's entity information from the request or, if that
@@ -115,7 +66,7 @@ response to the caller, transparently.
**IMPORTANT NOTE:** at the present moment, commands (both push and poll) are supported only in the case of explicitly
provisioned agents. For autoprovisioned agents commands are not currently supported, although
-[an issue](https://github.com/telefonicaid/iot-agent-node-lib/issues/572) has been created about this functionality.
+[an issue](https://github.com/telefonicaid/iotagent-node-lib/issues/572) has been created about this functionality.
Commands are modelled as updates over a lazy attribute. As in the case of the lazy attributes, updates over a command
will be forwarded by the Context Broker to the IoT Agent, that will in turn interact with the device to perform the
@@ -165,52 +116,6 @@ taking too much to pick them up. See the configuration section for details.
The library does not deal with protocol transformation or South Bound communications for neither of the command types
(that's the task for those specific IoT Agents using the library).
-##### NGSI-LD `datasetId` support
-
-Limited support for parsing the NGSI-LD `datasetId` attribute is included within the library. A series of sequential
-commands for a single attribute can be sent as an NGSI-LD PATCH payload as follows:
-
-```json
-{
- "lampColor": [
- {
- "type": "Property",
- "value": { "color": "green", "duration": "55 secs" },
- "datasetId": "urn:ngsi-ld:Sequence:do-this"
- },
- {
- "type": "Property",
- "value": { "color": "red", "duration": "10 secs" },
- "datasetId": "urn:ngsi-ld:Sequence:then-do-this"
- }
- ]
-}
-```
-
-This results in the following sequential array of attribute updates to be sent to the `UpdateHandler` of the IoT Agent
-itself:
-
-```json
-[
- {
- "name": "lampColor",
- "type": "Property",
- "datasetId": "urn:ngsi-ld:Sequence:do-this",
- "metadata": {},
- "value": { "color": "green", "duration": "55 secs" }
- },
- {
- "name": "lampColor",
- "type": "Property",
- "datasetId": "urn:ngsi-ld:Sequence:then-do-this",
- "metadata": {},
- "value": { "color": "red", "duration": "10 secs" }
- }
-]
-```
-
-The equivalent for NGSI-v2 is not required since `datasetId` syntax is not supported by NGSI-v2.
-
#### Active attributes
Whenever a device proactively sends a message to the IoT Agent, it should transform its data to the appropriate NGSI
@@ -254,51 +159,4 @@ the concrete IoT Agent implementations will be to map between the native device
The following figure offers a graphical example of how a COAP IoT Agent work, ordered from the registration of the
device to a command update to the device.
-
-
-### The `TimeInstant` element
-
-As part of the device to entity mapping process the IoT Agent creates and updates automatically a special timestamp.
-This timestamp is represented as two different properties of the mapped entity::
-
-- With NGSI-v2, an attribute metadata named `TimeInstant` (per dynamic attribute mapped, which captures as an ISO8601
- timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the Standard
- `observedAt` property-of-a-property is used instead.
-
-- For NGSI-v2 only, an additional attribute `TimeInstant` is added to the entity which captures as an ISO8601
- timestamp when the last measurement received from the device was observed.
-
-If no information about the measurement timestamp is received by the IoT Agent, the arrival time of the measurement will
-be used to generate a `TimeInstant` for both the entity attribute and the attribute metadata.
-
-Take into account that:
-
-- the timestamp of different attributes belonging to the same measurement record may not be equal.
-- the arrival time and the measurement timestamp will not be the same in the general case.
-- if `timezone` field is defined as part of the provisioning of the device or group, timestamp fields will be
- generated using it. For instance, if `timezone` is set to `America/Los_Angeles`, a possible timestamp could be
- `2025-08-05T00:35:01.468-07:00`. If `timezone` field is not defined, by default Zulu Time Zone (UTC +0) will be
- used. Following the previous example, timestamp could be `2015-08-05T07:35:01.468Z`.
-
-E.g.: in the case of a device that can take measurements every hour of both temperature and humidity and sends the data
-once every day, at midnight, the `TimeInstant` reported for each measurement will be the hour when that measurement was
-observed (e.g. 4:00 PM), while all the measurements will have an arrival time around midnight. If no timestamps were
-reported with such measurements, the `TimeInstant` attribute would take those values around midnight.
-
-This functionality can be turned on and off through the use of the `timestamp` configuration flag (described in the
-configuration), as well as 'timestamp' flag in device or group provision.
-
-### Implementation decisions
-
-Given the aforementioned requirements, there are some aspects of the implementation that were chosen, and are
-particularly under consideration:
-
-- Aside from its configuration, the IoT Agent Lib is considered to be stateless. To be precise, the library mantains a
- state (the list of entities/devices whose information the agent can provide) but that state is considered to be
- transient. It's up to the particular implementation of the agent to consider whether it should have a persistent
- storage to hold the device information (so the internal list of devices is read from a DB) or to register the
- devices each time a device sends a measure. To this extent, two flavours of the Device Registry has been provided: a
- transient one (In-memory Registry) and a persistent one (based in MongoDB).
-- The IoT Agent does not care about the origin of the data, its type or structure. The mapping from raw data to the
- entity model, if there is any, is a responsibility of the particular IoT Agent implementation, or of another third
- party library.
+
diff --git a/doc/Contribution.md b/doc/devel/contribution-guidelines.md
similarity index 70%
rename from doc/Contribution.md
rename to doc/devel/contribution-guidelines.md
index bbd921fde..cfce3a297 100644
--- a/doc/Contribution.md
+++ b/doc/devel/contribution-guidelines.md
@@ -4,21 +4,22 @@
Before we get started, here are a few things we expect from you (and that you should expect from others):
-* Be kind and thoughtful in your conversations around this project. We all come from different backgrounds and
- projects, which means we likely have different perspectives on "how open source is done." Try to listen to others
- rather than convince them that your way is correct.
-* Please ensure that your contribution passes all tests. If there are test failures, you will need to address them
- before we can merge your contribution.
-* When adding content, please consider if it is widely valuable. Please don't add references or links to things you or
- your employer have created as others will do so if they appreciate it.
-* When reporting a vulnerability on the software, please, put in contact with IoT Agent Node Lib repository maintainers in order to discuss it
- in a private way.
+- Be kind and thoughtful in your conversations around this project. We all come from different backgrounds and
+ projects, which means we likely have different perspectives on "how open source is done." Try to listen to others
+ rather than convince them that your way is correct.
+- Please ensure that your contribution passes all tests. If there are test failures, you will need to address them
+ before we can merge your contribution.
+- When adding content, please consider if it is widely valuable. Please don't add references or links to things you or
+ your employer have created as others will do so if they appreciate it.
+- When reporting a vulnerability on the software, please, put in contact with IoT Agent Node Lib repository
+ maintainers in order to discuss it in a private way.
## How to contribute
-If you'd like to contribute, start by searching through the [issues](https://github.com/telefonicaid/iotagent-node-lib/issues) and
-[pull requests](https://github.com/telefonicaid/iotagent-node-lib/pulls) to see whether someone else has raised a similar idea or
-question.
+If you'd like to contribute, start by searching through the
+[issues](https://github.com/telefonicaid/iotagent-node-lib/issues) and
+[pull requests](https://github.com/telefonicaid/iotagent-node-lib/pulls) to see whether someone else has raised a
+similar idea or question.
If you don't see your idea listed, and you think it fits into the goals of this guide, do one of the following:
@@ -28,36 +29,43 @@ If you don't see your idea listed, and you think it fits into the goals of this
### Pull Request protocol
-As explained in ([FIWARE Contribution Requirements](https://fiware-requirements.readthedocs.io/en/latest))
-contributions are done using a pull request (PR). The detailed "protocol" used in such PR is described below:
-
-* Direct commits to master branch (even single-line modifications) are not allowed. Every modification has to come as a PR
-* In case the PR is implementing/fixing a numbered issue, the issue number has to be referenced in the body of the PR at creation time
-* Anybody is welcome to provide comments to the PR (either direct comments or using the review feature offered by GitHub)
-* Use *code line comments* instead of *general comments*, for traceability reasons (see comments lifecycle below)
-* Comments lifecycle
- * Comment is created, initiating a *comment thread*
- * New comments can be added as responses to the original one, starting a discussion
- * After discussion, the comment thread ends in one of the following ways:
- * `Fixed in ` in case the discussion involves a fix in the PR branch (which commit hash is
- included as reference)
- * `NTC`, if finally nothing needs to be done (NTC = Nothing To Change)
- * PR can be merged when the following conditions are met:
- * All comment threads are closed
- * All the participants in the discussion have provided a `LGTM` general comment (LGTM = Looks good to me)
- * Self-merging is not allowed (except in rare and justified circumstances)
+As explained in ([FIWARE Contribution Requirements](https://fiware-requirements.readthedocs.io/en/latest)) contributions
+are done using a pull request (PR). The detailed "protocol" used in such PR is described below:
+
+- Direct commits to master branch (even single-line modifications) are not allowed. Every modification has to come as
+ a PR
+- In case the PR is implementing/fixing a numbered issue, the issue number has to be referenced in the body of the PR
+ at creation time
+- Anybody is welcome to provide comments to the PR (either direct comments or using the review feature offered by
+ GitHub)
+- Use _code line comments_ instead of _general comments_, for traceability reasons (see comments lifecycle below)
+- Comments lifecycle
+ - Comment is created, initiating a _comment thread_
+ - New comments can be added as responses to the original one, starting a discussion
+ - After discussion, the comment thread ends in one of the following ways:
+ - `Fixed in ` in case the discussion involves a fix in the PR branch (which commit hash is
+ included as reference)
+ - `NTC`, if finally nothing needs to be done (NTC = Nothing To Change)
+- PR can be merged when the following conditions are met:
+ - All comment threads are closed
+ - All the participants in the discussion have provided a `LGTM` general comment (LGTM = Looks good to me)
+- Self-merging is not allowed (except in rare and justified circumstances)
Some additional remarks to take into account when contributing with new PRs:
-* PR must include not only code contributions, but their corresponding pieces of documentation (new or modifications to existing one) and tests
-* PR modifications must pass full regression based on existing test (unit, functional, memory, e2e) in addition to whichever new test added due to the new functionality
-* PR should be of an appropriated size that makes review achievable. Too large PRs could be closed with a "please, redo the work in smaller pieces" without any further discussing
+- PR must include not only code contributions, but their corresponding pieces of documentation (new or modifications
+ to existing one) and tests
+- PR modifications must pass full regression based on existing test (unit, functional, memory, e2e) in addition to
+ whichever new test added due to the new functionality
+- PR should be of an appropriated size that makes review achievable. Too large PRs could be closed with a "please,
+ redo the work in smaller pieces" without any further discussing
## Community
Discussions about the Open Source Guides take place on this repository's
-[Issues](https://github.com/telefonicaid/iotagent-node-lib/issues) and [Pull Requests](https://github.com/telefonicaid/iotagent-node-lib/pulls)
-sections. Anybody is welcome to join these conversations.
+[Issues](https://github.com/telefonicaid/iotagent-node-lib/issues) and
+[Pull Requests](https://github.com/telefonicaid/iotagent-node-lib/pulls) sections. Anybody is welcome to join these
+conversations.
Wherever possible, do not take these conversations to private channels, including contacting the maintainers directly.
diff --git a/doc/devel/development.md b/doc/devel/development.md
new file mode 100644
index 000000000..0c1d2243e
--- /dev/null
+++ b/doc/devel/development.md
@@ -0,0 +1,2091 @@
+# Development documentation
+
+- [Preface](#preface)
+- [Contributing](#contributing)
+- [Project management](#project-management)
+ - [Installing dependencies](#installing-dependencies)
+ - [Project build](#project-build)
+ - [Testing](#testing)
+ - [Test requirements](#test-requirements)
+ - [Debug Test](#debug-test)
+ - [Continuous testing](#continuous-testing)
+ - [Code Coverage](#code-coverage)
+ - [Clean](#clean)
+ - [Checking code style](#checking-code-style)
+ - [Source code style validation - ESLint](#source-code-style-validation---eslint)
+ - [Documentation Markdown validation](#documentation-markdown-validation)
+ - [Documentation Spell-checking](#documentation-spell-checking)
+ - [Prettify Code](#prettify-code)
+- [Library functions and modules](#library-functions-and-modules)
+ - [Stats Registry](#stats-registry)
+ - [Alarm module](#alarm-module)
+ - [Transactions](#transactions)
+ - [Library overview](#library-overview)
+ - [Function reference](#function-reference)
+ - [Generic middlewares](#generic-middlewares)
+- [DB Models from API document](#db-models-from-api-document)
+ - [Config group model](#config-group-model)
+ - [Device model](#device-model)
+- [Developing a new IoT Agent](#developing-a-new-iot-agent)
+ - [Protocol](#protocol)
+ - [Requirements](#requirements)
+ - [Basic IOTA](#basic-iot-agent)
+ - [IOTA With Active attributes](#iot-agent-with-active-attributes)
+ - [IOTA With Lazy attributes](#iota-with-lazy-attributes)
+ - [Previous considerations](#previous-considerations)
+ - [Implementation](#implementation)
+ - [IoT Agent in multi-thread mode](#iot-agent-in-multi-thread-mode)
+ - [Configuration management](#configuration-management)
+ - [Provisioning handlers](#provisioning-handlers)
+- [IoT Agent additional tools](#iot-agent-additional-tools)
+
+## Preface
+
+The **IoT Agent node library** as the name suggests is a library that provides a set of functions that can be used by
+IoT Agents to implement the northbound interface. The library is used by several FIWARE IoT Agents, such as:
+
+a standalone library that can be used by any IoT Agent to implement the northbound interface,
+
+is not a standalone product and should be added as a dependency to `package.json` of the IoT Agent.
+
+```json
+...
+"dependencies": {
+ "iotagent-node-lib": "*",
+}
+```
+
+In order to use the library within your own IoT Agent, you must first you require it before use:
+
+```javascript
+const iotagentLib = require('iotagent-node-lib');
+```
+
+This file contains the documentation for developers who wish to contribute to the **IoT Agent node library** project and
+also for those who wish to use the library within their own IoT Agent project.
+
+## Contributing
+
+Contributions to this project are welcome. Developers planning to contribute should follow the
+[Contribution Guidelines](contribution-guidelines.md)
+
+## Project management
+
+The **IoT Agent node library** project is managed using [npm](https://www.npmjs.com/). The following sections show the
+available options in detail:
+
+### Installing dependencies
+
+This is the first step to be executed after cloning the project. To install them, type the following command:
+
+```bash
+npm install
+```
+
+### Project build
+
+The project is managed using npm.
+
+For a list of available task, type
+
+```bash
+npm run
+```
+
+The following sections show the available options in detail.
+
+### Testing
+
+[Mocha](https://mochajs.org/) Test Runner + [Should.js](https://shouldjs.github.io/) Assertion Library.
+
+The test environment is preconfigured to run BDD testing style.
+
+Module mocking during testing can be done with [proxyquire](https://github.com/thlorenz/proxyquire)
+
+To run tests, type
+
+```bash
+npm test
+```
+
+There are additional targets starting with `test:` prefix to run specific test subsets isolated. For instance, the
+`test:expressions` target runs the subset of tests related with expression language feature:
+
+```bash
+npm run test:expressions
+```
+
+#### Test requirements
+
+A [MongoDB](https://www.mongodb.com/) 3.2+ instance is required to run tests. You can deploy one by using the commodity
+`docker-compose-dev.yml`:
+
+```
+docker-compose -f docker-compose-dev.yml up -d
+```
+
+To run docker compose you will need [docker](https://docs.docker.com/get-docker/) and
+[docker-compose](https://docs.docker.com/compose/install/).
+
+#### Debug Test
+
+To debug the code while running run tests, type
+
+```bash
+npm run test:debug
+```
+
+In the console the link to the debugger will be provided. You can connect to it via Chrome, for example, by opening the
+following url: `chrome://inspect`.
+
+Additional debug clients are listed on [node.js](https://nodejs.org/en/docs/guides/debugging-getting-started/).
+
+#### Continuous testing
+
+Support for continuous testing by modifying a src file or a test. For continuous testing, type
+
+```bash
+npm run test:watch
+```
+
+If you want to continuously check also source code style, use instead:
+
+```bash
+npm run watch
+```
+
+#### Code Coverage
+
+Istanbul
+
+Analyze the code coverage of your tests.
+
+To generate an HTML coverage report under `site/coverage/` and to print out a summary, type
+
+```bash
+# Use git-bash on Windows
+npm run test:coverage
+```
+
+### Clean
+
+Removes `node_modules` and `coverage` folders, and `package-lock.json` file so that a fresh copy of the project is
+restored.
+
+```bash
+# Use git-bash on Windows
+npm run clean
+```
+
+### Checking code style
+
+#### Source code style validation - ESLint
+
+Uses the provided `.eslintrc.json` flag file. To check source code style, type
+
+```bash
+npm run lint
+```
+
+#### Documentation Markdown validation
+
+Checks the Markdown documentation for consistency
+
+```bash
+# Use git-bash on Windows
+npm run lint:md
+```
+
+#### Documentation Spell-checking
+
+Uses the provided `.textlintrc` flag file. To check the Markdown documentation for spelling and grammar errors, dead
+links & etc.
+
+```bash
+# Use git-bash on Windows
+npm run lint:text
+```
+
+#### Prettify Code
+
+Runs the [prettier](https://prettier.io) code formatter to ensure consistent code style (whitespacing, parameter
+placement and breakup of long lines etc.) within the codebase.
+
+```bash
+# Use git-bash on Windows
+npm run prettier
+```
+
+To ensure consistent Markdown formatting run the following:
+
+```bash
+# Use git-bash on Windows
+npm run prettier:text
+```
+
+## Library functions and modules
+
+### Stats Registry
+
+The library provides a mechanism for the collection of stats related to the library's work. The Stats Registry holds a
+dictionary with the historical global value of each stat.
+
+The stats library currently stores only the following values:
+
+- **deviceCreationRequests**: number of Device Creation Requests that arrived to the API (no matter the result).
+- **deviceRemovalRequests**: number of Removal Device Requests that arrived to the API (no matter the result).
+- **measureRequests**: number of times the ngsiService.update() function has been invoked (no matter the result).
+- **raiseAlarm**: number of times the alarmManagement.raise() function has been invoked.
+- **releaseAlarm**: number of times the alarmManagement.release() function has been invoked.
+- **updateEntityRequestsOk**: number of times the ngsiService.sendUpdateValue() function has been invoked
+ successfully.
+- **updateEntityRequestsError**: number of times the ngsiService.sendUpdateValue() function has been invoked and
+ failed.
+
+More values will be added in the future to the library. The applications using the library can add values to the Stats
+Registry just by using the following function:
+
+```javascript
+iotagentLib.statsRegistry.add('statName', statIncrementalValue, callback);
+```
+
+The first time this function is invoked, it will add the new stat to the registry. Subsequent calls will add the value
+to the specified stat.
+
+### Alarm module
+
+The library provide an alarm module that can be used to track through the logs alarms raised in the IoTAgent. This
+module provides:
+
+- Two functions to raise and release and alarm (`raise()` and `release()`): every alarm is identified by a name and a
+ description. When the alarm is raised, an error with the text `Raising [%s]` is logged. When the alarm is released,
+ the corresponding text, `Releasing [%s]` is logged. If an alarm is raised multiple times, it is only logged once. If
+ its released multiple times it is only released once. Releasing a non-existing alarm has no effect.
+
+- Functions to list all the raised alarms and clean all the alarms (`list()` and `clean()`).
+
+- A function to instrument other functions, so when one of that functions return an error, an alarm is raised, and
+ when it returns a success an alarm is ceased (`intercept()`).
+
+All this functions can be accessed through the `.alarms` attribute of the library.
+
+### Transactions
+
+The library implements a concept of transactions, in order to follow the execution flow the library follows when
+treating requests entering both from the North and the South ports of the IoT Agent.
+
+To follow the transactions, a new Domain is created for each incoming request; in the case of requests received on the
+North Port of the IoT Agent, this domain is automatically created by a Express middleware, and no further action is
+needed from the user. For the case of requests received on the South Port of the IoT Agent, the user is responsible of
+creating an stopping the transaction, using the `ensureSouthboundDomain` and `finishSouthBoundTransaction`. In this
+case, the transaction will last from the invocation to the former to the invocation of the latter.
+
+The Transaction Correlator is used along all the IoT Platform to follow the trace of a transaction between multiple
+components. To do so, in all the HTTP requests sent to other components of the platform, a custom header named
+`Fiware-Correlator` is sent with the correlator of the transaction that generated the request. If a component of the
+platform receives a request containing this header that starts a transaction, the component will create the transaction
+with the received correlator, instead of creating a new one. If the header is not present or the transaction originates
+in the component, the transaction ID in this component will be used as the correlator.
+
+During the duration of a transaction, all the log entries created by the code will write the current Transaction ID and
+correlator for the operation being executed.
+
+### Library overview
+
+In order to use the library, add the following dependency to your package.json file:
+
+```json
+"iotagent-node-lib": "*"
+```
+
+In order to use this library, first you must require it:
+
+```javascript
+var iotagentLib = require('iotagent-node-lib');
+```
+
+The library supports four groups of features, one for each direction of the communication: client-to-server and
+server-to-client (and each flow both for the client and the server). Each feature set is defined in the following
+sections.
+
+### Function reference
+
+> **WARNING** This section is outdated. Functions described here may be outdated and not reflect the current
+> implementation of the IoT Agent Library. You could have a look to [iotagentLib.js](./lib/iotagentLib.js) file to see
+> the current detail of functions implemented.
+
+The following fucntions are available in the library:
+
+- [iotagentLib.activate()](#iotagentlibactivate)
+- [iotagentLib.deactivate()](#iotagentlibdeactivate)
+- [iotagentLib.register()](#iotagentlibregister)
+- [iotagentLib.unregister()](#iotagentlibunregister)
+- [iotagentLib.update()](#iotagentlibupdate)
+- [iotagentLib.setCommandResult()](#iotagentlibsetcommandresult)
+- [iotagentLib.listDevices()](#iotagentliblistdevices)
+- [iotagentLib.setDataUpdateHandler()](#iotagentlibsetdataupdatehandler)
+- [iotagentLib.setDataQueryHandler()](#iotagentlibsetdataqueryhandler)
+- [iotagentLib.setNotificationHandler()](#iotagentlibsetnotificationhandler)
+- [iotagentLib.setCommandHandler()](#iotagentlibsetcommandhandler)
+- [iotagentLib.setMergePatchHandler()](#iotagentlibsetmergepatchhandler)
+- [iotagentLib.setProvisioningHandler()](#iotagentlibsetprovisioninghandler)
+- [iotagentLib.setRemoveDeviceHandler()](#iotagentlibsetremovedevicehandler)
+- [iotagentLib.setConfigurationHandler()](#iotagentlibsetconfigurationhandler)
+- [iotagentLib.setRemoveConfigurationHandler()](#iotagentlibsetremoveconfigurationhandler)
+- [iotagentLib.getDevice()](#iotagentlibgetdevice)
+- [iotagentLib.getDeviceByName()](#iotagentlibgetdevicebyname)
+- [iotagentLib.getDevicesByAttribute()](#iotagentlibgetdevicesbyattribute)
+- [iotagentLib.retrieveDevice()](#iotagentlibretrievedevice)
+- [iotagentLib.mergeDeviceWithConfiguration()](#iotagentlibmergedevicewithconfiguration)
+- [iotagentLib.getConfiguration()](#iotagentlibgetconfiguration)
+- [iotagentLib.findConfiguration()](#iotagentlibfindconfiguration)
+- [iotagentLib.getEffectiveApiKey()](#iotagentlibgeteffectiveapikey)
+- [iotagentLib.subscribe()](#iotagentlibsubscribe)
+- [iotagentLib.unsubscribe()](#iotagentlibunsubscribe)
+- [iotagentLib.ensureSouthboundDomain()](#iotagentlibensuresouthbounddomain)
+- [iotagentLib.finishSouthBoundTransaction()](#iotagentlibfinishsouthboundtransaction)
+- [iotagentLib.startServer()](#iotagentlibstartserver)
+- [iotagentLib.request()](#iotagentlibrequest)
+
+##### iotagentLib.activate()
+
+###### Signature
+
+```javascript
+function activate(newConfig, callback)
+```
+
+###### Description
+
+Activates the IoT Agent to start listening for NGSI Calls (acting as a Context Provider). It also creates the device
+registry for the IoT Agent (based on the deviceRegistry.type configuration option).
+
+###### Params
+
+- newConfig: Configuration of the Context Server (described in the [Configuration](../admin.md#configuration)
+ section).
+
+##### iotagentLib.deactivate()
+
+###### Signature
+
+```javascript
+function deactivate(callback)
+```
+
+###### Description
+
+Stops the HTTP server.
+
+###### Params
+
+##### iotagentLib.register()
+
+###### Signature
+
+```javascript
+function registerDevice(deviceObj, callback)
+```
+
+###### Description
+
+Register a new device in the IoT Agent. This registration will also trigger a Context Provider registration in the
+Context Broker for all its lazy attributes.
+
+The device Object can have the following attributes:
+
+- `id`: Device ID of the device.
+- `type`: type to be assigned to the device.
+- `name`: name that will be used for the Entity representing the device in the Context Broker.
+- `service`: name of the service associated with the device.
+- `subservice`: name of the subservice associated with th device.
+- `lazy`: list of lazy attributes with their types.
+- `active`: list of active attributes with their types.
+- `staticAttributes`: list of NGSI attributes to add to the device entity 'as is' in updates, queries and
+ registrations.
+- `internalAttributes`: optional section with free format, to allow specific IoT Agents to store information along
+ with the devices in the Device Registry.
+
+The device `id` and `type` are required fields for any registration. The rest of the attributes are optional, but, if
+they are not present in the function call arguments, the type must be registered in the configuration, so the service
+can infer their default values from the configured type. If an optional attribute is not given in the parameter list and
+there isn't a default configuration for the given type, a TypeNotFound error is raised.
+
+If the device has been previously preprovisioned, the missing data will be completed with the values from the registered
+device.
+
+###### Params
+
+- deviceObj: object containing all the information about the device to be registered (mandatory).
+
+##### iotagentLib.unregister()
+
+###### Signature
+
+```javascript
+function unregisterDevice(id, service, subservice, callback)
+```
+
+###### Description
+
+Unregister a device from the Context broker and the internal registry.
+
+###### Params
+
+- id: Device ID of the device to register.
+- service: Service of the device to unregister.
+- subservice: Subservice inside the service for the unregistered device.
+
+##### iotagentLib.update()
+
+###### Signature
+
+```javascript
+function update(entityName, attributes, typeInformation, token, callback)
+```
+
+###### Description
+
+Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
+array should comply to the NGSI's attribute format.
+
+###### Params
+
+- entityName: Name of the entity to register.
+- attributes: Attribute array containing the values to update.
+- typeInformation: Configuration information for the device.
+- token: User token to identify against the PEP Proxies (optional).
+
+##### iotagentLib.setCommandResult()
+
+###### Signature
+
+```javascript
+function setCommandResult(entityName, resource, apikey, commandName, commandResult, status, deviceInformation, callback)
+```
+
+###### Description
+
+Update the result of a command in the Context Broker. The result of the command has two components: the result of the
+command itself will be represented with the suffix `_info` in the entity while the status is updated in the attribute
+with the `_status` suffix.
+
+###### Params
+
+- entityName: Name of the entity holding the command.
+- resource: Resource name of the endpoint the device is calling.
+- apikey: Apikey the device is using to send the values (can be the empty string if none is needed).
+- commandName: Name of the command whose result is being updated.
+- commandResult: Result of the command in string format.
+- deviceInformation: Device information, including security and service information. (optional).
+
+##### iotagentLib.listDevices()
+
+###### Signature
+
+```javascript
+function listDevices(callback)
+function listDevices(limit, offset, callback)
+function listDevices(service, subservice, limit, offset, callback)
+```
+
+###### Description
+
+Return a list of all the devices registered in the specified service and subservice. This function can be invoked in
+three different ways:
+
+- with just one parameter (the callback)
+- with three parameters (service, subservice and callback)
+- or with five parameters (including limit and offset).
+
+###### Params
+
+- service: service from where the devices will be retrieved.
+- subservice: subservice from where the devices will be retrieved.
+- limit: maximum number of results to retrieve (optional).
+- offset: number of results to skip from the listing (optional).
+
+##### iotagentLib.setDataUpdateHandler()
+
+###### Signature
+
+```javascript
+function setDataUpdateHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for Entity update requests. This handler will be called whenever an update request arrives
+with the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). Every object within of
+the `attributes` array contains `name`, `type` and `value` attributes, and may also include additional attributes for
+`metadata` and `datasetId`. The handler is in charge of updating the corresponding values in the devices with the
+appropriate protocol.
+
+Once all the updates have taken place, the callback must be invoked with the updated Context Element. E.g.:
+
+```javascript
+callback(null, {
+ type: 'TheType',
+ isPattern: false,
+ id: 'EntityID',
+ attributes: [
+ {
+ name: 'lumniscence',
+ type: 'Lumens',
+ value: '432'
+ }
+ ]
+});
+```
+
+In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
+entity, and all the results will be combined into a single response.
+
+###### Params
+
+- newHandler: User handler for update requests
+
+##### iotagentLib.setDataQueryHandler()
+
+###### Signature
+
+```javascript
+function setDataQueryHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for Entity query requests. This handler will be called whenever a query request arrives, with
+the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
+all the corresponding information from the devices and return a NGSI entity with the requested values.
+
+The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
+
+```javascript
+callback(null, {
+ type: 'TheType',
+ isPattern: false,
+ id: 'EntityID',
+ attributes: [
+ {
+ name: 'lumniscence',
+ type: 'Lumens',
+ value: '432'
+ }
+ ]
+});
+```
+
+In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
+entity, and all the results will be combined into a single response.
+
+###### Params
+
+- newHandler: User handler for query requests.
+
+##### iotagentLib.setNotificationHandler()
+
+###### Signature
+
+```javascript
+function setNotificationHandler(newHandler)
+```
+
+###### Description
+
+Sets the new handler for incoming notifications. The notifications are sent by the Context Broker based on the IoT Agent
+subscriptions created with the `subscribe()` function.
+
+The handler must adhere to the following signature:
+
+```javascript
+function mockedHandler(device, data, callback)
+```
+
+The `device` parameter contains the device object corresponding to the entity whose changes were notified with the
+incoming notification. Take into account that multiple entities may be modified with each single notification. The
+handler will be called once for each one of those entities.
+
+The `data` parameter is an array with all the attributes that were requested in the subscription and its respective
+values.
+
+The handler is expected to call its callback once with no parameters (failing to do so may cause unexpected behaviors in
+the IoT Agent).
+
+##### iotagentLib.setCommandHandler()
+
+###### Signature
+
+```javascript
+function setCommandHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for registered entity commands. This handler will be called whenever a command request
+arrives, with the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler
+must retrieve all the corresponding information from the devices and return a NGSI entity with the requested values.
+
+The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
+
+```javascript
+callback(null, {
+ type: 'TheType',
+ isPattern: false,
+ id: 'EntityID',
+ attributes: [
+ {
+ name: 'lumniscence',
+ type: 'Lumens',
+ value: '432'
+ }
+ ]
+});
+```
+
+In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
+entity, and all the results will be combined into a single response. Only IoT Agents which deal with actuator devices
+will include a handler for commands.
+
+###### Params
+
+- newHandler: User handler for command requests.
+
+##### iotagentLib.setMergePatchHandler()
+
+###### Signature
+
+```javascript
+function setMergePatchHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for NGSI-LD Entity [merge-patch](https://datatracker.ietf.org/doc/html/rfc7386) requests. This
+handler will be called whenever a merge-patch request arrives, with the following parameters: (`id`, `type`, `service`,
+`subservice`, `attributes`, `callback`). The handler must retrieve all the corresponding information from the devices
+and return a NGSI entity with the requested values.
+
+The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
+
+```javascript
+callback(null, {
+ type: 'TheType',
+ isPattern: false,
+ id: 'EntityID',
+ attributes: [
+ {
+ name: 'lumniscence',
+ type: 'Lumens',
+ value: '432'
+ }
+ ]
+});
+```
+
+In the case of NGSI-LD requests affecting multiple entities, this handler will be called multiple times. Since
+merge-patch is an advanced function, not all IoT Agents will include a handler for merge-patch.
+
+###### Params
+
+- newHandler: User handler for merge-patch requests.
+
+##### iotagentLib.setProvisioningHandler()
+
+###### Signature
+
+```javascript
+function setProvisioningHandler (newHandler)
+```
+
+###### Description
+
+Sets the new user handler for the provisioning of devices. This handler will be called every time a new device is
+created.
+
+The handler must adhere to the following signature:
+
+```javascript
+function(newDevice, callback)
+```
+
+The `newDevice` parameter will contain the newly created device. The handler is expected to call its callback with no
+parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
+
+##### iotagentLib.setRemoveDeviceHandler()
+
+###### Signature
+
+```javascript
+function setRemoveDeviceHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for the removal of a device. This handler will be called every time a device is removed.
+
+The handler must adhere to the following signature:
+
+```javascript
+function(deviceToDelete, callback)
+```
+
+The `deviceToDelete` parameter will contain the device to be deleted. The handler is expected to call its callback with
+no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
+
+##### iotagentLib.setConfigurationHandler()
+
+###### Signature
+
+```javascript
+function setConfigurationHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for the configuration updates. This handler will be called every time a new configuration is
+created or an old configuration is updated.
+
+The handler must adhere to the following signature:
+
+```javascript
+function(newConfiguration, callback)
+```
+
+The `newConfiguration` parameter will contain the newly created configuration. The handler is expected to call its
+callback with no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
+
+For the cases of multiple updates (a single Device Configuration POST that will create several device groups), the
+handler will be called once for each of the config groups (both in the case of the creations and the updates).
+
+The handler will be also called in the case of updates related to config groups. In that situation, the
+`newConfiguration` parameter contains also the fields needed to identify the configuration to be updated, i.e.,
+`service`, `subservice`, `resource` and `apikey`.
+
+##### iotagentLib.setRemoveConfigurationHandler()
+
+###### Signature
+
+```javascript
+function setRemoveConfigurationHandler(newHandler)
+```
+
+###### Description
+
+Sets the new user handler for the removal of configuratios. This handler will be called every time a configuration is
+removed.
+
+The handler must adhere to the following signature:
+
+```javascript
+function(configurationToDelete, callback)
+```
+
+The `configurationToDelete` parameter will contain the configuration to be deleted. The handler is expected to call its
+callback with no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
+
+##### iotagentLib.getDevice()
+
+###### Signature
+
+```javascript
+function getDevice(deviceId, service, subservice, callback)
+```
+
+###### Description
+
+Retrieve all the information about a device from the device registry.
+
+###### Params
+
+- deviceId: ID of the device to be found.
+- service: Service for which the requested device.
+- subservice: Subservice inside the service for which the device is requested.
+
+##### iotagentLib.getDeviceByName()
+
+###### Signature
+
+```javascript
+function getDeviceByName(deviceName, service, subservice, callback)
+```
+
+###### Description
+
+Retrieve a device from the registry based on its entity name.
+
+###### Params
+
+- deviceName: Name of the entity associated to a device.
+- service: Service the device belongs to.
+- subservice: Division inside the service.
+
+##### iotagentLib.getDevicesByAttribute()
+
+###### Signature
+
+```javascript
+function getDevicesByAttribute(attributeName, attributeValue, service, subservice, callback)
+```
+
+###### Description
+
+Retrieve all the devices having an attribute named `name` with value `value`.
+
+###### Params
+
+- name: name of the attribute to match.
+- value: value to match in the attribute.
+- service: Service the device belongs to.
+- subservice: Division inside the service.
+
+##### iotagentLib.retrieveDevice()
+
+###### Signature
+
+```javascript
+function retrieveDevice(deviceId, apiKey, callback)
+```
+
+###### Description
+
+Retrieve a device from the device repository based on the given APIKey and DeviceID, creating one if none is found for
+the given data.
+
+###### Params
+
+- deviceId: Device ID of the device that wants to be retrieved or created.
+- apiKey: APIKey of the Device Group (or default APIKey).
+
+##### iotagentLib.mergeDeviceWithConfiguration()
+
+###### Signature
+
+```javascript
+function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuration, callback)
+```
+
+###### Description
+
+Complete the information of the device with the information in the configuration group (with precedence of the device).
+The first argument indicates what fields would be merged.
+
+###### Params
+
+- fields: Fields that will be merged.
+- defaults: Default values fot each of the fields.
+- deviceData: Device data.
+- configuration: Configuration data.
+
+##### iotagentLib.getConfiguration()
+
+###### Signature
+
+```javascript
+function getConfiguration(resource, apikey, callback)
+```
+
+###### Description
+
+Gets the device group identified by the given (`resource`, `apikey`) pair.
+
+###### Params
+
+- resource: representation of the configuration in the IoT Agent (dependent on the protocol) .
+- apikey: special key the devices will present to prove they belong to a particular configuration.
+
+##### iotagentLib.findConfiguration()
+
+###### Signature
+
+```javascript
+function findConfiguration(service, subservice, callback)
+```
+
+###### Description
+
+Find a device group based on its service and subservice.
+
+###### Params
+
+- service: name of the service of the configuration.
+- subservice: name of the subservice of the configuration.
+
+##### iotagentLib.getEffectiveApiKey()
+
+###### Signature
+
+```javascript
+function getEffectiveApiKey(service, subservice, type, callback)
+```
+
+###### Description
+
+Get the API Key for the selected service if there is any, or the default API Key if a specific one does not exist.
+
+###### Params
+
+- service: Name of the service whose API Key we are retrieving.
+- subservice: Name of the subservice whose API Key we are retrieving.
+- type: Type of the device.
+
+##### iotagentLib.subscribe()
+
+###### Signature
+
+```javascript
+function subscribe(device, triggers, content, callback)
+```
+
+###### Description
+
+Creates a subscription for the IoTA to the entity representing the selected device.
+
+###### Params
+
+- device: Object containing all the information about a particular device.
+- triggers: Array with the names of the attributes that would trigger the subscription
+- content: Array with the names of the attributes to retrieve in the notification.
+
+##### iotagentLib.unsubscribe()
+
+###### Signature
+
+```javascript
+function unsubscribe(device, id, callback)
+```
+
+###### Description
+
+Removes a single subscription from the selected device, identified by its ID.
+
+###### Params
+
+- `device`: Object containing all the information about a particular device.
+- `id`: ID of the subscription to remove.
+
+##### iotagentLib.ensureSouthboundDomain()
+
+###### Signature
+
+```javascript
+function ensureSouthboundTransaction(context, callback)
+```
+
+###### Description
+
+Ensures that the current operation is executed inside a transaction with all the information needed for the appropriate
+platform logging: start date, transaction ID and correlator in case one is needed. If the function is executed in the
+context of a previous transaction, just the context is changed (and the Transaction ID and start time are kept).
+
+###### Params
+
+- context: New context data for the transaction.
+
+##### iotagentLib.finishSouthBoundTransaction()
+
+###### Signature
+
+```javascript
+function finishSouthboundTransaction(callback)
+```
+
+###### Description
+
+Terminates the current transaction, if there is any, cleaning its context.
+
+##### iotagentLib.startServer()
+
+###### Signature
+
+```javascript
+function startServer(newConfig, iotAgent, callback)
+```
+
+###### Description
+
+Start the HTTP server either in single-thread or multi-thread (multi-core) based on the value of _multiCore_ variable
+(described in the [Configuration](../admin.md#configuration) section). If the value is `False` (either was directly
+specified `False` in the `config.js` or it was not specified and by default is assigned `False`), it is a normal
+(single-thread) behaviour. Nevertheless, if _multiCore_ is `True`, the IoTAgent is executed in multi-thread environment.
+
+The number of parallel processes is calculated based on the number of available CPUs. In case of some of the process
+unexpectedly dead, a new process is created automatically to keep always the maximum of them working in parallel.
+
+> Note: `startServer()` initializes the server but it does not activate the library. The function in the Node Lib will
+> call the `iotAgent.start()` in order to complete the activation of the library. Therefore, it is expected that the IoT
+> Agent implement the `iotAgent.start()` function with the proper invocation to the `iotAgentLib.activate()`.
+
+###### Params
+
+- newConfig: Configuration of the Context Server (described in the [Configuration](../admin.md#configuration)
+ section).
+- iotAgent: The IoT Agent Objects, used to start the agent.
+- callback: The callback function.
+
+##### iotagentLib.request()
+
+###### Signature
+
+```javascript
+function request(options, callback)
+```
+
+###### Description
+
+Make a direct HTTP request using the underlying request library (currently [got](https://github.com/sindresorhus/got)),
+this is useful when creating agents which use an HTTP transport for their southbound commands, and removes the need for
+the custom IoT Agent to import its own additional request library
+
+###### Params
+
+- options: definition of the request (see
+ [got options](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md) for more details). The
+ following attributes are currently exposed.
+ - `method` - HTTP Method
+ - `searchParams` - query string params
+ - `qs` - alias for query string params
+ - `headers`
+ - `responseType` - either `text` or `json`. `json` is the default
+ - `json` - a supplied JSON object as the request body
+ - `body` - any ASCII text as the request body. It takes precedence over `json` if both are provided at the same
+ time (not recommended).
+ - `url` - the request URL
+ - `uri` - alternative alias for the request URL.
+- callback: The callback currently returns an `error` Object, the `response` and `body`. The `body` is parsed to a
+ JSON object if the `responseType` is JSON.
+
+#### Generic middlewares
+
+This collection of utility middlewares is aimed to be used to north of the IoT Agent Library, as well as in other
+HTTP-based APIs of the IoT Agents. All the middlewares follow the Express convention of `(req, res, next)` objects, so
+this information will not be repeated in the descriptions for the middleware functions. All the middlewares can be added
+to the servers using the standard Express mechanisms.
+
+##### iotagentLib.middlewares.handleError()
+
+###### Signature
+
+```javascript
+function handleError(error, req, res, next)
+```
+
+###### Description
+
+Express middleware for handling errors in the IoTAs. It extracts the code information to return from the error itself
+returning 500 when no error code has been found.
+
+##### iotagentLib.middlewares.traceRequest()
+
+###### Signature
+
+```javascript
+function traceRequest(req, res, next)
+```
+
+###### Description
+
+Express middleware for tracing the complete request arriving to the IoTA in debug mode.
+
+##### iotagentLib.middlewares.changeLogLevel()
+
+###### Signature
+
+```javascript
+function changeLogLevel(req, res, next)
+```
+
+###### Description
+
+Changes the log level to the one specified in the request.
+
+##### iotagentLib.middlewares.ensureType()
+
+###### Signature
+
+```javascript
+function ensureType(req, res, next)
+```
+
+###### Description
+
+Ensures the request type is one of the supported ones.
+
+##### iotagentLib.middlewares.validateJson()
+
+###### Signature
+
+```javascript
+function validateJson(template)
+```
+
+###### Description
+
+Generates a Middleware that validates incoming requests based on the JSON Schema template passed as a parameter.
+
+Returns an Express middleware used in request validation with the given template.
+
+###### Params
+
+- _template_: JSON Schema template to validate the request.
+
+##### iotagentLib.middlewares.retrieveVersion()
+
+###### Signature
+
+```javascript
+function retrieveVersion(req, res, next)
+```
+
+###### Description
+
+Middleware that returns all the IoTA information stored in the module.
+
+##### iotagentLib.middlewares.setIotaInformation()
+
+###### Signature
+
+```javascript
+function setIotaInformation(newIoTAInfo)
+```
+
+###### Description
+
+Stores the information about the IoTAgent for further use in the `retrieveVersion()` middleware.
+
+###### Params
+
+- _newIoTAInfo_: Object containing all the IoTA Information.
+
+## DB Models (from API document)
+
+> **WARNING** This section is outdated. DB fields described here may be outdated and not reflect the current
+> implementation of the IoT Agent Library.
+
+The following sections describe the models used in the database to store the information about the devices and the
+config groups.
+
+### Config group model
+
+The table below shows the information held in the Config group provisioning resource and the correspondence between the
+API resource fields and the same fields in the database model.
+
+You can find the description of the fields in the config group datamodel of the
+[API document](../api.md#config-group-datamodel).
+
+| Payload Field | DB Field | Note |
+| ------------------------------ | ------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `service` | `service` | |
+| `subservice` | `subservice` | |
+| `resource` | `resource` | |
+| `apikey` | `apikey` | |
+| `timestamp` | `timestamp` | |
+| `entity_type` | `entity_type` | |
+| `trust` | `trust` | |
+| `cbHost` | `cbHost` | |
+| `lazy` | `lazy` | |
+| `commands` | `commands` | |
+| `attributes` | `attributes` | |
+| `static_attributes` | `staticAttributes` | |
+| `internal_attributes` | `internalAttributes` | |
+| `explicitAttrs` | `explicitAttrs` | |
+| `entityNameExp` | `entityNameExp` | |
+| `ngsiVersion` | `ngsiVersion` | |
+| `defaultEntityNameConjunction` | `defaultEntityNameConjunction` | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
+| `autoprovision` | `autoprovision` | |
+
+### Device model
+
+The table below shows the information held in the Device resource. The table also contains the correspondence between
+the API resource fields and the same fields in the database model.
+
+You can find the description of the fields in the config group datamodel of the
+[API document](../api.md#device-datamodel).
+
+| Payload Field | DB Field | Note |
+| --------------------- | -------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
+| `device_id` | `id` | |
+| `service` | `service` | |
+| `service_path` | `subservice` | |
+| `entity_name` | `name` | |
+| `entity_type` | `type` | |
+| `timezone` | `timezone` | |
+| `timestamp` | `timestamp` | |
+| `apikey` | `apikey` | |
+| `endpoint` | `endpoint` | |
+| `protocol` | `protocol` | Name of the device protocol, for its use with an IoT Manager. IE: IoTA-UL |
+| `transport` | `transport` | |
+| `attributes` | `active` | |
+| `lazy` | `lazy` | |
+| `commands` | `commands` | |
+| `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration. I.E:LWM2M mappings from object URIs to attributes |
+| `static_attributes` | `staticAttributes` | |
+| `explicitAttrs` | `explicitAttrs` | |
+| `ngsiVersion` | `ngsiVersion` | |
+
+## Developing a new IoT Agent
+
+> **WARNING** This section is outdated. Methods and steps described here may be outdated and not reflect the current
+> implementation of the IoT Agent Library. You could have a look to other IoT Agents developed using the IoT Agent
+> Library to get a better idea of how to use it, like the
+> [IoT Agent JSON](http://www.github.com/telefonicaid/iotagent-json)
+
+This section's goal is to show how to develop a new IoT Agent step by step. To do so, a simple invented HTTP protocol
+will be used, so it can be tested with simple command-line instructions as `curl` and `nc`.
+
+### Protocol
+
+The invented protocol will be freely adapted from
+[Ultralight 2.0](https://github.com/telefonicaid/fiware-IoTAgent-Cplusplus/blob/develop/doc/modules.md#ultra-light-agent).
+Whenever a device wants to send an update, it will send a request as the following:
+
+```bash
+curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
+```
+
+Where:
+
+- **i**: is the device ID.
+- **k**: the API Key for the device's service.
+- **d**: the data payload, consisting of key-value pairs separated by a pipe (`|`), with each pair separated by comma
+ (`,`);
+
+### Requirements
+
+This tutorial expects a Node.js v8 (at least) installed and working on your machine. It also expects you to have access
+to a Context Broker (without any security proxies).
+
+### Basic IoT Agent
+
+In this first chapter, we will just develop an IoT Agent with a fully connected North Port. This will send and receive
+NGSI traffic and can be administered using the IoT Agent's Device Provisioning API. The South Port will remain
+unconnected and no native protocol traffic will be sent to the devices. This may seem useless (and indeed it is) but it
+will serve us well on showing the basic steps in the creation of an IoT Agent.
+
+First of all, we have to create the Node project. Create a folder to hold your project and type the following
+instruction:
+
+```bash
+npm init
+```
+
+This will create the `package.json` file for our project. Now, add the following lines to your project file:
+
+```json
+ "dependencies": {
+ "iotagent-node-lib": "*"
+ },
+
+```
+
+And install the dependencies, executing, as usual:
+
+```bash
+npm install
+```
+
+The first step is to write a configuration file, that will be used to tune the behavior of our IOTA. The contents can be
+copied from the following example:
+
+```javascript
+var config = {
+ logLevel: 'DEBUG',
+ contextBroker: {
+ host: 'localhost',
+ port: '1026'
+ },
+ server: {
+ port: 4041
+ },
+ deviceRegistry: {
+ type: 'memory'
+ },
+ types: {},
+ service: 'howtoService',
+ subservice: '/howto',
+ providerUrl: 'http://localhost:4041',
+ defaultType: 'Thing'
+};
+
+module.exports = config;
+```
+
+Create a `config.js` file with it in the root folder of your project. Remember to change the Context Broker IP to your
+local Context Broker.
+
+Now we can begin with the code of our IoT Agent. The very minimum code we need to start an IoT Agent is the following:
+
+```javascript
+var iotAgentLib = require('iotagent-node-lib'),
+ config = require('./config');
+
+iotAgentLib.activate(config, function (error) {
+ if (error) {
+ console.log('There was an error activating the IOTA');
+ process.exit(1);
+ }
+});
+```
+
+The IoT Agent is now ready to be used. Execute it with the following command:
+
+```bash
+node index.js
+```
+
+The North Port interface should now be fully functional, i.e.: management of device registrations and config groups.
+
+### IoT Agent With Active attributes
+
+In the previous section we created an IoT Agent that exposed just the North Port interface, but that was pretty useless
+(aside from its didactic use). In this section we are going to create a simple South Port interface. It's important to
+remark that the nature of the traffic South of the IoT Agent itself has nothing to do with the creation process of an
+IoT Agent. Each device protocol will use its own mechanisms and it is up to the IoT Agent developer to find any
+libraries that would help him in its development. In this example, we will use Express as such library.
+
+In order to add the Express dependency to your project, add the following line to the `dependencies` section of the
+`package.json`:
+
+```json
+ "express": "*",
+```
+
+The require section would end up like this (the standard `http` module is also needed):
+
+```javascript
+var iotAgentLib = require('iotagent-node-lib'),
+ http = require('http'),
+ express = require('express'),
+ config = require('./config');
+```
+
+And install the dependencies as usual with `npm install`. You will have to require both `express` and `http` in your
+code as well.
+
+Now, in order to accept connections in our code, we have to start express first. With this purpose in mind, we will
+create a new function `initSouthbound()`, that will be called from the initialization code of our IoT Agent:
+
+```javascript
+function initSouthbound(callback) {
+ southboundServer = {
+ server: null,
+ app: express(),
+ router: express.Router()
+ };
+
+ southboundServer.app.set('port', 8080);
+ southboundServer.app.set('host', '0.0.0.0');
+
+ southboundServer.router.get('/iot/d', manageULRequest);
+ southboundServer.server = http.createServer(southboundServer.app);
+ southboundServer.app.use('/', southboundServer.router);
+ southboundServer.server.listen(southboundServer.app.get('port'), southboundServer.app.get('host'), callback);
+}
+```
+
+This Express code sets up a HTTP server, listening in the 8080 port, that will handle incoming requests targeting path
+`/iot/d` using the middleware `manageULRequest()`. This middleware will contain all the logic south of the IoT Agent,
+and the library methods we need in order to progress the information to the Context Broker. The code of this middleware
+would be as follows:
+
+```javascript
+function manageULRequest(req, res, next) {
+ var values;
+
+ iotAgentLib.retrieveDevice(req.query.i, req.query.k, function (error, device) {
+ if (error) {
+ res.status(404).send({
+ message: "Couldn't find the device: " + JSON.stringify(error)
+ });
+ } else {
+ values = parseUl(req.query.d, device);
+ iotAgentLib.update(device.name, device.type, '', values, device, function (error) {
+ if (error) {
+ res.status(500).send({
+ message: 'Error updating the device'
+ });
+ } else {
+ res.status(200).send({
+ message: 'Device successfully updated'
+ });
+ }
+ });
+ }
+ });
+}
+```
+
+For this middleware we have made use of a function `parseUl()` that parses the data payload and transforms it in the
+data object expected by the update function (i.e.: an attribute array with NGSI syntax):
+
+```javascript
+function parseUl(data, device) {
+ function findType(name) {
+ for (var i = 0; i < device.active.length; i++) {
+ if (device.active[i].name === name) {
+ return device.active[i].type;
+ }
+ }
+
+ return null;
+ }
+
+ function createAttribute(element) {
+ var pair = element.split('|'),
+ attribute = {
+ name: pair[0],
+ value: pair[1],
+ type: findType(pair[0])
+ };
+
+ return attribute;
+ }
+
+ return data.split(',').map(createAttribute);
+}
+```
+
+Here as an example of the output of the function return for the UL payload `t|15,l|19.6`:
+
+```json
+[
+ {
+ "name": "t",
+ "type": "celsius",
+ "value": "15"
+ },
+ {
+ "name": "l",
+ "type": "meters",
+ "value": "19.6"
+ }
+]
+```
+
+The last thing to do is to invoke the initialization function inside the IoT Agent startup function. The next excerpt
+show the modifications in the `activate()` function:
+
+```javascript
+iotAgentLib.activate(config, function (error) {
+ if (error) {
+ console.log('There was an error activating the IOTA');
+ process.exit(1);
+ } else {
+ initSouthbound(function (error) {
+ if (error) {
+ console.log('Could not initialize South bound API due to the following error: %s', error);
+ } else {
+ console.log('Both APIs started successfully');
+ }
+ });
+ }
+});
+```
+
+Some logs were added in this piece of code to help debugging.
+
+Once the IOTA is finished the last thing to do is to test it. To do so, launch the IoT Agent and provision a new device
+(an example for provisioning can be found in the `examples/howtoProvisioning1.json` file). Once the device is
+provisioned, send a new measure by using the example command:
+
+```bash
+curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
+```
+
+Now you should be able to see the measures in the Context Broker entity of the device.
+
+### IOTA With Lazy attributes
+
+#### Previous considerations
+
+The IoT Agents also give the possibility for the device to be asked about the value of one of its measures, instead of
+reporting it. In order to do so, the device must be capable of receiving messages of some kind. In this case, we are
+going to simulate an HTTP server with `nc` in order to see the values sent by the IOTA. We also have to decide a syntax
+for the protocol request for asking the device about a measure. For clarity, we will use the same HTTP GET request we
+used to report a measure, but indicating the attribute to ask instead of the data payload. Something like:
+
+```bash
+curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&q=t,l' -i
+```
+
+In a real implementation, the server will need to know the URL and port where the devices are listening, in order to
+send the request to the appropriate device. For this example, we will assume that the device is listening in port 9999
+in localhost. For more complex cases, the mechanism to bind devices to addresses would be IoT-Agent-specific (e.g.: the
+OMA Lightweight M2M IoT Agent captures the address of the device in the device registration, and stores the
+device-specific information in a MongoDB document).
+
+Being lazy attributes of a read/write nature, another syntax has to be declared for updating. This syntax will mimic the
+one used for updating the server:
+
+```
+curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
+```
+
+Both types of calls to the device will be distinguished by the presence or absence of the `d` and `q` attributes.
+
+A HTTP request library will be needed in order to make those calls. To this extent, `mikeal/request` library will be
+used. In order to do so, add the following require statement to the initialization code:
+
+```javascript
+request = require('request');
+```
+
+and add the `request` dependency to the `package.json` file:
+
+```json
+ "dependencies": [
+ [...]
+
+ "request": "*",
+
+ ]
+```
+
+The require section should now look like this:
+
+```javascript
+var iotAgentLib = require('iotagent-node-lib'),
+ http = require('http'),
+ express = require('express'),
+ request = require('request'),
+ config = require('./config');
+```
+
+#### Implementation
+
+##### QueryContext implementation
+
+The main step to complete in order to implement the Lazy attributes mechanism in the IoT Agent is to provide handlers
+for the context provisioning requests. At this point, we should provide two handlers: the `/v2/op/update` and the
+`/v2/op/query` handlers. To do so, we must first define the handlers themselves:
+
+```javascript
+function queryContextHandler(id, type, service, subservice, attributes, callback) {
+ var options = {
+ url: 'http://127.0.0.1:9999/iot/d',
+ method: 'GET',
+ qs: {
+ q: attributes.join()
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (error) {
+ callback(error);
+ } else {
+ callback(null, createResponse(id, type, attributes, body));
+ }
+ });
+}
+```
+
+The queryContext handler is called whenever a `/v2/op/query` request arrives at the North port of the IoT Agent. It is
+invoked once for each entity requested, passing the entity ID and Type as the parameters, as well as a list of the
+attributes that are requested. In our case, the handler uses this parameters to compose a request to the device. Once
+the results of the device are returned, the values are returned to the caller, in the NGSI attribute format.
+
+In order to format the response from the device in a readable way, we created a `createResponse()` function that maps
+the values to its correspondent attributes. This function assumes the type of all the attributes is "string" (this will
+not be the case in a real scenario, where the IoT Agent should retrieve the associated device to guess the type of its
+attributes). Here is the code for the `createResponse()` function:
+
+```javascript
+function createResponse(id, type, attributes, body) {
+ var values = body.split(','),
+ responses = [];
+
+ for (var i = 0; i < attributes.length; i++) {
+ responses.push({
+ name: attributes[i],
+ type: 'string',
+ value: values[i]
+ });
+ }
+
+ return {
+ id: id,
+ type: type,
+ attributes: responses
+ };
+}
+```
+
+##### UpdateContext implementation
+
+```javascript
+function updateContextHandler(id, type, service, subservice, attributes, callback) {
+ var options = {
+ url: 'http://127.0.0.1:9999/iot/d',
+ method: 'GET',
+ qs: {
+ d: createQueryFromAttributes(attributes)
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (error) {
+ callback(error);
+ } else {
+ callback(null, {
+ id: id,
+ type: type,
+ attributes: attributes
+ });
+ }
+ });
+}
+```
+
+The updateContext handler deals with the modification requests that arrive at the North Port of the IoT Agent via
+`/v2/op/update`. It is invoked once for each entity requested (note that a single request can contain multiple entity
+updates), with the same parameters used in the queryContext handler. The only difference is the value of the attributes
+array, now containing a list of attribute objects, each containing name, type and value. The handler must also make use
+of the callback to return a list of updated attributes.
+
+For this handler we have used a helper function called `createQueryFromAttributes()`, that transforms the NGSI
+representation of the attributes to the UL type expected by the device:
+
+```javascript
+function createQueryFromAttributes(attributes) {
+ var query = '';
+
+ for (var i in attributes) {
+ query += attributes[i].name + '|' + attributes[i].value;
+
+ if (i != attributes.length - 1) {
+ query += ',';
+ }
+ }
+
+ return query;
+}
+```
+
+##### Handler registration
+
+Once both handlers have been defined, they have to be registered in the IoT Agent, adding the following code to the
+setup function:
+
+```javascript
+iotAgentLib.setDataUpdateHandler(updateContextHandler);
+iotAgentLib.setDataQueryHandler(queryContextHandler);
+```
+
+Where necessary, additional handlers to deal with command actuations and merge-patch operations may also be added when
+necessary.
+
+```javascript
+iotAgentLib.setCommandHandler(commandHandler);
+iotAgentLib.setMergePatchHandler(mergePatchHandler);
+```
+
+##### IOTA Testing
+
+In order to test it, we need to create an HTTP server simulating the device. The quickest way to do that may be using
+netcat. In order to start it just run the following command from the command-line (Linux and Mac only):
+
+```bash
+nc -l 9999
+```
+
+This will open a simple TCP server listening on port `9999`, where the requests from the IoT Agent will be printed. In
+order for the complete workflow to work (and to receive the response in the application side), the HTTP response has to
+be written in the `nc` console (although for testing purposes this is not needed).
+
+While netcat is great to test simple connectivity, you will need something just a bit more complex to get the complete
+scenario working (at least without the need to be incredibly fast sending your response). In order to do so, a simple
+echo server was created, that answers 42 to any query to its `/iot/d` path. You can use it to test your attributes one
+by one (or you can modify it to accept more requests and give more complex responses). Copy the
+[Echo Server script](echo.js) to the same folder of your IoTAgent (as it uses the same dependencies). In order to run
+the echo server, just execute the following command:
+
+```bash
+node echo.js
+```
+
+Once the mock server has been started (either `nc` or the `echo` server), proceed with the following steps to test your
+implementation:
+
+1. Provision a device with two lazy attributes. The following request can be used as an example:
+
+```text
+POST /iot/devices HTTP/1.1
+Host: localhost:4041
+Content-Type: application/json
+fiware-service: howtoserv
+fiware-servicepath: /test
+Cache-Control: no-cache
+Postman-Token: 993ac66b-72da-9e96-ab46-779677a5896a
+
+{
+ "devices": [
+ {
+ "device_id": "ULSensor",
+ "entity_name": "Sensor01",
+ "entity_type": "BasicULSensor",
+ "lazy": [
+ {
+ "name": "t",
+ "type": "celsius"
+ },
+ {
+ "name": "l",
+ "type": "meters"
+ }
+ ],
+ "attributes": [
+ ]
+ }
+ ]
+}
+```
+
+2. Execute a `/v2/op/query` or `/v2/op/update` against one of the entity attributes (use a NGSI client of curl command).
+
+```text
+POST /v2/op/query HTTP/1.1
+Host: localhost:1026
+Content-Type: application/json
+Accept: application/json
+Fiware-Service: howtoserv
+Fiware-ServicePath: /test
+Cache-Control: no-cache
+
+{
+ entities: [
+ {
+ id: 'Light:light1'
+ }
+ ],
+ attrs: ['dimming']
+}
+```
+
+3. Check the received request in the nc console is the expected one.
+
+4. (In case you use netcat). Answer the request with an appropriate HTTP response and check the result of the
+ `/v2/op/query` or `/v2/op/update` request is the expected one. An example of HTTP response, for a query to the `t`
+ and `l` attributes would be:
+
+```text
+HTTP/1.0 200 OK
+Content-Type: text/plain
+Content-Length: 3
+
+5,6
+```
+
+This same response can be used both for updates and queries for testing purposes (even though in the former the body
+won't be read).
+
+### IoT Agent in multi-thread mode
+
+It is possible that an IoT Agent can be executed in multi-thread approach, which will increase the number of
+request/seconds that can be manage by the server. It's important to remark that the nature of this functionality in
+included in the IoT Agent Node Lib but it is not mandatory that you activate this functionality. In this example, we
+will see how to use this functionality to deploy an IoT Agent in multi-thread environment.
+
+In order to activate the functionality, you have two options, configure the `config.js` file to add the following line:
+
+```javascript
+/**
+ * flag indicating whether the node server will be executed in multi-core option (true) or it will be a
+ * single-thread one (false).
+ */
+config.multiCore = true;
+```
+
+or you can define the proper IOTA_MULTI_CORE environment variable. By default, the first choice is the environment
+variable and afterward the value of the multiCore in the `config.js` file. The require section would end up like this
+(the standard `http` module is also needed):
+
+```javascript
+var iotAgent = require('../lib/iotagent-implementation'),
+ iotAgentLib = require('iotagent-node-lib'),
+ config = require('./config');
+```
+
+It is important to mention the purpose of the `iotAgent` variable. It is the proper implementation of the IoT Agent
+based on the IoT Agent Node Lib. We will need this variable just to make a callback to the corresponding `start()`
+process from the library. The variable `config` is used to get details of the configuration file and send that
+information to the Node Lib. The Node Lib will take the decision of single-thread or multi-thread execution base on the
+value of `config.multiCore` attribute.
+
+Finally, we can call the corresponding [iotagentLib.startServer()](#iotagentlibstartserver) like the following code with
+a callback function to show details about any error during the execution or just print the message about starting the
+IoTAgent:
+
+```javascript
+iotAgentLib.startServer(config, iotAgent, function (error) {
+ if (error) {
+ console.log(context, 'Error starting IoT Agent: [%s] Exiting process', error);
+ } else {
+ console.log(context, 'IoT Agent started');
+ }
+});
+```
+
+> Note: `startServer()` initializes the server but it does not activate the library. The function in the Node Lib will
+> call the `iotAgent.start()` in order to complete the activation of the library. Therefore, it is expected that the IoT
+> Agent implement the `iotAgent.start()` function with the proper invocation to the `iotAgentLib.activate()`.
+
+### Configuration management
+
+For some IoT Agents, it will be useful to know what devices or config groups were registered in the Agent, or to do
+some actions whenever a new device is registered. All this configuration and provisioning actions can be performed using
+two mechanisms: the provisioning handlers and the provisioning API.
+
+#### Provisioning handlers
+
+The handlers provide a way for the IoT Agent to act whenever a new device, or configuration is provisioned. This can be
+used for registering the device in external services, for storing important information about the device, or to listen
+in new ports in the case of new configuration. For the simple example we are developing, we will just print the
+information we are receiving whenever a new device or configuration is provisioned.
+
+We need to complete two further steps to have a working set of provisioning handlers. First of all, defining the
+handlers themselves. Here we can see the definition of the configuration handler:
+
+```javascript
+function configurationHandler(configuration, callback) {
+ console.log('\n\n* REGISTERING A NEW CONFIGURATION:\n%s\n\n', JSON.stringify(configuration, null, 4));
+ callback(null, configuration);
+}
+```
+
+As we can see, the handlers receive the device or configuration that is being provisioned, as well as a callback. The
+handler MUST call the callback once in order for the IOTA to work properly. If an error is passed as a parameter to the
+callback, the provisioning will be aborted. If no error is passed, the provisioning process will continue. This
+mechanism can be used to implement security mechanisms or to filter the provisioning of devices to the IoT Agent.
+
+Note also that the same `device` or `configuration` object is passed along to the callback. This lets the IoT Agent
+change some of the values provisioned by the user, to add or restrict information in the provisioning. To test this
+feature, let's use the provisioning handler to change the value of the type of the provisioning device to
+`CertifiedType` (reflecting some validation process performed on the provisioning):
+
+```javascript
+function provisioningHandler(device, callback) {
+ console.log('\n\n* REGISTERING A NEW DEVICE:\n%s\n\n', JSON.stringify(device, null, 4));
+ device.type = 'CertifiedType';
+ callback(null, device);
+}
+```
+
+Once the handlers are defined, the new set of handlers has to be registered into the IoT Agent:
+
+```javascript
+iotAgentLib.setConfigurationHandler(configurationHandler);
+iotAgentLib.setProvisioningHandler(provisioningHandler);
+```
+
+Now we can test our implementation by sending provisioning requests to the North Port of the IoT Agent. If we provision
+a new device into the platform, and then we ask for the list of provisioned devices, we shall see the type of the
+provisioned device has changed to `CertifiedType`.
+
+## IoT Agent additional tools
+
+The IoT Agent Node Lib provides some additional tools that can be used to ease the development of IoT Agents and test
+their functionality.
+
+### Agent Console
+
+A command-line client to experiment with the library is packed with it. The command-line client can be started using the
+following command:
+
+```console
+bin/agentConsole.js
+```
+
+The client offers an API similar to the one offered by the library: it can start and stop an IoT agent, register and
+unregister devices, send measures mimicking the device and receive updates of the device data. Take into account that,
+by default, the console uses the same `config.js` file than the IoT Agent.
+
+The command-line client creates a console that offers the following options:
+
+```text
+stressInit
+
+ Start recording a stress batch.
+
+stressCommit
+
+ Executes the recorded batch as many times as requested, with delay (ms) between commands.
+ The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
+ parameter indicates the mean of the random initial waiting times for each agent.
+
+exit
+
+ Exit from the command-line.
+
+start
+
+ Start the IoT Agent
+
+stop
+
+ Stop the IoT Agent
+
+register
+
+ Register a new device in the IoT Agent. The attributes to register will be extracted from the
+ type configuration
+
+unregister
+
+ Unregister the selected device
+
+showConfig
+
+ Show the current configuration file
+
+config
+
+ Change the configuration file to a new one
+
+updatevalue
+
+ Update a device value in the Context Broker. The attributes should be triads with the following
+ format: "name/type/value" sepparated by commas.
+
+listdevices
+
+ List all the devices that have been registered in this IoT Agent session
+```
+
+### Agent tester
+
+#### Command-line testing
+
+The library also offers a Context Broker and IoT Agent client that can be used to:
+
+- Simulate operations to the Context Broker used by the IoT Agent, triggering Context Provider forwardings for lazy
+ attributes and checking the appropriate values for active ones.
+- Simulate operations to the Device Provisioning API and Configuration API of the IoT Agent.
+
+The tester can be started with the following command, from the root folder of the project:
+
+```console
+bin/iotAgentTester.js
+```
+
+From the command-line, the `help` command can be used to show a description of the currently supported features. These
+are the following:
+
+```text
+stressInit
+
+ Start recording a stress batch.
+
+stressCommit
+
+ Executes the recorded batch as many times as requested, with delay (ms) between commands.
+ The "threads" parameter indicates how many agents will repeat that same sequence. The "initTime" (ms)
+ parameter indicates the mean of the random initial waiting times for each agent.
+
+exit
+
+ Exit from the command-line.
+
+update
+
+ Update the values of the defined set of attributes, using the following format: name#type=value(|name#type=value)*
+
+append
+
+ Append a new Entity with the defined set of attributes, using the following format: name:type=value(,name:type=value)*
+
+query
+
+ Get all the information on the selected object.
+
+queryAttr
+
+ Get information on the selected object for the selected attributes.
+
+discover
+
+ Get all the context providers for a entity and type.
+
+configCb
+
+ Config a new host and port for the remote Context Broker.
+
+showConfigCb
+
+ Show the current configuration of the client for the Context Broker.
+
+configIot
+
+ Config a new host and port for the remote IoT Agent.
+
+showConfigIot
+
+ Show the current configuration of the client for the IoT Agent.
+
+provision
+
+ Provision a new device using the Device Provisioning API. The device configuration is
+ read from the file specified in the "filename" parameter.
+
+provisionGroup
+
+ Provision a group of devices with the selected template, taking the information needed to
+ fill the template from a CSV with two columns, DEVICE_ID and DEVICE_NAME. The third parameter, type
+ will be used to replace the DEVICE_TYPE field in the template. All the devices will be provisioned
+ to the same IoT Agent, once the templates have been fulfilled.
+
+listProvisioned
+
+ List all the provisioned devices in an IoT Agent.
+
+removeProvisioned
+
+ Remove the selected provisioned device from the IoT Agent, specified by its Device ID.
+
+addGroup
+
+ Add a new device group to the specified IoT Agent through the Configuration API. The
+ body is taken from the file specified in the "filename" parameter.
+
+listGroups
+
+ List all the device groups created in the selected IoT Agent for the configured service
+
+removeGroup
+
+ Remove the device group corresponding to the current configured subservice.
+
+authenticate
+
+ Authenticates to the given authentication server, and use the token in subsequent requests.
+
+setProtocol
+
+ Sets the protocol to use in the requests (http or https). Defaults to http.
+
+configMigration
+
+ Sets the configuration for a migration between a C++ IoTA and a Node.js one.
+
+showConfigMigration
+
+ Shows the current migration configuration.
+
+addProtocols
+
+ Add a protocol translation table, in the following format:
+ protocolOrigin1=protocolTarget1;protocolOrigin2=protocolTarget2...
+
+
+migrate
+
+ Migrate all the devices and groups for the selected service and subservice into the
+ specified Mongo database. To perform the migration for all the services or all the
+ subservices, use the "*" value.
+```
+
+The agent session stores transient configuration data about the target Context Broker and the target IoT Agent. This
+configuration is independent, and can be checked with the `showConfigCb` and `showConfigIot` commands, respectively.
+Their values can be changed with the `configCb` and `configIot` commands respectively. The new config group will be
+deleted upon startup.
+
+---
diff --git a/doc/echo.js b/doc/devel/echo.js
similarity index 100%
rename from doc/echo.js
rename to doc/devel/echo.js
diff --git a/doc/finalResult.js b/doc/devel/finalResult.js
similarity index 100%
rename from doc/finalResult.js
rename to doc/devel/finalResult.js
diff --git a/doc/northboundinteractions.md b/doc/devel/northboundinteractions.md
similarity index 76%
rename from doc/northboundinteractions.md
rename to doc/devel/northboundinteractions.md
index ff209c357..df7648491 100644
--- a/doc/northboundinteractions.md
+++ b/doc/devel/northboundinteractions.md
@@ -145,7 +145,7 @@ This **NGSI-v2** payload is associated to an update operation (POST `/v2/op/upda
}
}
],
- "actionType": "update"
+ "actionType": "append"
}
```
@@ -155,9 +155,8 @@ As it can be seen in the example, the payload is a JSON Object with the followin
with the information needed to identify the target entity `id` and `type` attributes. The `entities` attribute is an
array, so a single update context batch operation can be used to update multiple devices
-- An `actionType` indicating the type of update: if this attribute has the value `"append"` the appropriate entity and
- attributes will be created if the don't exist; if the value is `"update"`, an error will be thrown if the target
- resources don't exist.
+- An `actionType` indicating the type of update. It has the value `"append"` the appropriate entity and attributes
+ will be created if the don't exist.
The equivalent **NGSI-LD** payload is associated to an update operation (PATCH `/ngsi-ld/v1/entities//attrs/`).
@@ -347,6 +346,9 @@ queries (and thus P2 and R2 payloads).

+**FIXME:** this scenario describes the registration-based commanding mechanism, which is currently deprecated. It should
+be reworked
+
This scenario requires that the attributes that are going to be requested are marked as provided by the IoT Agent,
through a registration process (NGSIv9). Examples of this registration process will be provided in the practical section
of this document. It's worth mentioning that Orion Context Broker **will not** store locally any data about attributes
@@ -427,7 +429,7 @@ Be sure to understand how each scenario works (as shown in the theory section) b
Along this document, IP addresses and passwords will be concealed. Substitute the concealed passwords by your own.
A postman collection is available alongside this document to help in reproducing this examples. It can be found
-[here](./doc/NorthboundInteractions.postman_collection).
+[here](NorthboundInteractions.postman_collection).
### Retrieving a token
@@ -507,8 +509,8 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
}
],
- "actionType": "update"
-} ' "https://:10027/v2/op/update"
+ "actionType": "append"
+} ' "https://:1026/v2/op/update"
```
If the request is correct, the Context Broker will reply with the following R1 response (200 OK):
@@ -554,7 +556,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
],
"attrs": ["temperature","pressure"]
-}' "https://:10027/v2/op/query"
+}' "https://:1026/v2/op/query"
```
The Context Broker will reply with the updated data values in R2 format (200 OK):
@@ -600,20 +602,6 @@ It is worth mentioning that the Context Broker will reply with a 200 OK status c
refer to transport protocol level errors, while the status codes inside of a payload give information about the
application level protocol.
-The example shows an error updating an non-existent attribute (due to the use of UPDATE instead of APPEND).
-
-The following error payload is also valid in standard NGSI:
-
-```json
-{
- "error": "NotFound",
- "description": "The requested entity has not been found. Check type and id"
-}
-```
-
-Different kinds of errors can return their information in different formats, so NGSI implementations should check for
-the existence of both.
-
### Scenario 2: lazy attributes (happy path)
Scenario 2 relies on the Context Provider mechanism of the Context Broker. For this scenario to work, the IoTAgent must
@@ -639,7 +627,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
}
}
-' "https://:10027/v2/registrations"
+' "https://:1026/v2/registrations"
```
If everything has gone OK, the Context Broker will return the following payload:
@@ -669,7 +657,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
],
"attrs": ["batteryLevel"]
-}' "https://:10027/v2/op/query"
+}' "https://:1026/v2/op/query"
```
The Context Broker receives this request and detects that it can be served by a Context Provider (the IoT Agent), so it
@@ -744,53 +732,62 @@ error, that error must follow the NGSI payloads described in the Scenario 1 erro
### Scenario 3: commands (happy path)
-#### Context Provider Registration
+The interactions depend on the command mode (`cmdMode`):
+
+- `legacy`
+- `notification`
+- `advancedNotification`
-Scenario 3 relies on the Context Provider mechanism of the Context Broker. For this scenario to work, the IoTAgent must
+The way of setting up Context Broker to IotAgent communication and the interaction between Context Broker and IoTAgent
+when the command is executed depends on the mode (thus specific subsections about it are provided next for the three
+modes). However, the way in which the command result is provided is the same to all modes (so it is described in a
+[common section](#result-reporting)).
+
+#### `legacy` mode
+
+##### Set up Context Broker to IotAgent comunication mechanism
+
+This mode relies on the Context Provider mechanism of the Context Broker. For this scenario to work, the IoTAgent must
register its commands for each device, with a request like the following:
```bash
curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-service: workshop" \
-H "fiware-servicepath: /iota2ngsi " -H "x-auth-token: " -d '{
- "contextRegistrations": [
- {
- "entities": [
- {
- "type": "device",
- "isPattern": "false",
- "id": "Dev0001"
- }
- ],
- "attributes": [
- {
- "name": "switch",
- "type": "command",
- "isDomain": "false"
- }
- ],
- "providingApplication": "http://:1026/v1"
- }
- ],
- "duration": "P1M"
-}' "https://:10027/v2/registrations"
+ "dataProvided": {
+ "entities": [
+ {
+ "id": "Dev0001",
+ "type": "Device"
+ }
+ ],
+ "attrs": [ "switch" ]
+ },
+ "provider": {
+ "http": {
+ "url": ""
+ }
+ }
+}' "https://:1026/v2/registrations"
```
-If everything has gone OK, the Context Broker will return the following payload:
+If everything has gone OK, the Context Broker will return 201 Created with the ID of the registration in the `Location`
+header:
-```json
-{
- "duration": "P1M",
- "registrationId": "41adf79dc5a0bba830a6f3824"
-}
+```
+HTTP/1.1 201 Created
+Date: ...
+Fiware-Correlator: ...
+Location: /v2/registrations/41adf79dc5a0bba830a6f3824
+Content-Length: 0
```
This ID can be used to update the registration in the future.
The registration of the commands is performed once in the lifetime of the Device.
-#### Command Execution
+##### Command Execution
-Scenario 3 begins with the request for a command from the User to the Context Broker (P1):
+Execution begins with the request for a command from the User to the Context Broker (P1):
```bash
curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-Service: workshop" \
@@ -798,7 +795,6 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
"entities": [
{
"type": "device",
- "isPattern": "false",
"id": "Dev0001",
"switch": {
"type": "command",
@@ -807,7 +803,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
],
"updateAction": "update"
-} ' "https://:10027/v2/op/update"
+} ' "https://:1026/v2/op/update"
```
The Context Broker receives this command and detects that it can be served by a Context Provider (the IoT Agent), so it
@@ -840,45 +836,278 @@ Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
}
```
-The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with the following payload
-(200 OK):
+The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 No Content
+(without payload).
-```json
-[
- {
- "type": "device",
- "id": "Dev0001",
- "switch": {
- "type": "command",
- "value": ""
+This response just indicates that the IoT Agent has received the command successfully, and gives no information about
+the requested information or command execution.
+
+The Context Broker, forwards the same response to the user, thus replying the original request with 204 No Content.
+
+At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
+request will be.
+
+#### `notification` mode
+
+##### Set up Context Broker to IoTAgent comunication mechanism
+
+This mode relies on the notification mechanism of the Context Broker. For this scenario to work, the IoTAgent must
+subscribe its commands for each device, with a request like the following:
+
+```bash
+curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-service: workshop" \
+ -H "fiware-servicepath: /iota2ngsi " -H "x-auth-token: " -d '{
+ "subject": {
+ "entities": [
+ {
+ "type": "device",
+ "id": "Dev0001",
+ }
+ ],
+ "condition": {
+ "attrs": [ "switch" ]
}
+ },
+ "notification": {
+ "http": {
+ "url": "/notify"
+ },
+ "attrsFormat": "normalized",
+ "attrs": [ "switch" ]
}
-]
+}' "https://:1026/v2/subscriptions"
+```
+
+If everything has gone OK, the Context Broker will return 201 Created with the ID of the subscription in the `Location`
+header:
+
+```
+HTTP/1.1 201 Created
+Date: ...
+Fiware-Correlator: ...
+Location: /v2/subscription/60b0cedd497e8b681d40b58e
+Content-Length: 0
+```
+
+This ID can be used to update the subscription in the future.
+
+The subscription of the commands is performed once in the lifetime of the Device.
+
+##### Command Execution
+
+As in the legacy mode, execution begins with the request for a command from the User to the Context Broker (P1):
+
+```bash
+curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-Service: workshop" \
+ -H "Fiware-ServicePath: /iota2ngsi " -H "X-Auth-Token: " -d '{
+ "entities": [
+ {
+ "type": "device",
+ "id": "Dev0001",
+ "switch": {
+ "type": "command",
+ "value": "54, 12"
+ }
+ }
+ ],
+ "updateAction": "update"
+} ' "https://:1026/v2/op/update"
+```
+
+The Context Broker receives this update and detects that it triggers the subscription, so it notifies to the IoT Agent,
+as follows:
+
+```bash
+POST /notify HTTP/1.1
+Host: :
+Fiware-service: workshop
+Fiware-ServicePath: /iota2ngsi
+Accept: application/json
+Content-length: ...
+Content-type: application/json; charset=utf-8
+Ngsiv2-Attrsformat: normalized
+Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
+
+{
+ "subscriptionId": "60b0cedd497e8b681d40b58e",
+ "data": [{
+ "id": "Dev0001",
+ "type": "device",
+ "switch": {
+ "type": "command",
+ "value": "54, 12",
+ "metadata": {}
+ }
+ }]
+}
```
+The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
+payload).
+
This response just indicates that the IoT Agent has received the command successfully, and gives no information about
the requested information or command execution.
-The Context Broker, forwards the same response to the user, thus replying the original request (200 OK):
+At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
+request will be.
-```json
-[
- {
- "type": "device",
- "id": "Dev0001",
- "switch": {
- "type": "command",
- "value": ""
+#### `advancedNotification` mode
+
+##### Set up ContextBroker to IOTA comunication mechanism
+
+The communication mechanism will be based on subscriptions, although a different one than the one used in `notification`
+mode. Note this mode has not been implemented yet, so following should be taken as a draft:
+
+```bash
+curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Fiware-Service: workshop" \
+ -H "Fiware-ServicePath: /iota2ngsi " -H "X-Auth-Token: " -d '{
+ "subject": {
+ "entities": [
+ {
+ "idPattern": ".*",
+ "type": "switchExecution"
+ }
+ ],
+ "condition": {
+ "expression": {
+ "q": "status:FORWARDED;targetEntityType:StreetLight"
+ }
+ }
+ },
+ "notification": {
+ "http": {
+ "url": "/notify"
}
}
-]
+}' "https://:1026/v2/subscriptions"
+```
+
+##### Command Execution
+
+Command execution involves to create a _command execution entity_ (details are yet to be implemented), which in sequence
+triggers a notification to IoTAgent as this one (draft)
+
+```bash
+POST /notify HTTP/1.1
+Host: :
+fiware-service: workshop
+Fiware-ServicePath: /iota2ngsi
+Accept: application/json
+Content-length: 290
+Content-type: application/json; charset=utf-8
+Ngsiv2-Attrsformat: normalized
+Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
+
+{
+ "subscriptionId": "60b0cedd497e8b681d40b58e",
+ "data": [{
+ "id": "123456abcdefg",
+ "type": "switchOnOffExecution",
+ "targetEntityId": {
+ "type": "Text",
+ "value": "Dev0001",
+ "metadata": {}
+ },
+ "targetEntityType": {
+ "type": "Text",
+ "value": "device",
+ "metadata": {}
+ },
+ "execTs": {
+ "type": "DateTime",
+ "value": "2020-05-27T00:00:00.000Z",
+ "metadata": {}
+ },
+ "cmd": {
+ "type": "Text",
+ "value": "switch",
+ "metadata": {}
+ },
+ "params": {
+ "type": "Text",
+ "value": "54, 12",
+ "metadata": {}
+ },
+ "status": {
+ "type": "Text",
+ "value": "FORWARDED",
+ "metadata": {}
+ },
+ "info": {
+ "type": "Text",
+ "value": null,
+ "metadata": {}
+ },
+ "onDelivered": {
+ "type": "Request",
+ "value": {
+ }
+ },
+ "onOk": {
+ "type": "Request",
+ "value": {
+ }
+ },
+ "onError": {
+ "type": "Request",
+ "value": {
+ }
+ },
+ "onInfo": {
+ "type": "Request",
+ "value": {
+ }
+ },
+ "cmdExecution": {
+ "type": "value",
+ "value": true,
+ "metadata": {}
+ },
+ "dateExpiration": {
+ "type": "DateTime",
+ "value": "2020-05-27T20:00:00.000Z",
+ "metadata": {}
+ }
+ }]
+}
```
+In this case relevant fields are just `targetEntityId`, `targetEntityType`, `cmd` and `params`.
+
+The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
+payload).
+
+This response just indicates that the IoT Agent has received the command successfully, and gives no information about
+the requested information or command execution.
+
At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
request will be.
+As mentioned before, advanced notification mode has not been yet implemented. The following considerations are gap in
+the current implementation at IoT Agent side that should be addressed:
+
+- Fields others than `targetEntityId`, `targetEntityType`, `cmd` and `params` (i.e. `execTs`, `status`, `info`,
+ `onDelivered`, `onOk`, `onError`, `onInfo`, `cmdExecution` and `dataExpiration`), are not actually used. By the
+ moment they are stored in the commands model (commands collection) but nothing is done with them appart from
+ storing.
+- The "Result reporting" should not be a "hardwired" behaviour updating the entity associated to the device, but using
+ the corresponding `on*` attribute in the notificaiton (e.g. `onOk` in the case of success). That attribute would
+ typically be a [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) object like this
+ `"onOk": { "href": "/v2/entities/123456abcdefg/attrs/status?type=switchExecution", "method": "PUT" }`. Moreover, the
+ entity to be updated in that HATEOAS would be the transient entity corresponding to command execuion, not the entity
+ associated to the device.
+
+```
+PUT /v2/entities/123456abcdefg/attrs/status?type=switchExecution
+content-type: text/plain
+
+OK
+```
+
#### Result reporting
+No matter the command mode (legacy or notification based), the result reporting is the same in all cases.
+
Once the IoT Agent has executed the command or retrieved the information from the device, it reports the results to the
Context Broker, with an updateContext (P1):
@@ -888,7 +1117,6 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
"entities": [
{
"type": "device",
- "isPattern": "false",
"id": "Dev0001",
"switch_info": {
"type": "commandResult",
@@ -900,31 +1128,14 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
}
],
- "actionType": "update"
-} ' "https://:10027/v2/op/update"
+ "actionType": "append"
+} ' "https://:1026/v2/op/update"
```
This update does not modify the original command attribute, but two auxiliary attributes, that are not provided by the
IoT Agent (usually, those attributes has the same name as the command, with an added suffix).
-The Context Broker replies to the IoT Agent with a R1 payload (200 OK):
-
-```json
-[
- {
- "type": "device",
- "id": "Dev0001",
- "switch_info": {
- "type": "commandResult",
- "value": ""
- },
- "switch_status": {
- "type": "commandStatus",
- "value": ""
- }
- }
-]
-```
+The Context Broker replies to the IoT Agent with 204 No Content response (no payload).
This operation stores the retrieved values locally in the Context Broker, so it can be retrieved with standard NGSI
mechanisms.
@@ -939,7 +1150,6 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
-H "Fiware-ServicePath: /iota2ngsi " -H "X-Auth-Token: " -d '{
"entities": [
{
- "isPattern": "false",
"id": "Dev0001",
"type": "device"
}
@@ -948,7 +1158,7 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
"switch_info",
"switch_status"
]
-}' "https://:10027/v2/op/query"
+}' "https://:1026/v2/op/query"
```
The Context Broker replies with all the desired data, in R2 format (200 OK):
@@ -982,7 +1192,6 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
"entities": [
{
"type": "device",
- "isPattern": "false",
"id": "Dev0001",
"switch_info":{
"type": "commandResult",
@@ -994,27 +1203,10 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
}
}
],
- "actionType": "update"
-} ' "https://:10027/v2/op/update"
+ "actionType": "append"
+} ' "https://:1026/v2/op/update"
```
-In this case, the Context Broker reply with the following response (200 OK):
-
-```json
-[
- {
- "type": "device",
- "id": "Dev0001",
- "switch_info": {
- "type": "commandResult",
- "value": ""
- },
- "switch_status": {
- "type": "commandStatus",
- "value": ""
- }
- }
-]
-```
+In this case, the Context Broker reply with the a 204 No Content response (no payload).
The User will acknowledge the error the next time he queries the Context Broker for information about the command.
diff --git a/doc/development.md b/doc/development.md
deleted file mode 100644
index 1fe830637..000000000
--- a/doc/development.md
+++ /dev/null
@@ -1,285 +0,0 @@
-## Development documentation
-
-- [Contributions](#contributions)
-- [Project build](#project-build)
-- [Testing](#testing)
-- [Coding guidelines](#coding-guidelines)
-- [Continuous testing](#continuous-testing)
-- [Code Coverage](#code-coverage)
-- [Clean](#clean)
-- [Data mapping plugins](#data-mapping-plugins)
- - [Development](#development)
- - [Provided plugins](#provided-plugins)
-
-### Contributions
-
-All contributions to this project are welcome. Developers planning to contribute should follow the
-[Contribution Guidelines](Contribution.md)
-
-### Project build
-
-The project is managed using npm.
-
-For a list of available task, type
-
-```bash
-npm run
-```
-
-The following sections show the available options in detail.
-
-### Environment requirements
-
-A [MongoDB](https://www.mongodb.com/) 3.2+ instance is required to run tests. You can deploy one by using the commodity
-`docker-compose-dev.yml`:
-
-```
-docker-compose -f docker-compose-dev.yml up -d
-```
-
-To run docker compose you will need [docker](https://docs.docker.com/get-docker/) and
-[docker-compose](https://docs.docker.com/compose/install/).
-
-### Testing
-
-[Mocha](https://mochajs.org/) Test Runner + [Should.js](https://shouldjs.github.io/) Assertion Library.
-
-The test environment is preconfigured to run BDD testing style.
-
-Module mocking during testing can be done with [proxyquire](https://github.com/thlorenz/proxyquire)
-
-To run tests, type
-
-```bash
-npm test
-```
-
-There are additional targets starting with `test:` prefix to run specific test subsets isolatedly. For instance, the
-`test:expressions` target runs the subset of tests related with expression language feature:
-
-```bash
-npm run test:expressions
-```
-
-### Debug Test
-
-To debug the code while running run tests, type
-
-```bash
-npm run test:debug
-```
-
-In the console the link to the debugger will be provided. You can connect to it via Chrome, for example, by opening the
-following url: `chrome://inspect`.
-
-Additional debug clients are listed on [node.js](https://nodejs.org/en/docs/guides/debugging-getting-started/).
-
-### Coding guidelines
-
-ESLint
-
-Uses the provided `.eslintrc.json` flag file. To check source code style, type
-
-```bash
-npm run lint
-```
-
-### Continuous testing
-
-Support for continuous testing by modifying a src file or a test. For continuous testing, type
-
-```bash
-npm run test:watch
-```
-
-If you want to continuously check also source code style, use instead:
-
-```bash
-npm run watch
-```
-
-### Code Coverage
-
-Istanbul
-
-Analyze the code coverage of your tests.
-
-To generate an HTML coverage report under `site/coverage/` and to print out a summary, type
-
-```bash
-# Use git-bash on Windows
-npm run test:coverage
-```
-
-### Clean
-
-Removes `node_modules` and `coverage` folders, and `package-lock.json` file so that a fresh copy of the project is
-restored.
-
-```bash
-# Use git-bash on Windows
-npm run clean
-```
-
-### Documentation Markdown validation
-
-Checks the Markdown documentation for consistency
-
-```bash
-# Use git-bash on Windows
-npm run lint:md
-```
-
-### Documentation Spell-checking
-
-Uses the provided `.textlintrc` flag file. To check the Markdown documentation for spelling and grammar errors, dead
-links & etc.
-
-```bash
-# Use git-bash on Windows
-npm run lint:text
-```
-
-### Clean
-
-Removes `node_modules` and `coverage` folders, and `package-lock.json` file so that a fresh copy of the project is
-restored.
-
-```bash
-# Use git-bash on Windows
-npm run clean
-```
-
-### Prettify Code
-
-Runs the [prettier](https://prettier.io) code formatter to ensure consistent code style (whitespacing, parameter
-placement and breakup of long lines etc.) within the codebase.
-
-```bash
-# Use git-bash on Windows
-npm run prettier
-```
-
-To ensure consistent Markdown formatting run the following:
-
-```bash
-# Use git-bash on Windows
-npm run prettier:text
-```
-
-# DB Models (from API document)
-
-## Service group model
-
-The table below shows the information held in the service group provisioning resource. The table also contains the
-correspondence between the API resource fields and the same fields in the database model.
-
-| Payload Field | DB Field | Definition |
-| ------------------------------ | ------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
-| `service` | `service` | Service of the devices of this type |
-| `subservice` | `subservice` | Subservice of the devices of this type. |
-| `resource` | `resource` | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). |
-| `apikey` | `apikey` | API Key string. |
-| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true |
-| `entity_type` | `entity_type` | name of the Entity `type` to assign to the group. |
-| `trust` | `trust` | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). |
-| `cbHost` | `cbHost` | Context Broker connection information. This options can be used to override the global ones for specific types of devices. |
-| `lazy` | `lazy` | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. |
-| `commands` | `commands` | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
-| `attributes` | `attributes` | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
-| `static_attributes` | `staticAttributes` | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
-| `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
-| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. |
-| `explicitAttrs` | `explicitAttrs` | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](advanced-topics.md#explicitly-defined-attributes-explicitattrs) |
-| `entityNameExp` | `entityNameExp` | optional field to allow use expressions to define entity name, instead default `id` and `type` |
-| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. |
-| `defaultEntityNameConjunction` | `defaultEntityNameConjunction` | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
-| `autoprovision` | `autoprovision` | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. |
-
-## Device model
-
-The table below shows the information held in the Device resource. The table also contains the correspondence between
-the API resource fields and the same fields in the database model.
-
-| Payload Field | DB Field | Definition | Example of value |
-| --------------------- | -------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------- |
-| `device_id` | `id` | Device ID that will be used to identify the device. | UO834IO |
-| `service` | `service` | Name of the service the device belongs to (will be used in the fiware-service header). | smartGondor |
-| `service_path` | `subservice` | Name of the subservice the device belongs to (used in the fiware-servicepath header). | /gardens |
-| `entity_name` | `name` | Name of the entity representing the device in the Context Broker | ParkLamplight12 |
-| `entity_type` | `type` | Type of the entity in the Context Broker | Lamplights |
-| `timezone` | `timezone` | Time zone of the sensor if it has any | America/Santiago |
-| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true |
-| `apikey` | `apikey` | Optional Apikey key string to use instead of group apikey | 9n4hb1vpwbjozzmw9f0flf9c2 |
-| `endpoint` | `endpoint` | Endpoint where the device is going to receive commands, if any. | http://theDeviceUrl:1234/commands |
-| `protocol` | `protocol` | Name of the device protocol, for its use with an IoT Manager. | IoTA-UL |
-| `transport` | `transport` | Name of the device transport protocol, for the IoT Agents with multiple transport protocols. | MQTT |
-| `attributes` | `active` | List of active attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
-| `lazy` | `lazy` | List of lazy attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
-| `commands` | `commands` | List of commands of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
-| `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes |
-| `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` |
-| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. |
-| `explicitAttrs` | `explicitAttrs` | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](advanced-topics.md#explicitly-defined-attributes-explicitattrs) | (see details in specific section) |
-| `ngsiVersion` | `ngsiVersion` | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. | `v2/ld` |
-
-## Data mapping plugins
-
-The IoT Agent Library provides a plugin mechanism in order to facilitate reusing code that makes small transformations
-on incoming data (both from the device and from the context consumers). This mechanism is based in the use of
-middlewares, i.e.: small pieces of code that receive and return an `entity`, making as many changes as they need, but
-taking care of returning a valid entity, that can be used as the input for other middlewares; this way, all those pieces
-of code can be chained together in order to make all the needed transformations in the target entity.
-
-There are two kinds of middlewares: updateContext middlewares and queryContext middlewares. The updateContext
-middlewares are applied before the information is sent to the Context Broker, modifiying the entity before it is sent to
-Orion. The queryContext middlewares are applied on the received data, whenever the IoT Agent queries the Context Broker
-for information. I.e.: both middlewares will be automatically applied whenever the `update()` or `query()` functions are
-called in the library.
-
-All the middlewares have the opportunity to break the chain of middleware applications by calling the `callback()` with
-an error object (the usual convention). If any of the updateContext middlewares raise an error, no request will be sent
-to the Context Broker. On the other hand, the queryContext request is always performed, but the call to the `query()`
-function will end up in an error if any of the queryContext middlewares report an error.
-
-### Development
-
-All the middlewares have the same signature:
-
-```javascript
-function middlewareName(entity, typeInformation, callback) {}
-```
-
-The arguments for any middleware are the NGSI data over which it can operate:
-
-- An updateContext payload in the case of an updateContext middleware and a queryContext payload otherwise;
-- a typeInformation object containing all the information about the device stored during registration.
-- and the customary `callback` parameter, with the usual meaning. It's really important for the library user to call
- this callback, as failing to do so may hang the IoT Agent completely. The callback must be called with the an
- optional error in the first argument and the same arguments received (potentially modified) as the following.
-
-In order to manage the middlewares to the system, the following functions can be used:
-
-- `addUpdateMiddleware`: adds an updateContext middleware to the stack of middlewares. All the middlewares will be
- applied to every call to the `update()` function. The final payload of the updateContext request will be the result
- of applying all this middlewares in the order they have been defined.
-
-- `addQueryMiddleware`: adds a queryContext middleware to the stack of middlewares. All the middlewares will be
- applied to every call to the `query()` function.
-
-- `resetMiddlewares`: remove all the middlewares from the system.
-
-Usually, the full list of middlewares an IoT Agent will use would be added in the IoTAgent start sequence, so they
-should not change a lot during the IoT lifetime.
-
-### Provided plugins
-
-The library provides some plugins out of the box, in the `dataPlugins` collection. In order to load any of them, just
-use the `addQueryMiddleware` and `addUpdateMiddleware` functions with the selected plugin, as in the example:
-
-```javascript
-var iotaLib = require('iotagent-node-lib');
-
-iotaLib.addUpdateMiddleware(iotaLib.dataPlugins.compressTimestamp.update);
-iotaLib.addQueryMiddleware(iotaLib.dataPlugins.compressTimestamp.query);
-```
diff --git a/doc/getting-started.md b/doc/getting-started.md
index e253b88e0..8973c5700 100644
--- a/doc/getting-started.md
+++ b/doc/getting-started.md
@@ -1,64 +1,92 @@
-## Getting Started
+# Getting Started
-Every IoT Agent which uses the library is different, but the concepts for provisioning IoT devices remain the same
-regardless of protocol.
+- [Introduction](#introduction)
+- [IoT Agent settings - `config.js`](#iot-agent-settings---configjs)
+- [Provisioning a Config Group](#provisioning-a-config-group)
+- [Provisioning an Individual Device](#provisioning-an-individual-device)
+- [Receiving measures from devices](#receiving-measures-from-devices)
+ - [Receiving a measure from a known Device](#receiving-a-measure-from-a-known-device)
+ - [Receiving a measure from an anonymous Device](#receiving-a-measure-from-an-anonymous-device)
-### `config.js` - IoT Agent settings
+## Introduction
+
+In this guide we will be using the IoT Agent JSON (which is the reference IoTAgent using the IoTAgent Library) as an
+example to demonstrate how to provision config groups, devices and how to receive measures from devices.
+
+Be aware that every IoT Agent which uses the library is different, but the concepts for provisioning IoT devices remain
+the same regardless of protocol.
+
+The IoT Agent JSON is a simple IoT Agent which uses JSON payloads to send and receive data. It is a good starting point
+for understanding how an IoT Agent works since it uses JSON payloads to send and receive data.
+
+## IoT Agent settings - `config.js`
The `config.js` holds common information about the interactions between the Agent and the Context Broker. Additional
-custom settings may also be required dependent upon the actual IoT Agent used.
+custom settings may also be required dependent upon the actual IoT Agent used. The following is an example of a typical
+`config.js` file:
```javascript
config = {
- logLevel: "DEBUG",
+ logLevel: 'DEBUG',
contextBroker: {
- host: "orion",
- port: "1026",
+ host: 'orion',
+ port: '1026'
},
server: {
port: 4041,
- host: "0.0.0.0",
+ host: '0.0.0.0'
},
deviceRegistry: {
- type: "memory",
+ type: 'mongodb'
+ },
+ mongodb: {
+ host: 'localhost',
+ port: '27017',
+ db: 'iotagent'
},
- service: "openiot",
- subservice: "/",
- providerUrl: "http://iot-agent:4041",
- defaultType: "Thing",
+ service: 'openiot',
+ subservice: '/',
+ providerUrl: 'http://iot-agent:4041',
+ defaultType: 'Thing'
};
```
-In this case the context broker is called `orion` and is listening on port `1026`, the IoT Agent can be provisioned by
-sending requests to port `4041` which is also the port used to receive NGSI requests. The IoT Agent is holding the
-device mappings in memory.
+In this case the context broker hostname is `orion` and is listening on port `1026`, the IoT Agent can be provisioned by
+sending requests to port `4041` which is also the port used to receive NGSI requests. The IoT Agent is using the
+`iotagent` database from a MongoDB instance at `localhost:27017` to store needed information (provisioned groups and
+devices, etc.).
The remaining settings help to define the NGSI interactions - the IoT Agent will be using the `fiware-service=openiot`
and `fiware-service-path=/`. The default `type`for each created entity is `Thing`, although this can be overridden as
shown below. Devices will be registered for a period of one month and the IoT Agent will receive registration callbacks
at the URL `http://iot-agent:4041`.
-All configuration settings can also updated using Docker environment variables.
+All configuration settings can also updated using Docker environment variables. You can find more information about the
+available configuration parameters and environment variables in the [Administration Guide](admin.md).
-### Provisioning a Service Group
+## Provisioning a Config Group
-Settings which are common to a group of devices can be passed to the IoT Agent using the Service API. The
-`fiware-service` and `fiware-service-path` to be used to access the API are defined within the `config.js`. Each service
-may override values previously defined in the configuration if necessary.
+Settings which are common to a group of devices can be passed to the IoT Agent using the Config Group API. Each config
+group may override values previously defined in the global configuration if necessary. When using the config group API,
+the `fiware-service` and `fiware-servicepath` headers will define the service and subservice to which the configuration
+will be applied. Additionally, the `apikey` field is used to identify the configuration group. An example of a basic
+config group is shown below:
```bash
curl -iX POST \
- 'http://localhost:4041/iot/services' \
+ 'http://localhost:4041/iot/groups' \
-H 'Content-Type: application/json' \
-H 'fiware-service: openiot' \
-H 'fiware-servicepath: /' \
-d '{
- "services": [
+ "groups": [
{
"apikey": "4jggokgpepnvsb2uv4s40d59ov",
- "cbHost": "http://orion:1026",
"entity_type": "Device",
- "resource": "/iot/d",
+ "resource": "/iot/json",
+ "attributes": [
+ { "object_id": "t", "name": "temperature", "type": "Number" }
+ ]
}
]
}'
@@ -66,12 +94,18 @@ curl -iX POST \
In this case an `apiKey` for identifying devices has been created and all interactions to the path `/iot/d` which
present this `apiKey` will be created as entities of `type=Device` rather than using the configuration default of
-`type=Thing`. The service group would usual hold additional attribute mappings, commands and common static attributes as
-well.
+`type=Thing`.
+
+Additionally, the group has defined an attribute mapping for a measurement `t` to be mapped to `temperature` attribute
+when receiving data from devices.
+
+The config group would usual hold additional attribute mappings, commands and common static attributes as well.
-### Provisioning an Individual Device
+## Provisioning an Individual Device
-Settings which are specific to an individual device can be passed to the IoT Agent using the Device API
+Settings which are specific to an individual device can be passed to the IoT Agent using the Device API. The
+configuration provided in the Device API will override any settings defined in the Config Group API and the global
+configuration as well. An example of a basic device configuration is shown below:
```bash
curl -iX POST \
@@ -86,7 +120,7 @@ curl -iX POST \
"entity_name": "urn:ngsi-ld:Motion:001",
"entity_type": "Motion",
"attributes": [
- { "object_id": "c", "name": "count", "type": "Integer" }
+ { "object_id": "c", "name": "count", "type": "Number" }
],
"static_attributes": [
{ "name":"refStore", "type": "Relationship", "value": "urn:ngsi-ld:Store:001"}
@@ -97,54 +131,81 @@ curl -iX POST \
'
```
-This information is combined with the common service group information whenever a request is received at the South port
-of the IoT Agent and used to create or update the relevant entity in the Context Broker.
+The device `motion001` has been provisioned to persist data to the Context Broker as an entity of `type=Motion` (instead
+of the default `type=Thing`). The destination entity is identified by the `entity_name` field, which is set to
+`urn:ngsi-ld:Motion:001`. The device has a single attribute mapping for a measurement `c` to be mapped to `count`
+attribute, additionally to one defined in the group mapping (`temperature`). The device also has a static attribute
+`refStore` which is a relationship to the entity `urn:ngsi-ld:Store:001`.
-#### Receiving a measure from a known Device
+This information is combined with the common config group information whenever a measurement is received at the IoT
+Agent and used to create or update the relevant entity in the Context Broker.
-For example, imagine we are using the Ultralight IoT Agent and the following request is sent to the South port:
+## Receiving measures from devices
+
+In order to see the complete process from provisioning the groups and devices to receiving measures, we will show how a
+device can send a measure to the IoT Agent. In this case, we will use the IoTA JSON as an example, using the HTTP
+transport protocol. To reproduce the measure sending, you can use the following `curl` commands.
+
+The device measures are sent to the South port of the IoT Agent which is listening in the port `7896`.
+
+### Receiving a measure from a known Device
+
+In this case, the device has been provisioned previously. We will use the `motion001` device defined in the previous
+example. To simulate the device sending a measure, the following request is sent to the South port:
```bash
curl -iX POST \
- 'http://localhost:7896/iot/d?k=4jggokgpepnvsb2uv4s40d59ov&i=motion001' \
- -H 'Content-Type: text/plain' \
- -d 'c|1'
+ 'http://localhost:7896/iot/json?k=4jggokgpepnvsb2uv4s40d59ov&i=motion001' \
+ -H 'Content-Type: application/json' \
+ -d '{"t":23,"c":1}'
```
-The IoT Agent South port is listening to the path defined in the service group, and the API key is recognized to match.
+The IoT Agent South port is listening to the path defined in the config group, and the API key is recognized to match.
Because the `device_id` is also recognized, the provisioned device configuration is used and its settings are combined
-with the service group. A mapping has been found to rename the `c` measurement to `count` - the following context entity
-is created in the context broker:
+with the config group.
+
+Mapping has been found to use the `c` measurement as `count` and the `t` measurement as `temperature` attributes values.
+The following context entity is created in the context broker:
```json
{
"id": "urn:ngsi-ld:Motion:001",
"type": "Motion",
- "count": { "value": "1", "type": "Integer" },
+ "temperature": { "value": 23, "type": "Number" },
+ "count": { "value": 1, "type": "Number" },
"refStore": { "value": "urn:ngsi-ld:Store:001", "type": "Relationship" }
}
```
-#### Receiving a measure from an anonymous Device
+### Receiving a measure from an anonymous Device
+
+When receiving a measure, it is not necessary to have the device provisioned. In this case, the IoT Agent will use the
+config group configuration to create the device and the entity. This process is called "autoprovision" and it is enabled
+by default in provisioned groups (for further information, review the
+[Autoprovision](api.md#autoprovision-configuration-autoprovision) section in the API documentation).
-For example, imagine we are using the Ultralight IoT Agent and a request from an anonymous, unprovisioned device is sent
-to the South port:
+Take as an example the following request from an anonymous device:
```bash
curl -iX POST \
- 'http://localhost:7896/iot/d?k=4jggokgpepnvsb2uv4s40d59ov&i=temp001' \
- -H 'Content-Type: text/plain' \
- -d 't|1'
+ 'http://localhost:7896/iot/json?k=4jggokgpepnvsb2uv4s40d59ov&i=dev001' \
+ -H 'Content-Type: application/json' \
+ -d '{"t":13,"c":4}'
```
-The IoT Agent South port is listen to the path defined in the service group, and the API key is recognized to match, so
-the Service group configuration will be used. No mappings will be made for the Entity `id` or the attribute names and
-the following entity will be created:
+The IoT Agent South port is listen to the path defined in the config group, and the API key is recognized to match, so
+the config group configuration will be used. No device has been provisioned with the `device_id=dev001`, so the IoT
+Agent will only use the config group configuration.
+
+A new entity will be created in the Context Broker with the `id` `Device:dev001` and the `type` `Device`. Only the `t`
+measurement will be mapped to the `temperature` attribute, as defined in the config group. The remaining measurements
+will be created as attributes with the same name. The following context entity will be created in the context broker:
```json
{
- "id": "temp001",
+ "id": "Device:dev001",
"type": "Device",
- "t": { "value": "1", "type": "Number" }
+ "temperature": { "value": 13, "type": "Number" },
+ "c": { "value": 4, "type": "Number" }
}
```
diff --git a/doc/howto.md b/doc/howto.md
deleted file mode 100644
index 55e1fdace..000000000
--- a/doc/howto.md
+++ /dev/null
@@ -1,645 +0,0 @@
-# How to develop a new IOTAgent
-
-- [Overview](#overview)
- - [Protocol](#protocol)
- - [Requirements](#requirements)
-- [Basic IOTA](#basic-iot-agent)
-- [IOTA With Active attributes](#iot-agent-with-active-attributes)
-- [IOTA With Lazy attributes](#iota-with-lazy-attributes)
- - [Previous considerations](#previous-considerations)
- - [Implementation](#implementation)
-- [IoT Agent in multi-thread mode](#iot-agent-in-multi-thread-mode)
-- [Configuration management](#configuration-management)
- - [Provisioning handlers](#provisioning-handlers)
-
-## Overview
-
-This document's goal is to show how to develop a new IoT Agent step by step. To do so, a simple invented HTTP protocol
-will be used, so it can be tested with simple command-line instructions as `curl` and `nc`.
-
-### Protocol
-
-The invented protocol will be freely adapted from
-[Ultralight 2.0](https://github.com/telefonicaid/fiware-IoTAgent-Cplusplus/blob/develop/doc/modules.md#ultra-light-agent).
-Whenever a device wants to send an update, it will send a request as the following:
-
-```bash
-curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
-```
-
-Where:
-
-- **i**: is the device ID.
-- **k**: the API Key for the device's service.
-- **d**: the data payload, consisting of key-value pairs separated by a pipe (`|`), with each pair separated by comma
- (`,`);
-
-### Requirements
-
-This tutorial expects a Node.js v8 (at least) installed and working on your machine. It also expects you to have access
-to a Context Broker (without any security proxies).
-
-## Basic IoT Agent
-
-In this first chapter, we will just develop an IoT Agent with a fully connected North Port. This will send and receive
-NGSI traffic and can be administered using the IoT Agent's Device Provisioning API. The South Port will remain
-unconnected and no native protocol traffic will be sent to the devices. This may seem useless (and indeed it is) but it
-will serve us well on showing the basic steps in the creation of an IoT Agent.
-
-First of all, we have to create the Node project. Create a folder to hold your project and type the following
-instruction:
-
-```bash
-npm init
-```
-
-This will create the `package.json` file for our project. Now, add the following lines to your project file:
-
-```json
- "dependencies": {
- "iotagent-node-lib": "*"
- },
-
-```
-
-And install the dependencies, executing, as usual:
-
-```bash
-npm install
-```
-
-The first step is to write a configuration file, that will be used to tune the behavior of our IOTA. The contents can be
-copied from the `config-basic-example.js` file, in this same folder. Create a `config.js` file with it in the root
-folder of your project. Remember to change the Context Broker IP to your local Context Broker.
-
-Now we can begin with the code of our IoT Agent. The very minimum code we need to start an IoT Agent is the following:
-
-```javascript
-var iotAgentLib = require("iotagent-node-lib"),
- config = require("./config");
-
-iotAgentLib.activate(config, function (error) {
- if (error) {
- console.log("There was an error activating the IOTA");
- process.exit(1);
- }
-});
-```
-
-The IoT Agent is now ready to be used. Execute it with the following command:
-
-```bash
-node index.js
-```
-
-The North Port interface should now be fully functional, i.e.: management of device registrations and configurations.
-
-## IoT Agent With Active attributes
-
-In the previous section we created an IoT Agent that exposed just the North Port interface, but that was pretty useless
-(aside from its didactic use). In this section we are going to create a simple South Port interface. It's important to
-remark that the nature of the traffic South of the IoT Agent itself has nothing to do with the creation process of an
-IoT Agent. Each device protocol will use its own mechanisms and it is up to the IoT Agent developer to find any
-libraries that would help him in its development. In this example, we will use Express as such library.
-
-In order to add the Express dependency to your project, add the following line to the `dependencies` section of the
-`package.json`:
-
-```json
- "express": "*",
-```
-
-The require section would end up like this (the standard `http` module is also needed):
-
-```javascript
-var iotAgentLib = require("iotagent-node-lib"),
- http = require("http"),
- express = require("express"),
- config = require("./config");
-```
-
-And install the dependencies as usual with `npm install`. You will have to require both `express` and `http` in your
-code as well.
-
-Now, in order to accept connections in our code, we have to start express first. With this purpose in mind, we will
-create a new function `initSouthbound()`, that will be called from the initialization code of our IoT Agent:
-
-```javascript
-function initSouthbound(callback) {
- southboundServer = {
- server: null,
- app: express(),
- router: express.Router(),
- };
-
- southboundServer.app.set("port", 8080);
- southboundServer.app.set("host", "0.0.0.0");
-
- southboundServer.router.get("/iot/d", manageULRequest);
- southboundServer.server = http.createServer(southboundServer.app);
- southboundServer.app.use("/", southboundServer.router);
- southboundServer.server.listen(southboundServer.app.get("port"), southboundServer.app.get("host"), callback);
-}
-```
-
-This Express code sets up a HTTP server, listening in the 8080 port, that will handle incoming requests targeting path
-`/iot/d` using the middleware `manageULRequest()`. This middleware will contain all the logic south of the IoT Agent,
-and the library methods we need in order to progress the information to the Context Broker. The code of this middleware
-would be as follows:
-
-```javascript
-function manageULRequest(req, res, next) {
- var values;
-
- iotAgentLib.retrieveDevice(req.query.i, req.query.k, function (error, device) {
- if (error) {
- res.status(404).send({
- message: "Couldn't find the device: " + JSON.stringify(error),
- });
- } else {
- values = parseUl(req.query.d, device);
- iotAgentLib.update(device.name, device.type, "", values, device, function (error) {
- if (error) {
- res.status(500).send({
- message: "Error updating the device",
- });
- } else {
- res.status(200).send({
- message: "Device successfully updated",
- });
- }
- });
- }
- });
-}
-```
-
-For this middleware we have made use of a function `parseUl()` that parses the data payload and transforms it in the
-data object expected by the update function (i.e.: an attribute array with NGSI syntax):
-
-```javascript
-function parseUl(data, device) {
- function findType(name) {
- for (var i = 0; i < device.active.length; i++) {
- if (device.active[i].name === name) {
- return device.active[i].type;
- }
- }
-
- return null;
- }
-
- function createAttribute(element) {
- var pair = element.split("|"),
- attribute = {
- name: pair[0],
- value: pair[1],
- type: findType(pair[0]),
- };
-
- return attribute;
- }
-
- return data.split(",").map(createAttribute);
-}
-```
-
-Here as an example of the output of the function return for the UL payload `t|15,l|19.6`:
-
-```json
-[
- {
- "name": "t",
- "type": "celsius",
- "value": "15"
- },
- {
- "name": "l",
- "type": "meters",
- "value": "19.6"
- }
-]
-```
-
-The last thing to do is to invoke the initialization function inside the IoT Agent startup function. The next excerpt
-show the modifications in the `activate()` function:
-
-```javascript
-iotAgentLib.activate(config, function (error) {
- if (error) {
- console.log("There was an error activating the IOTA");
- process.exit(1);
- } else {
- initSouthbound(function (error) {
- if (error) {
- console.log("Could not initialize South bound API due to the following error: %s", error);
- } else {
- console.log("Both APIs started successfully");
- }
- });
- }
-});
-```
-
-Some logs were added in this piece of code to help debugging.
-
-Once the IOTA is finished the last thing to do is to test it. To do so, launch the IoT Agent and provision a new device
-(an example for provisioning can be found in the `examples/howtoProvisioning1.json` file). Once the device is
-provisioned, send a new measure by using the example command:
-
-```bash
-curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
-```
-
-Now you should be able to see the measures in the Context Broker entity of the device.
-
-## IOTA With Lazy attributes
-
-### Previous considerations
-
-The IoT Agents also give the possibility for the device to be asked about the value of one of its measures, instead of
-reporting it. In order to do so, the device must be capable of receiving messages of some kind. In this case, we are
-going to simulate an HTTP server with `nc` in order to see the values sent by the IOTA. We also have to decide a syntax
-for the protocol request for asking the device about a measure. For clarity, we will use the same HTTP GET request we
-used to report a measure, but indicating the attribute to ask instead of the data payload. Something like:
-
-```bash
-curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&q=t,l' -i
-```
-
-In a real implementation, the server will need to know the URL and port where the devices are listening, in order to
-send the request to the appropriate device. For this example, we will assume that the device is listening in port 9999
-in localhost. For more complex cases, the mechanism to bind devices to addresses would be IoT-Agent-specific (e.g.: the
-OMA Lightweight M2M IoT Agent captures the address of the device in the device registration, and stores the
-device-specific information in a MongoDB document).
-
-Being lazy attributes of a read/write nature, another syntax has to be declared for updating. This syntax will mimic the
-one used for updating the server:
-
-```
-curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
-```
-
-Both types of calls to the device will be distinguished by the presence or absence of the `d` and `q` attributes.
-
-A HTTP request library will be needed in order to make those calls. To this extent, `mikeal/request` library will be
-used. In order to do so, add the following require statement to the initialization code:
-
-```javascript
-request = require("request");
-```
-
-and add the `request` dependency to the `package.json` file:
-
-```json
- "dependencies": [
- [...]
-
- "request": "*",
-
- ]
-```
-
-The require section should now look like this:
-
-```javascript
-var iotAgentLib = require("iotagent-node-lib"),
- http = require("http"),
- express = require("express"),
- request = require("request"),
- config = require("./config");
-```
-
-### Implementation
-
-#### QueryContext implementation
-
-The main step to complete in order to implement the Lazy attributes mechanism in the IoT Agent is to provide handlers
-for the context provisioning requests. At this point, we should provide two handlers: the `/v2/op/update` and the
-`/v2/op/query` handlers. To do so, we must first define the handlers themselves:
-
-```javascript
-function queryContextHandler(id, type, service, subservice, attributes, callback) {
- var options = {
- url: "http://127.0.0.1:9999/iot/d",
- method: "GET",
- qs: {
- q: attributes.join(),
- },
- };
-
- request(options, function (error, response, body) {
- if (error) {
- callback(error);
- } else {
- callback(null, createResponse(id, type, attributes, body));
- }
- });
-}
-```
-
-The queryContext handler is called whenever a `/v2/op/query` request arrives at the North port of the IoT Agent. It is
-invoked once for each entity requested, passing the entity ID and Type as the parameters, as well as a list of the
-attributes that are requested. In our case, the handler uses this parameters to compose a request to the device. Once
-the results of the device are returned, the values are returned to the caller, in the NGSI attribute format.
-
-In order to format the response from the device in a readable way, we created a `createResponse()` function that maps
-the values to its correspondent attributes. This function assumes the type of all the attributes is "string" (this will
-not be the case in a real scenario, where the IoT Agent should retrieve the associated device to guess the type of its
-attributes). Here is the code for the `createResponse()` function:
-
-```javascript
-function createResponse(id, type, attributes, body) {
- var values = body.split(","),
- responses = [];
-
- for (var i = 0; i < attributes.length; i++) {
- responses.push({
- name: attributes[i],
- type: "string",
- value: values[i],
- });
- }
-
- return {
- id: id,
- type: type,
- attributes: responses,
- };
-}
-```
-
-#### UpdateContext implementation
-
-```javascript
-function updateContextHandler(id, type, service, subservice, attributes, callback) {
- var options = {
- url: "http://127.0.0.1:9999/iot/d",
- method: "GET",
- qs: {
- d: createQueryFromAttributes(attributes),
- },
- };
-
- request(options, function (error, response, body) {
- if (error) {
- callback(error);
- } else {
- callback(null, {
- id: id,
- type: type,
- attributes: attributes,
- });
- }
- });
-}
-```
-
-The updateContext handler deals with the modification requests that arrive at the North Port of the IoT Agent via `/v2/op/update`. It is
-invoked once for each entity requested (note that a single request can contain multiple entity updates), with the same
-parameters used in the queryContext handler. The only difference is the value of the attributes array, now containing a
-list of attribute objects, each containing name, type and value. The handler must also make use of the callback to
-return a list of updated attributes.
-
-For this handler we have used a helper function called `createQueryFromAttributes()`, that transforms the NGSI
-representation of the attributes to the UL type expected by the device:
-
-```javascript
-function createQueryFromAttributes(attributes) {
- var query = "";
-
- for (var i in attributes) {
- query += attributes[i].name + "|" + attributes[i].value;
-
- if (i != attributes.length - 1) {
- query += ",";
- }
- }
-
- return query;
-}
-```
-
-#### Handler registration
-
-Once both handlers have been defined, they have to be registered in the IoT Agent, adding the following code to the
-setup function:
-
-```javascript
-iotAgentLib.setDataUpdateHandler(updateContextHandler);
-iotAgentLib.setDataQueryHandler(queryContextHandler);
-```
-
-Where necessary, additional handlers to deal with command actuations and merge-patch operations may also be added when
-necessary.
-
-```javascript
-iotAgentLib.setCommandHandler(commandHandler);
-iotAgentLib.setMergePatchHandler(mergePatchHandler);
-```
-
-#### IOTA Testing
-
-In order to test it, we need to create an HTTP server simulating the device. The quickest way to do that may be using
-netcat. In order to start it just run the following command from the command-line (Linux and Mac only):
-
-```bash
-nc -l 9999
-```
-
-This will open a simple TCP server listening on port `9999`, where the requests from the IoT Agent will be printed. In
-order for the complete workflow to work (and to receive the response in the application side), the HTTP response has to
-be written in the `nc` console (although for testing purposes this is not needed).
-
-While netcat is great to test simple connectivity, you will need something just a bit more complex to get the complete
-scenario working (at least without the need to be incredibly fast sending your response). In order to do so, a simple
-echo server was created, that answers 42 to any query to its `/iot/d` path. You can use it to test your attributes one
-by one (or you can modify it to accept more requests and give more complex responses). Copy the
-[Echo Server script](echo.js) to the same folder of your IoTAgent (as it uses the same dependencies). In order to run
-the echo server, just execute the following command:
-
-```bash
-node echo.js
-```
-
-Once the mock server has been started (either `nc` or the `echo` server), proceed with the following steps to test your
-implementation:
-
-1. Provision a device with two lazy attributes. The following request can be used as an example:
-
-```text
-POST /iot/devices HTTP/1.1
-Host: localhost:4041
-Content-Type: application/json
-fiware-service: howtoserv
-fiware-servicepath: /test
-Cache-Control: no-cache
-Postman-Token: 993ac66b-72da-9e96-ab46-779677a5896a
-
-{
- "devices": [
- {
- "device_id": "ULSensor",
- "entity_name": "Sensor01",
- "entity_type": "BasicULSensor",
- "lazy": [
- {
- "name": "t",
- "type": "celsius"
- },
- {
- "name": "l",
- "type": "meters"
- }
- ],
- "attributes": [
- ]
- }
- ]
-}
-```
-
-2. Execute a `/v2/op/query` or `/v2/op/update` against one of the entity attributes (use a NGSI client of curl command).
-
-```text
-POST /v2/op/query HTTP/1.1
-Host: localhost:1026
-Content-Type: application/json
-Accept: application/json
-Fiware-Service: howtoserv
-Fiware-ServicePath: /test
-Cache-Control: no-cache
-
-{
- entities: [
- {
- id: 'Light:light1'
- }
- ],
- attrs: ['dimming']
-}
-```
-
-3. Check the received request in the nc console is the expected one.
-
-4. (In case you use netcat). Answer the request with an appropriate HTTP response and check the result of the
- `/v2/op/query` or `/v2/op/update` request is the expected one. An example of HTTP response, for a query to the `t`
- and `l` attributes would be:
-
-```text
-HTTP/1.0 200 OK
-Content-Type: text/plain
-Content-Length: 3
-
-5,6
-```
-
-This same response can be used both for updates and queries for testing purposes (even though in the former the body
-won't be read).
-
-## IoT Agent in multi-thread mode
-
-It is possible that an IoT Agent can be executed in multi-thread approach, which will increase the number of
-request/seconds that can be manage by the server. It's important to remark that the nature of this functionality in
-included in the IoT Agent Node Lib but it is not mandatory that you activate this functionality. In this example, we
-will see how to use this functionality to deploy an IoT Agent in multi-thread environment.
-
-**WARNING:** it has been observed in Orion-IOTA integration tests some fails in bidirectional plugin usage scenarios in
-multi-thread mode. The fail has not been confirmed yet (it could be a glitch of the testing environment). However, take
-this into account if you use multi-thread in combination with bidirectional plugin.
-
-In order to activate the functionality, you have two options, configure the `config.js` file to add the following line:
-
-```javascript
-/**
- * flag indicating whether the node server will be executed in multi-core option (true) or it will be a
- * single-thread one (false).
- */
-config.multiCore = true;
-```
-
-or you can define the proper IOTA_MULTI_CORE environment variable. By default, the first choice is the environment
-variable and afterward the value of the multiCore in the `config.js` file. The require section would end up like this
-(the standard `http` module is also needed):
-
-```javascript
-var iotAgent = require("../lib/iotagent-implementation"),
- iotAgentLib = require("iotagent-node-lib"),
- config = require("./config");
-```
-
-It is important to mention the purpose of the `iotAgent` variable. It is the proper implementation of the IoT Agent
-based on the IoT Agent Node Lib. We will need this variable just to make a callback to the corresponding `start()`
-process from the library. The variable `config` is used to get details of the configuration file and send that
-information to the Node Lib. The Node Lib will take the decision of single-thread or multi-thread execution base on the
-value of `config.multiCore` attribute.
-
-Finally, we can call the corresponding [iotagentLib.startServer()](usermanual.md#iotagentlibstartserver) like the
-following code with a callback function to show details about any error during the execution or just print the message
-about starting the IoTAgent:
-
-```javascript
-iotAgentLib.startServer(config, iotAgent, function (error) {
- if (error) {
- console.log(context, "Error starting IoT Agent: [%s] Exiting process", error);
- } else {
- console.log(context, "IoT Agent started");
- }
-});
-```
-
-> Note: `startServer()` initializes the server but it does not activate the library. The function in the Node Lib will
-> call the `iotAgent.start()` in order to complete the activation of the library. Therefore, it is expected that the IoT
-> Agent implement the `iotAgent.start()` function with the proper invocation to the `iotAgentLib.activate()`.
-
-## Configuration management
-
-For some IoT Agents, it will be useful to know what devices or configurations were registered in the Agent, or to do
-some actions whenever a new device is registered. All this configuration and provisioning actions can be performed using
-two mechanisms: the provisioning handlers and the provisioning API.
-
-### Provisioning handlers
-
-The handlers provide a way for the IoT Agent to act whenever a new device, or configuration is provisioned. This can be
-used for registering the device in external services, for storing important information about the device, or to listen
-in new ports in the case of new configuration. For the simple example we are developing, we will just print the
-information we are receiving whenever a new device or configuration is provisioned.
-
-We need to complete two further steps to have a working set of provisioning handlers. First of all, defining the
-handlers themselves. Here we can see the definition of the configuration handler:
-
-```javascript
-function configurationHandler(configuration, callback) {
- console.log("\n\n* REGISTERING A NEW CONFIGURATION:\n%s\n\n", JSON.stringify(configuration, null, 4));
- callback(null, configuration);
-}
-```
-
-As we can see, the handlers receive the device or configuration that is being provisioned, as well as a callback. The
-handler MUST call the callback once in order for the IOTA to work properly. If an error is passed as a parameter to the
-callback, the provisioning will be aborted. If no error is passed, the provisioning process will continue. This
-mechanism can be used to implement security mechanisms or to filter the provisioning of devices to the IoT Agent.
-
-Note also that the same `device` or `configuration` object is passed along to the callback. This lets the IoT Agent
-change some of the values provisioned by the user, to add or restrict information in the provisioning. To test this
-feature, let's use the provisioning handler to change the value of the type of the provisioning device to
-`CertifiedType` (reflecting some validation process performed on the provisioning):
-
-```javascript
-function provisioningHandler(device, callback) {
- console.log("\n\n* REGISTERING A NEW DEVICE:\n%s\n\n", JSON.stringify(device, null, 4));
- device.type = "CertifiedType";
- callback(null, device);
-}
-```
-
-Once the handlers are defined, the new set of handlers has to be registered into the IoT Agent:
-
-```javascript
-iotAgentLib.setConfigurationHandler(configurationHandler);
-iotAgentLib.setProvisioningHandler(provisioningHandler);
-```
-
-Now we can test our implementation by sending provisioning requests to the North Port of the IoT Agent. If we provision
-a new device into the platform, and then we ask for the list of provisioned devices, we shall see the type of the
-provisioned device has changed to `CertifiedType`.
diff --git a/doc/index.md b/doc/index.md
index f32f7a6c1..7a021b507 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -1,7 +1,7 @@
# Welcome to the FIWARE IoT Agent Framework
-[](https://www.fiware.org/developers/catalogue/)
-[](https://stackoverflow.com/questions/tagged/fiware+iot)
+[](https://www.fiware.org/developers/catalogue/)
+[](https://stackoverflow.com/questions/tagged/fiware+iot)
This project aims to provide a Node.js library to enable IoT Agent developers to build custom agents for their devices
that can easily connect to NGSI Context Brokers (such as [Orion](https://github.com/telefonicaid/fiware-orion) ).
@@ -11,8 +11,8 @@ Broker using their own native protocols. IoT Agents should also be able to deal
platform (authentication and authorization of the channel) and provide other common services to the device programmer.
Github's [README.md](https://github.com/telefonicaid/iotagent-node-lib/blob/master/README.md) provides a good
-documentation summary. The [User Manual](usermanual.md) and the [Admin Guide](installationguide.md) cover more advanced
-topics.
+documentation summary. The [API reference](api.md) and the [Development documentation](devel/development.md) cover
+more advanced topics.
## Background
@@ -26,8 +26,6 @@ functions.
communications are left to the library.
- Standardized OAuth2-based security is available to enable each IoT Agent to connect to several common Identity
Managers (e.g. Keystone and Keyrock) so that communications can be restricted to trusted components.
-- A series of additional plugins are offered where necessary to allow for expression parsing, attribute aliasing and
- the processing of timestamp metadata.
Each individual IoT Agent offers is driven by a `config.js` configuration file contains explicit custom settings based
on the protocol and payload the IoT Agent is translating. It will also contain some common flags for common
@@ -51,5 +49,5 @@ IoT Agent
In order to use the library within your own IoT Agent, you must first you require it before use:
```javascript
-const iotagentLib = require("iotagent-node-lib");
+const iotagentLib = require('iotagent-node-lib');
```
diff --git a/doc/installationguide.md b/doc/installationguide.md
deleted file mode 100644
index 987a9425f..000000000
--- a/doc/installationguide.md
+++ /dev/null
@@ -1,370 +0,0 @@
-# Installation & Administration Guide
-
-## Configuration
-
-The `activate()` function that starts the IoT Agent receives as single parameter with the configuration for the IoT
-Agent. The Agent Console reads the same configuration from the `config.js` file.
-
-### Global Configuration
-
-These are the parameters that can be configured in the global section:
-
-- **logLevel**: minimum log level to log. May take one of the following values: DEBUG, INFO, ERROR, FATAL. E.g.:
- 'DEBUG'.
-- **contextBroker**: connection data to the Context Broker (host and port). E.g.:
-
-```javascript
-{
- host: '192.168.56.101',
- port: '1026'
-}
-```
-
-- If you want to use **NGSI v2**:
-
-```javascript
-{
- host: '192.168.56.101',
- port: '1026',
- ngsiVersion: 'v2'
-}
-```
-
-- If you want to use **NGSI-LD** (experimental):
-
-```javascript
-{
- host: '192.168.56.101',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld' // or ['http://context1.json-ld','http://context2.json-ld'] if you need more than one
-}
-```
-
-Where `http://context.json-ld` is the location of the NGSI-LD `@context` element which provides additional information
-allowing the computer to interpret the rest of the data with more clarity and depth. Read the
-[JSON-LD specification](https://w3c.github.io/json-ld-syntax/#the-context) for more information.
-
-- If you want to support a "mixed" mode with both **NGSI-v2** and **NGSI-LD** (experimental):
-
-```javascript
-{
- host: '192.168.56.101',
- port: '1026',
- ngsiVersion: 'mixed',
- jsonLdContext: 'http://context.json-ld' // or ['http://context1.json-ld','http://context2.json-ld'] if you need more than one
-}
-```
-
-Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also
-be switched to **NGSI LD** at service group or device provisioning time using the `ngsiVersion` field in the
-provisioning API. The `ngsiVersion` field switch may be added at either group or device level, with the device level
-overriding the group setting.
-
-- **server**: configuration used to create the Context Server (port where the IoT Agent will be listening as a Context
- Provider and base root to prefix all the paths). The `port` attribute is required. If no `baseRoot` attribute is
- used, '/' is used by default. E.g.:
-
-```javascript
-{
- baseRoot: '/',
- port: 4041
-}
-```
-
-When connected to an **NGSI-LD** context broker, an IoT Agent is able to indicate whether it is willing to accept `null`
-values and also whether it is able to process the **NGSI-LD** `datasetId` metadata element. Setting these values to
-`false` will cause the IoT Agent to return a 400 **Bad Request** HTTP status code explaining that the IoT Agent does not
-support nulls or multi-attribute requests if they are encountered.
-
-```javascript
-{
- baseRoot: '/',
- port: 4041,
- ldSupport : {
- null: true,
- datasetId: true
- }
-}
-```
-
-- **stats**: configure the periodic collection of statistics. Use `interval` in milliseconds to set the time between
- stats writings.
-
-```javascript
-stats: {
- interval: 100;
-}
-```
-
-- **authentication**: authentication data, for use in retrieving tokens for devices with a trust token (required in
- scenarios with security enabled in the Context Broker side). Currently, two authentication provider are supported:
- `keystone` and `oauth2`. Authentication need to be enabled by setting the field `enabled` to `true`. In `keystone`
- based authentication, the `trust` associated to the `device` or `deviceGroup` is a token representing a specific
- user and his rights on a given domain (i.e. combination of `fiware-service` and `fiware-servicepath`). The
- authentication process use the trust delegation workflow to check if the trust provided is valid, in which case
- return a `x-subject-token` that can be used to authenticate the request to the Context Broker. Required parameters
- are: the `url` of the keystone to be used (alternatively `host` and `port` but if you use this combination, the IoT
- Agent will assume that the protocol is HTTP), the `user` and `password` to which it is delegated the `trust`
- verification. E.g.:
-
-```javascript
-{
- enabled: true,
- url: 'https://localhost:5000',
- type: 'keystone',
- user: 'iotagent',
- password: 'iotagent'
-}
-```
-
-In `oauth2` based authentication, two types of tokens can be used depending on the availability in the IDM to be used.
-On one hand, the `trust` associated to the `device` or `deviceGroup` is a `refresh_token` issued by a specific user for
-the Context Broker client. The authentication process uses the
-[`refresh_token` grant type](https://tools.ietf.org/html/rfc6749#section-1.5) to obtain an `access_token` that can be
-used to authenticate the request to the Context Broker. At the time being the assumption is that the `refresh_token` is
-a not expiring `offline_token` (we believe this is the best solution in the case of IoT Devices, since injecting a
-refresh token look may slow down communication. Still, the developer would be able to invalidate the refresh token on
-the provider side in case of security issues connected to a token). The code was tested using
-[Keycloak](http://www.keycloak.org), [Auth0](https://auth0.com) and [FIWARE Keyrock](https://github.com/ging/fiware-idm)
-(it may require customisation for other providers - while OAuth2 is a standard, not all implementations behave in the
-same way, especially as regards status codes and error messages). Required parameters are: the `url` of the OAuth 2
-provider to be used (alternatively `host` and `port` but if you use this combination, the IoT Agent will assume that the
-protocol is HTTP), the `tokenPath` to which the validation request should be sent
-(`/auth/realms/default/protocol/openid-connect/token` for Keycloak and Auth0, `/oauth2/token` for Keyrock), the
-`clientId` and `clientSecret` that identify the Context Broker, and the `header` field that should be used to send the
-authentication request (that will be sent in the form `Authorization: Bearer `). E.g.:
-
-```javascript
-{
- enabled: true,
- type: 'oauth2',
- url: 'http://localhost:3000',
- header: 'Authorization',
- clientId: 'context-broker',
- clientSecret: 'c8d58d16-0a42-400e-9765-f32e154a5a9e',
- tokenPath: '/auth/realms/default/protocol/openid-connect/token'
-}
-```
-
-Nevertheless, this kind of authentication relying on `refresh_token` grant type implies that when the acces_token
-expires, it is needed to request a new one from the IDM, causing some overhead in the communication with the Context
-Broker. To mitigate this issue, FIWARE KeyRock IDM implements `permanent tokens` that can be retrieved using
-`scope=permanent`. With this approach, the IOTA does not need to interact with the IDM and directly include the
-`permanent token` in the header. In order to use this type of token, an additional parameter `permanentToken` must be
-set to `true` in the `authentication` configuration. An environment variable `IOTA_AUTH_PERMANENT_TOKEN` can be also
-used for the same purpose. For instance:
-
-```javascript
-{
- type: 'oauth2',
- url: 'http://localhost:3000',
- header: 'Authorization',
- clientId: 'context-broker',
- clientSecret: '0c2492e1-3ce3-4cca-9723-e6075b89c244',
- tokenPath: '/oauth2/token',
- enabled: true,
- permanentToken: true
-}
-```
-
-- **deviceRegistry**: type of Device Registry to create. Currently, two values are supported: `memory` and `mongodb`.
- If the former is configured, a transient memory-based device registry will be used to register all the devices. This
- registry will be emptied whenever the process is restarted. If the latter is selected, a MongoDB database will be
- used to store all the device information, so it will be persistent from one execution to the other. Mongodb
- databases must be configured in the `mongob` section (as described bellow). E.g.:
-
-```javascript
-{
- type: 'mongodb';
-}
-```
-
-- **mongodb**: configures the MongoDB driver for those repositories with 'mongodb' type. If the `host` parameter is a
- list of comma-separated IPs, they will be considered to be part of a Replica Set. In that case, the optional
- property `replicaSet` should contain the Replica Set name. If the database requires authentication, username
- (`username`), password (`password`) and authSource (`authSource`) can be set. If the database requires TLS/SSL
- connection but any validation of the certificate chain is not mandatory, all you need is to set the ssl (`ssl`)
- option as `true` to connect the database. If you need to add more complex option(s) such as `retryWrites=true` or
- `w=majority` when connection database, extraArgs (`extraArgs`) can be used to perform it. For The MongoBD driver
- will retry the connection at startup time `retries` times, waiting `retryTime` seconds between attempts, if those
- attributes are present (default values are 5 and 5 respectively). E.g.:
-
-```javascript
-{
- host: 'localhost',
- port: '27017',
- db: 'iotagent',
- retries: 5,
- retryTime: 5
-}
-```
-
-```javascript
-{
- host: 'mongodb-0,mongodb-1,mongodb-2',
- port: '27017',
- db: 'iotagent',
- replicaSet: 'rs0',
- user: 'rootuser',
- password: 'password',
- authSource: 'admin',
- ssl: true,
- extraArgs: {
- retryWrites: true,
- readPreference: 'nearest',
- w: 'majority'
- },
- retries: 5,
- retryTime: 5
-}
-```
-
-- **iotManager**: configures all the information needed to register the IoT Agent in the IoTManager. If this section
- is present, the IoTA will try to register to a IoTAM in the `host`, `port` and `path` indicated, with the
- information configured in the object. The IoTAgent URL that will be reported will be the `providedUrl` (described
- below) with the added `agentPath`:
-
-```javascript
-{
- host: 'mockediotam.com',
- port: 9876,
- path: '/protocols',
- protocol: 'GENERIC_PROTOCOL',
- description: 'A generic protocol',
- agentPath: '/iot'
-}
-```
-
-- **types**: See **Type Configuration** in the [Configuration API](#configurationapi) section below.
-- **eventType**: Default type for the Events (useful only with the `addEvents` plugin).
-- **service**: default service for the IoT Agent. If a device is being registered, and no service information comes
- with the device data, and no service information is configured for the given type, the default IoT agent service
- will be used instead. E.g.: 'smartGondor'.
-- **subservice**: default subservice for the IoT Agent. If a device is being registered, and no subservice information
- comes with the device data, and no subservice information is configured for the given type, the default IoT agent
- subservice will be used instead. E.g.: '/gardens'.
-- **providerUrl**: URL to send in the Context Provider registration requests. Should represent the external IP of the
- deployed IoT Agent (the IP where the Context Broker will redirect the NGSI requests). E.g.:
- 'http://192.168.56.1:4041'.
-- **iotaVersion**: indicates the version of the IoTA that will be displayed in the about method (it should be filled
- automatically by each IoTA).
-- **appendMode**: if this flag is activated (its default behaviour), the update requests to the Context Broker will be
- performed always with APPEND type, instead of the default UPDATE. This have implications in the use of attributes
- with Context Providers, so this flag should be used with care. This flag is overwritten by `autoprovision` flag in
- group or device provision.
-- **dieOnUnexpectedError**: if this flag is activated, the IoTAgent will not capture global exception, thus dying upon
- any unexpected error.
-- **singleConfigurationMode**: enables the Single Configuration mode for backwards compatibility (see description in
- the Overview). Default to false.
-- **timestamp**: if this flag is activated:
- - For NGSI-v2, the IoT Agent will add a `TimeInstant` metadata attribute to all the attributes updated from device
- information. This flag is overwritten by `timestamp` flag in group or device
- - With NGSI-LD, the standard `observedAt` property-of-a-property is created instead.
-- **defaultResource**: default string to use as resource for the registration of new Configurations (if no resource is
- provided).
-- **defaultKey**: default string to use as API Key for devices that do not belong to a particular Configuration.
-- **componentName**: default string identifying the component name for this IoT Agent in the logs.
-- **pollingExpiration**: expiration time for commands waiting in the polling queue in miliseconds. If a command has
- been in the queue for this amount of time without being collected by the device, the expiration daemon will reclaim
- it. This attribute is optional (if it doesn't exist, commands won't expire).
-- **pollingDaemonFrequency**: time between collection of expired commands in milliseconds. This attribute is optional
- (if this parameter doesn't exist the polling daemon won't be started).
-- **autocast**: When enabled, the IoT Agents will try to cast attribute's values considering the JSON native type
- (only for NGSI v2).
-- **multiCore**: When enabled, the IoT Agents runs in multi-thread environment to take advantage of multi-core
- systems. It allows two values `true` or `false`. This attribute is optional with default to false, which means that
- the IoTAgent runs in a single thread. For more details about multi-core functionality, please refer to the
- [Cluster](https://nodejs.org/api/cluster.html) module in Node.js and
- [this section](howto.md#iot-agent-in-multi-thread-mode) of the library documentation.
-- **fallbackTenant** - For Linked Data Context Brokers which do not support multi-tenancy, this provides an
- alternative mechanism for supplying the `NGSILD-Tenant` header. Note that NGSILD-Tenant has not yet been included in
- the NGSI-LD standard (it has been proposed for the next update of the standard, but the final decision has yet been
- confirmed), take into account it could change. Note that for backwards compatibility with NGSI v2, the
- `fiware-service` header is already used as alternative if the `NGSILD-Tenant` header is not supplied.
-- **fallbackPath** - For Linked Data Context Brokers which do not support a service path, this provides an alternative
- mechanism for suppling the `NGSILD-Path` header. Note that for backwards compatibility with NGSI v2, the
- `fiware-servicepath` header is already used as alternative if the `NGSILD-Path` header is not supplied. Note that
- NGSILD-Path has not yet been included in the NGSI-LD standard (it has been proposed for the next update of the
- standard, but the final decision has yet been confirmed), take into account it could change
-- **explicitAttrs**: if this flag is activated, only provisioned attributes will be processed to Context Broker. This
- flag is overwritten by `explicitAttrs` flag in group or device provision. Additionally `explicitAttrs` can be used
- to define which meassures defined in JSON/JEXL array will be propagated to NGSI interface.
-- **defaultEntityNameConjunction**: the default conjunction string used to compose a default `entity_name` when is not
- provided at device provisioning time; in that case `entity_name` is composed by `type` + `:` + `device_id`. Default
- value is `:`. This value is overwritten by `defaultEntityNameConjunction` in group provision.
-- **relaxTemplateValidation**: if this flag is activated, `objectId` attributes for incoming devices are not
- validated, and may exceptionally include characters (such as semi-colons) which are
- [forbidden](https://fiware-orion.readthedocs.io/en/master/user/forbidden_characters/index.html) according to the
- NGSI specification. When provisioning devices, it is necessary that the developer provides valid `objectId`-`name`
- mappings whenever relaxed mode is used, to prevent the consumption of forbidden characters.
-
-### Configuration using environment variables
-
-Some of the configuration parameters can be overriden with environment variables, to ease the use of those parameters
-with container-based technologies, like Docker, Heroku, etc...
-
-The following table shows the accepted environment variables, as well as the configuration parameter the variable
-overrides.
-
-| Environment variable | Configuration attribute |
-| :----------------------------------- | :------------------------------ |
-| IOTA_CB_URL | `contextBroker.url` |
-| IOTA_CB_HOST | `contextBroker.host` |
-| IOTA_CB_PORT | `contextBroker.port` |
-| IOTA_CB_NGSI_VERSION | `contextBroker.ngsiVersion` |
-| IOTA_NORTH_HOST | `server.host` |
-| IOTA_NORTH_PORT | `server.port` |
-| IOTA_LD_SUPPORT_NULL | `server.ldSupport.null` |
-| IOTA_LD_SUPPORT_DATASET_ID | `server.ldSupport.datasetId` |
-| IOTA_PROVIDER_URL | `providerUrl` |
-| IOTA_AUTH_ENABLED | `authentication.enabled` |
-| IOTA_AUTH_TYPE | `authentication.type` |
-| IOTA_AUTH_HEADER | `authentication.header` |
-| IOTA_AUTH_URL | `authentication.url` |
-| IOTA_AUTH_HOST | `authentication.host` |
-| IOTA_AUTH_PORT | `authentication.port` |
-| IOTA_AUTH_USER | `authentication.user` |
-| IOTA_AUTH_PASSWORD | `authentication.password` |
-| IOTA_AUTH_CLIENT_ID | `authentication.clientId` |
-| IOTA_AUTH_CLIENT_SECRET | `authentication.clientSecret` |
-| IOTA_AUTH_TOKEN_PATH | `authentication.tokenPath` |
-| IOTA_AUTH_PERMANENT_TOKEN | `authentication.permanentToken` |
-| IOTA_REGISTRY_TYPE | `deviceRegistry.type` |
-| IOTA_LOG_LEVEL | `logLevel` |
-| IOTA_TIMESTAMP | `timestamp` |
-| IOTA_IOTAM_URL | `iotManager.url` |
-| IOTA_IOTAM_HOST | `iotManager.host` |
-| IOTA_IOTAM_PORT | `iotManager.port` |
-| IOTA_IOTAM_PATH | `iotManager.path` |
-| IOTA_IOTAM_AGENTPATH | `iotManager.agentPath` |
-| IOTA_IOTAM_PROTOCOL | `iotManager.protocol` |
-| IOTA_IOTAM_DESCRIPTION | `iotManager.description` |
-| IOTA_MONGO_HOST | `mongodb.host` |
-| IOTA_MONGO_PORT | `mongodb.port` |
-| IOTA_MONGO_DB | `mongodb.db` |
-| IOTA_MONGO_REPLICASET | `mongodb.replicaSet` |
-| IOTA_MONGO_USER | `mongodb.user` |
-| IOTA_MONGO_PASSWORD | `mongodb.password` |
-| IOTA_MONGO_AUTH_SOURCE | `mongodb.authSource` |
-| IOTA_MONGO_RETRIES | `mongodb.retries` |
-| IOTA_MONGO_RETRY_TIME | `mongodb.retryTime` |
-| IOTA_MONGO_SSL | `mongodb.ssl` |
-| IOTA_MONGO_EXTRAARGS | `mongodb.extraArgs` |
-| IOTA_SINGLE_MODE | `singleConfigurationMode` |
-| IOTA_APPEND_MODE | `appendMode` |
-| IOTA_POLLING_EXPIRATION | `pollingExpiration` |
-| IOTA_POLLING_DAEMON_FREQ | `pollingDaemonFrequency` |
-| IOTA_AUTOCAST | `autocast` |
-| IOTA_MULTI_CORE | `multiCore` |
-| IOTA_JSON_LD_CONTEXT | `jsonLdContext` |
-| IOTA_FALLBACK_TENANT | `fallbackTenant` |
-| IOTA_FALLBACK_PATH | `fallbackPath` |
-| IOTA_EXPLICIT_ATTRS | `explicitAttrs` |
-| IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` |
-| IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` |
-
-Note:
-
-- If you need to pass more than one JSON-LD context, you can define the IOTA_JSON_LD_CONTEXT environment variable as a
- comma separated list of contexts (e.g. `'http://context1.json-ld,http://context2.json-ld'`)
diff --git a/doc/models/models.md b/doc/models/models.md
new file mode 100644
index 000000000..b069e864d
--- /dev/null
+++ b/doc/models/models.md
@@ -0,0 +1,260 @@
+# MongoDB Datamodel
+
+This file displays IoT Agent datamodel stored in the MongoDB database.
+
+## Collections
+
+### Devices
+
+The collection 'devices' sotres information about the iotagent devices
+
+Fields:
+
+- **\_ID** _ObjectId_: unique object ID used by mongoDB
+- **id** _string_: id of device
+- **type** _string_: entity type used
+- **name** _string_: entity name used
+- **lazy** _array_: array of lazy attributes of device
+- **active** _array_: array of active attributes of device
+- **commands** _array_: array of commands of device
+- **apikey** _string_: apikey of device
+- **endpoint** _string_: endpoint used by push commands when http
+- **resource** _string_: iotagent resource
+- **protocol** _string_: device protocol (JSON)
+- **transport** _string_: device transport (http, mqtt, amqp)
+- **staticAttributes** _array_: array of static attributes of device
+- **subscriptions** _array_: subscriptions of device
+- **service** _string_: service which the device belongs to
+- **subservice** _string_: subservice which the rule belongs to.
+- **polling** _boolean_: if device uses polling for commands
+- **timezone** _string_: timezone of device
+- **timestamp** _boolean_ timestamp of device
+- **registrationId** _string_: registrationId of device
+- **internalId** _string_: internalId of device
+- **creationDate** _date_: creationDate of device
+- **internalAttributes** _object_: internalAttributes of device
+- **autoprovision** _boolean_: if device support autoprovision
+- **explicitAttrs** _enum_:
+- **ngsiVersion** _string_: ngsi version used by device
+- **payloadType** _string_: payloadType used by device
+- **useCBflowControl** _boolean_: if CBFlow will be used by device when updates in CB
+- **storeLastMeasure** _boolean_: if device store last measure received
+- **lastMeasure** _object_: last measure received by device
+- **oldCtxt** _object_: jexl Ctxt used at last measure
+
+Example:
+
+```json
+{
+ "_id": {
+ "$oid": "680b4b338d0e60f98718a8b2"
+ },
+ "lazy": [],
+ "commands": [
+ {
+ "name": "reset",
+ "type": "command",
+ "value": "",
+ "expression": "{ set: brand + '_' + reset }",
+ "object_id": "reset"
+ },
+ {
+ "name": "cmd1",
+ "type": "command",
+ "value": "",
+ "expression": "brand",
+ "object_id": "cmd1"
+ },
+ {
+ "name": "cmd2",
+ "type": "command",
+ "value": "",
+ "expression": "reset",
+ "object_id": "cmd2"
+ }
+ ],
+ "staticAttributes": [],
+ "creationDate": {
+ "$date": {
+ "$numberLong": "1745570611491"
+ }
+ },
+ "id": "disp",
+ "type": "thing",
+ "name": "thing:disp",
+ "service": "smartcity",
+ "subservice": "/",
+ "registrationId": "680b4b33956cc1ed0205840a",
+ "apikey": "APIKEY",
+ "protocol": "IoTA-JSON",
+ "transport": "HTTP",
+ "polling": false,
+ "active": [],
+ "oldCtxt": {
+ "level": "33",
+ "brand": "o1",
+ "id": "disp",
+ "type": "thing",
+ "service": "smartcity",
+ "subservice": "/",
+ "entity_name": "thing:disp",
+ "TimeInstant": "2025-04-25T08:43:31.496Z"
+ },
+ "subscriptions": []
+}
+```
+
+### Groups
+
+The collection groups' stores information about the iotagent groups of devices
+
+Fields:
+
+- **\_ID** _ObjectId_: unique object ID used by mongoDB
+- **url** _string_: url used by group of devices
+- **resource** _string_: iotagent resource
+- **apikey** _string_: apikey used by group of devices
+- **endpoint** _string_: endpoint used by push commands when http
+- **transport** _string_: group transport (http, mqtt, amqp)
+- **type** _string_: entity type used
+- **service** _string_: service which the group of device belongs to
+- **subservice** _string_: subservice which the group of devices belongs to
+- **description** _string_: description of group of devices
+- **trust** _string_: keystone trust id used when devices of this group request to CB
+- **cbHost** _string_: CB endpoint used by devices of this group
+- **timezone** _string_: timezone used by group of devices
+- **timestamp** _boolean_: timestamp of group
+- **commands** _array_: array of commands of device group
+- **staticAttributes** _array_: array of static attributes of device group
+- **lazy** _array_: array of lazy attributes of device group
+- **attributes** _array_: array of active attributes of device group
+- **internalAttributes** _array_: array of internal attributes used by devices of group
+- **autoprovision** _boolean_: if devices of group supports autoprovision
+- **explicitAttrs** _enum_: explicit attributes configuration used by devices of group
+- **defaultEntityNameConjunction** _string_:
+- **ngsiVersion** _string_: ngsi version used by devices of group
+- **entityNameExp** _string_: entity name expression used by devics of group
+- **payloadType** _string_: payloadType used by devices of group
+- **useCBflowControl** _boolean_: payloadType used by device group
+- **storeLastMeasure** _boolean_: if devices of group store last measure received
+
+Example:
+
+```json
+{
+ "_id": {
+ "$oid": "67a1e6447ae8b4ba4478f019"
+ },
+ "commands": [
+ {
+ "name": "reset",
+ "type": "command",
+ "value": "",
+ "expression": "{ set: brand + '_' + reset }"
+ },
+ {
+ "name": "cmd1",
+ "type": "command",
+ "value": "",
+ "expression": "brand"
+ },
+ {
+ "name": "cmd2",
+ "type": "command",
+ "value": "",
+ "expression": "reset"
+ }
+ ],
+ "staticAttributes": [
+ {
+ "name": "brand",
+ "type": "Text",
+ "value": "o1",
+ "metadata": {}
+ }
+ ],
+ "attributes": [],
+ "resource": "/iot/json",
+ "apikey": "APIKEY",
+ "type": "thing",
+ "service": "smartcity",
+ "subservice": "/",
+ "description": "miJSON",
+ "timestamp": true,
+ "internalAttributes": [],
+ "lazy": [],
+ "transport": "HTTP",
+ "endpoint": "'https://eoykcmmm.m.pipedream.net' + '/' + service + '/' + subservice + '/' + id + '/' + type"
+}
+```
+
+### Commands
+
+The collection 'commands' stores information about the commands
+
+Fields:
+
+- **\_ID** _ObjectId_: unique object ID used by mongoDB
+- **deviceId** _string_: device ID of the device
+- **type** _string_: type of the command
+- **name** _string_: name of the command
+- **value** _object_: value of the command
+- **service** _string_: service which the device command belongs to
+- **subservice** _string_: subservice which the device command belongs to
+- **execTs** _date_: related with new commands functionality (stored but not yet in use)
+- **status** _string_: related with new commands functionality (stored but not yet in use)
+- **info** _string_: related with new commands functionality (stored but not yet in use)
+- **onDelivered** _Object_: related with new commands functionality (stored but not yet in use)
+- **onOk**: _Object_: related with new commands functionality (stored but not yet in use)
+- **onError** _Object_: related with new commands functionality (stored but not yet in use)
+- **onInfo** _Object_: related with new commands functionality (stored but not yet in use)
+- **cmdExecution** \_Boolean\_\_: related with new commands functionality (stored but not yet in use)
+- **dateExpiration**: { type: Date }: related with new commands functionality (stored but not yet in use)
+- **creationDate** _date_: creation date of command
+
+Example:
+
+```json
+{
+ "_id": {
+ "$oid": "680b4b538d0e60f98718a8eb"
+ },
+ "creationDate": {
+ "$date": {
+ "$numberLong": "1745570643252"
+ }
+ },
+ "name": "cmd1",
+ "type": "command",
+ "value": "on",
+ "deviceId": "disp3",
+ "service": "smartcity",
+ "subservice": "/"
+}
+```
+
+## Indexes
+
+### Devices
+
+An index guarantees that every device is identified by the tuple (service, subservice, apikey, id)
+
+The index is created/ensured when iotagent starts, but it can be created from a mongoDB shell with
+
+```javascript
+db.devices.ensureIndex({ service: 1, subservice: 1, apikey: 1, id: 1 }, { unique: true });
+```
+
+### Groups
+
+An index guarantees that every group is identified by the tuple (apikey, resource)
+
+The index is created/ensured when iotagent starts, but it can be created from a mongoDB shell with
+
+```javascript
+db.groups.ensureIndex({ apikey: 1, resource: 1 }, { unique: true });
+```
+
+### Commands
+
+None index is defined
diff --git a/doc/operations.md b/doc/operations.md
deleted file mode 100644
index 103902657..000000000
--- a/doc/operations.md
+++ /dev/null
@@ -1,127 +0,0 @@
-# Operations Manual: logs and alarms
-
-- [Logs](#logs)
-- [Alarms](#alarms)
-- [Error naming code](#error-naming-code)
-
-The following document shows all the errors that can appear in an IoT Agent using this library, and gives a brief idea
-of the severity and how to react to those errors.
-
-## Logs
-
-The following section contains the error log entries that can appear in the IoT Agents logs, grouped by category.
-
-##### GENERAL-001 Couldn't find callback in listDevices() call.
-
-##### MONGODB-001: Error trying to connect to MongoDB: %s
-
-Implies there has been an error connecting with the DB. The component will automatically retry from this error, but it
-may be a sign of connectivity problems between the DB and the component. If the connection cannot be restablished from
-this error, a MONGODB-002 error will be raised.
-
-##### MONGODB-002: Error found after [%d] attempts: %s
-
-Indicates that it was impossible to establish a connection to the MongoDB cluster, even after retrying N times. This
-could be caused by a connectivity problem with the MongoDB machine, a problem in the MongoDB cluster, or a
-misconfiguration of the IoTA Manager. Check the conectivity, the state of the MongoDB cluster and the Mongo
-configuration data.
-
-##### MONGODB-003: No host found for MongoDB driver.
-
-This error will thrown if MongoDB is selected as the configured repository for data but some information is missing in
-the configuration file. Check the configuration file and add all the required information.
-
-##### MONGODB-004: MongoDB connection was lost.
-
-Indicates that it was impossible to reestablish the connection with the MongoDB server after retrying N times. This
-could be caused by a connectivity problem with the MongoDB machine or by changes on the configuration of the MongoDB
-server done while the IoT Agent was running. This error is only thrown when using a single MongoDB instance or when
-using sharding but just a single mongos proxy. When using MongoDB instances using replica sets or multiple mongos
-servers, the IoT Agent will retry connecting forever alternating between the different nodes.
-
-##### IOTAM-001: Error updating information in the IOTAM. Status Code [%d]
-
-The IoT Agent could not contact the IoT Agent manager to update its information. This condition may indicate a lack of
-connectivity between machines or a problem in the IoT Agent Manager. The IoT Agent information in the IoT Agent Manager
-will be out-of-date until this problem is solved.
-
-##### KEYSTONE-001: Error retrieving token from Keystone: %s
-
-There was connection error connecting with Keystone to retrieve a token. This condition may indicate a lack of
-connectivity between both machines or a problem with Keystone.
-
-##### KEYSTONE-002: Unexpected status code: %d
-
-There was a problem retrieving a token from keystone that was not caused by connectivity errors. Check the Keystone log
-for errors and the security configuration in the IoTAgent. This may also be caused by a wrong trust token used by the
-user.
-
-##### KEYSTONE-003: Token missing in the response headers.
-
-Authentication flow worked correctly, but the response headers did not include the expected header `x-subject-token`.
-Check the Keystone logs and configuration.
-
-##### OAUTH2-001: Error retrieving token from OAuth2 provider: %s
-
-There was connection error connecting with OAuth2 provider to retrieve a token. This condition may indicate a lack of
-connectivity between both machines or a problem with OAuth2 provider.
-
-##### OAUTH2-002: Unexpected status code: %d
-
-There was a problem retrieving a token from OAuth2 provider that was not caused by connectivity errors. Check the OAuth2
-provider log for errors and the security configuration in the IoTAgent. This may also be caused by an invalid
-`refresh_token` used by the user.
-
-##### OAUTH2-003: Token missing in the response body
-
-The JSON response body returned by the OAuth2 provider does not include a field `access_token`. Check the OAuth2 logs
-and configuration.
-
-##### ORION-001: Connection error creating initial entity in the Context Broker: %s
-
-There was a connectivity error accessing Context Broker to create an initial entity (or the Context Broker was down).
-Check connectivity between the machines, the status of the remote Context Broker and the configuration of the IoTAgent.
-
-##### ORION-002: Connection error sending registrations to the Context Broker: %s
-
-There was a connectivity error accessing Context Broker to register the IoTA as a Context Provider (or the Context
-Broker was down). Check connectivity between the machines, the status of the remote Context Broker and the configuration
-of the IoT Agent.
-
-##### VALIDATION-FATAL-001: Validation Request templates not found
-
-Validation templates were not found. Check all the validation templates are properly located in the IoTAgent Library
-folder and that the file permissions are correct.
-
-## Alarms
-
-The following table shows the alarms that can be raised in the IoTAgent library. All the alarms are signaled by a error
-log starting with the prefix "Raising [%s]:" (where %s is the alarm name). All the alarms are released by an info log
-with the prefix "Releasing [%s]". These texts appear in the `msg=` field of the generic log record format.
-
-| Alarm name | Severity | Description |
-| :------------- | :----------- | :------------------------------------------------------- |
-| MONGO-ALARM_XX | **Critical** | Indicates an error in the MongoDB connectivity |
-| ORION-ALARM | **Critical** | Indicates a persistent error accesing the Context Broker |
-| IOTAM-ALARM | **Critical** | Indicates a persistent error accessing the IoTAM |
-
-while the 'Severity' criterium is as follows:
-
-- **Critical** - The system is not working
-- **Major** - The system has a problem that degrades the service and must be addressed
-- **Warning** - It is happening something that must be notified
-
-In order to identify the internal flow which origins a mongo alarm, there is a suffix `_XX` which identifies from `01` to
-`11` each flow.
-
-## Error naming code
-
-Every error has a code composed of a prefix and an ID, codified with the following table:
-
-| Prefix | Type of operation |
-| :--------------- | :--------------------------------------------------------- |
-| MONGODB | Errors related with the MongoDB repository |
-| IOTAM | Errors related with the IoTA Manager |
-| KEYSTONE | Errors related with trust token retrieval |
-| ORION | Errors in Context Broker access |
-| VALIDATION-FATAL | Errors related with management of the Validation templates |
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 6a0c8dce1..f157bef13 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,4 +1,4 @@
-mkdocs==1.2.3
-Pygments==2.9.0
-Markdown==3.3.4
-jinja2==3.0.0
\ No newline at end of file
+mkdocs==1.2.4
+Pygments==2.15.0
+Markdown==3.8.1
+jinja2==3.1.6
diff --git a/doc/roadmap.md b/doc/roadmap.md
index cb40536fb..d8ccdd92e 100644
--- a/doc/roadmap.md
+++ b/doc/roadmap.md
@@ -13,7 +13,7 @@ only, and this section may be revised to provide newer information at any time.
Disclaimer:
-- This section has been last updated in March 2022. Please take into account its content could be obsolete.
+- This section has been last updated in August 2023. Please take into account its content could be obsolete.
- Note we develop this software in Agile way, so development plan is continuously under review. Thus, this roadmap has
to be understood as rough plan of features to be done along time which is fully valid only at the time of writing
it. This roadmap has not be understood as a commitment on features and/or dates.
@@ -25,17 +25,19 @@ Disclaimer:
The following list of features are planned to be addressed in the short term, and incorporated in a release of the
product:
-- cgroup literal in configuration groups management API (community)
-- Metadata processing improvements
-- Improve command functionalities (binary data + expression + mapping)
+- cgroup literal in configuration groups management API ([#752](https://github.com/telefonicaid/iotagent-node-lib/issues/752))
+- Add init and improve log traces ([#1452](https://github.com/telefonicaid/iotagent-node-lib/issues/1452))
+- Remove InMemory registry for Devices and Groups ([#1429](https://github.com/telefonicaid/iotagent-node-lib/issues/1429))
### Medium term
The following list of features are planned to be addressed in the medium term, typically within the subsequent
release(s) generated in the next 9 months after the next planned release:
-- Accept JEXL Expressions for entity name in autoprovisioned devices (#1145)
-- Refactor entities-NGSI-v2.js module (#1166)
+- Cache support ([#1467](https://github.com/telefonicaid/iotagent-node-lib/issues/1467))
+- MQTT per group advanced configuration (MQTT broker, topics) ([#1454](https://github.com/telefonicaid/iotagent-node-lib/issues/1454))
+- Subscription based commands ([#1455](https://github.com/telefonicaid/iotagent-node-lib/issues/1455))
+- Remove registration support ([#1456](https://github.com/telefonicaid/iotagent-node-lib/issues/1456))
### Long term
@@ -43,23 +45,33 @@ The following list of features are proposals regarding the longer-term evolution
development of these features has not yet been scheduled for a release in the near future. Please feel free to contact
us if you wish to get involved in the implementation or influence the roadmap:
+- Use the lightweight ingestion mechanism for connection oriented updates implemented in Context Broker (testing pending) ([#1457](https://github.com/telefonicaid/iotagent-node-lib/issues/1457))
+- Add support to other transport protocols (BacNET, Modbus, etc) ([#1458](https://github.com/telefonicaid/iotagent-node-lib/issues/1458))
+- Dynamic attribute generation (based on values array) ([#1459](https://github.com/telefonicaid/iotagent-node-lib/issues/1459))
- Incremental introduccion of ECMAScript6 syntax (previous analysis of which sub-set of interesting aspect we want to
take)
-- Use the lightweight ingestion mechanism for connection oriented updates implemented in Context Broker
-- Add support to other transport protocols (BacNET, Modbus, etc)
### Features already completed
The following list contains all features that were in the roadmap and have already been implemented.
+- Allow to add metadata to attributes from measures ([#1453](https://github.com/telefonicaid/iotagent-node-lib/issues/1453)) ([4.6.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/4.0.0))
+- Refactor Append Mode & initial entity ([#1413](https://github.com/telefonicaid/iotagent-node-lib/issues/1413)) ([4.0.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/4.0.0))
+- Native support for NGSI-v2 and LD ingestion ([#1451](https://github.com/telefonicaid/iotagent-node-lib/issues/1451)) ([4.0.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/4.0.0))
+- Remove plugins structure (bidirectional plugin) ([#1413](https://github.com/telefonicaid/iotagent-node-lib/issues/1413)) ([4.0.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/4.0.0))
+- Refactor entities-NGSI-v2.js module ([#1166](https://github.com/telefonicaid/iotagent-node-lib/issues/1166)) ([3.0.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/3.0.0))
+- Accept JEXL Expressions for entity name in autoprovisioned devices (entityNameExp) ([#1145](https://github.com/telefonicaid/iotagent-node-lib/issues/1145)) ([2.22.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.22.0))
+- Improve command functionalities (binary data + expression + mapping) ([2.22.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.22.0))
- Support for "delta" measures (i.e. "temperature _increased_ in 5 degress" instead of "temperature _is_ 25")
- Allow to handle binary messages ([iota-ul#530](https://github.com/telefonicaid/iotagent-ul/issues/530))
-- Removal support for NGSIv1 (#966) ([2.18.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.18.0))
+- Removal support for NGSIv1 ([#966](https://github.com/telefonicaid/iotagent-node-lib/issues/966)) ([2.18.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.18.0))
- Selectively ignore measure in the southbound interface
([iotagent-json#416](https://github.com/telefonicaid/iotagent-json/issues/416),
[iotagent-ul#372](https://github.com/telefonicaid/iotagent-ul/issues/372))
([2.13.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.13.0))
-- JEXL support in expressions (#801, #687, #868)
+- JEXL support in expressions ([#801](https://github.com/telefonicaid/iotagent-node-lib/issues/801),
+ [#687](https://github.com/telefonicaid/iotagent-node-lib/issues/687),
+ [#868](https://github.com/telefonicaid/iotagent-node-lib/issues/868))
([2.13.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.13.0))
-- Add MongoDB authentication support (#844)
+- Add MongoDB authentication support ([#844](https://github.com/telefonicaid/iotagent-node-lib/issues/844))
([2.12.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.12.0))
diff --git a/doc/usermanual.md b/doc/usermanual.md
deleted file mode 100644
index 843f6f77a..000000000
--- a/doc/usermanual.md
+++ /dev/null
@@ -1,900 +0,0 @@
-# Library Functions
-
-- [Stats Registry](#stats-registry)
-- [Alarm module](#alarm-module)
-- [Transactions](#transactions)
-- [Library overview](#library-overview)
-- [Function reference](#function-reference)
-
-### Stats Registry
-
-The library provides a mechanism for the periodic reporting of stats related to the library's work. In order to activate
-the use of the periodic stats, it must be configured in the config file, as described in the
-[Configuration](installationguide.md#configuration) section.
-
-The Stats Registry holds two dictionaries, with the same set of stats. For each stat, one of the dictionaries holds the
-historical global value and the other one stores the value since the last value reporting (or current value).
-
-The stats library currently stores only the following values:
-
-- **deviceCreationRequests**: number of Device Creation Requests that arrived to the API (no matter the result).
-- **deviceRemovalRequests**: number of Removal Device Requests that arrived to the API (no matter the result).
-- **measureRequests**: number of times the ngsiService.update() function has been invoked (no matter the result).
-
-More values will be added in the future to the library. The applications using the library can add values to the Stats
-Registry just by using the following function:
-
-```javascript
-iotagentLib.statsRegistry.add("statName", statIncrementalValue, callback);
-```
-
-The first time this function is invoked, it will add the new stat to the registry. Subsequent calls will add the value
-to the specified stat both to the current and global measures. The stat will be cleared in each interval as usual.
-
-### Alarm module
-
-The library provide an alarm module that can be used to track through the logs alarms raised in the IoTAgent. This
-module provides:
-
-- Two functions to raise and release and alarm (`raise()` and `release()`): every alarm is identified by a name and a
- description. When the alarm is raised, an error with the text `Raising [%s]` is logged. When the alarm is released,
- the corresponding text, `Releasing [%s]` is logged. If an alarm is raised multiple times, it is only logged once. If
- its released multiple times it is only released once. Releasing a non-existing alarm has no effect.
-
-- Functions to list all the raised alarms and clean all the alarms (`list()` and `clean()`).
-
-- A function to instrument other functions, so when one of that functions return an error, an alarm is raised, and
- when it returns a success an alarm is ceased (`intercept()`).
-
-All this functions can be accessed through the `.alarms` attribute of the library.
-
-### Transactions
-
-The library implements a concept of transactions, in order to follow the execution flow the library follows when
-treating requests entering both from the North and the South ports of the IoT Agent.
-
-To follow the transactions, a new Domain is created for each incoming request; in the case of requests received on the
-North Port of the IoT Agent, this domain is automatically created by a Express middleware, and no further action is
-needed from the user. For the case of requests received on the South Port of the IoT Agent, the user is responsible of
-creating an stopping the transaction, using the `ensureSouthboundDomain` and `finishSouthBoundTransaction`. In this
-case, the transaction will last from the invocation to the former to the invocation of the latter.
-
-The Transaction Correlator is used along all the IoT Platform to follow the trace of a transaction between multiple
-components. To do so, in all the HTTP requests sent to other components of the platform, a custom header named
-`Fiware-Correlator` is sent with the correlator of the transaction that generated the request. If a component of the
-platform receives a request containing this header that starts a transaction, the component will create the transaction
-with the received correlator, instead of creating a new one. If the header is not present or the transaction originates
-in the component, the transaction ID in this component will be used as the correlator.
-
-During the duration of a transaction, all the log entries created by the code will write the current Transaction ID and
-correlator for the operation being executed.
-
-### Library overview
-
-In order to use the library, add the following dependency to your package.json file:
-
-```json
-"iotagent-node-lib": "*"
-```
-
-In order to use this library, first you must require it:
-
-```javascript
-var iotagentLib = require("iotagent-node-lib");
-```
-
-The library supports four groups of features, one for each direction of the communication: client-to-server and
-server-to-client (and each flow both for the client and the server). Each feature set is defined in the following
-sections.
-
-### Function reference
-
-##### iotagentLib.activate()
-
-###### Signature
-
-```javascript
-function activate(newConfig, callback)
-```
-
-###### Description
-
-Activates the IoT Agent to start listening for NGSI Calls (acting as a Context Provider). It also creates the device
-registry for the IoT Agent (based on the deviceRegistry.type configuration option).
-
-###### Params
-
-- newConfig: Configuration of the Context Server (described in the [Configuration](installationguide.md#configuration)
- section).
-
-##### iotagentLib.deactivate()
-
-###### Signature
-
-```javascript
-function deactivate(callback)
-```
-
-###### Description
-
-Stops the HTTP server.
-
-###### Params
-
-##### iotagentLib.register()
-
-###### Signature
-
-```javascript
-function registerDevice(deviceObj, callback)
-```
-
-###### Description
-
-Register a new device in the IoT Agent. This registration will also trigger a Context Provider registration in the
-Context Broker for all its lazy attributes.
-
-The device Object can have the following attributes:
-
-- `id`: Device ID of the device.
-- `type`: type to be assigned to the device.
-- `name`: name that will be used for the Entity representing the device in the Context Broker.
-- `service`: name of the service associated with the device.
-- `subservice`: name of the subservice associated with th device.
-- `lazy`: list of lazy attributes with their types.
-- `active`: list of active attributes with their types.
-- `staticAttributes`: list of NGSI attributes to add to the device entity 'as is' in updates, queries and
- registrations.
-- `internalAttributes`: optional section with free format, to allow specific IoT Agents to store information along
- with the devices in the Device Registry.
-
-The device `id` and `type` are required fields for any registration. The rest of the attributes are optional, but, if
-they are not present in the function call arguments, the type must be registered in the configuration, so the service
-can infer their default values from the configured type. If an optional attribute is not given in the parameter list and
-there isn't a default configuration for the given type, a TypeNotFound error is raised.
-
-If the device has been previously preprovisioned, the missing data will be completed with the values from the registered
-device.
-
-###### Params
-
-- deviceObj: object containing all the information about the device to be registered (mandatory).
-
-##### iotagentLib.unregister()
-
-###### Signature
-
-```javascript
-function unregisterDevice(id, service, subservice, callback)
-```
-
-###### Description
-
-Unregister a device from the Context broker and the internal registry.
-
-###### Params
-
-- id: Device ID of the device to register.
-- service: Service of the device to unregister.
-- subservice: Subservice inside the service for the unregistered device.
-
-##### iotagentLib.update()
-
-###### Signature
-
-```javascript
-function update(entityName, attributes, typeInformation, token, callback)
-```
-
-###### Description
-
-Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
-array should comply to the NGSI's attribute format.
-
-###### Params
-
-- entityName: Name of the entity to register.
-- attributes: Attribute array containing the values to update.
-- typeInformation: Configuration information for the device.
-- token: User token to identify against the PEP Proxies (optional).
-
-##### iotagentLib.setCommandResult()
-
-###### Signature
-
-```javascript
-function setCommandResult(entityName, resource, apikey, commandName, commandResult, status, deviceInformation, callback)
-```
-
-###### Description
-
-Update the result of a command in the Context Broker. The result of the command has two components: the result of the
-command itself will be represented with the suffix `_info` in the entity while the status is updated in the attribute
-with the `_status` suffix.
-
-###### Params
-
-- entityName: Name of the entity holding the command.
-- resource: Resource name of the endpoint the device is calling.
-- apikey: Apikey the device is using to send the values (can be the empty string if none is needed).
-- commandName: Name of the command whose result is being updated.
-- commandResult: Result of the command in string format.
-- deviceInformation: Device information, including security and service information. (optional).
-
-##### iotagentLib.listDevices()
-
-###### Signature
-
-```javascript
-function listDevices(callback)
-function listDevices(limit, offset, callback)
-function listDevices(service, subservice, limit, offset, callback)
-```
-
-###### Description
-
-Return a list of all the devices registered in the specified service and subservice. This function can be invoked in
-three different ways:
-
-- with just one parameter (the callback)
-- with three parameters (service, subservice and callback)
-- or with five parameters (including limit and offset).
-
-###### Params
-
-- service: service from where the devices will be retrieved.
-- subservice: subservice from where the devices will be retrieved.
-- limit: maximum number of results to retrieve (optional).
-- offset: number of results to skip from the listing (optional).
-
-##### iotagentLib.setDataUpdateHandler()
-
-###### Signature
-
-```javascript
-function setDataUpdateHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for Entity update requests. This handler will be called whenever an update request arrives
-with the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). Every object within of
-the `attributes` array contains `name`, `type` and `value` attributes, and may also include additional attributes for
-`metadata` and `datasetId`. The handler is in charge of updating the corresponding values in the devices with the
-appropriate protocol.
-
-Once all the updates have taken place, the callback must be invoked with the updated Context Element. E.g.:
-
-```javascript
-callback(null, {
- type: "TheType",
- isPattern: false,
- id: "EntityID",
- attributes: [
- {
- name: "lumniscence",
- type: "Lumens",
- value: "432"
- }
- ]
-});
-```
-
-In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
-entity, and all the results will be combined into a single response.
-
-###### Params
-
-- newHandler: User handler for update requests
-
-##### iotagentLib.setDataQueryHandler()
-
-###### Signature
-
-```javascript
-function setDataQueryHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for Entity query requests. This handler will be called whenever a query request arrives, with
-the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
-all the corresponding information from the devices and return a NGSI entity with the requested values.
-
-The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
-
-```javascript
-callback(null, {
- type: "TheType",
- isPattern: false,
- id: "EntityID",
- attributes: [
- {
- name: "lumniscence",
- type: "Lumens",
- value: "432"
- }
- ]
-});
-```
-
-In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
-entity, and all the results will be combined into a single response.
-
-###### Params
-
-- newHandler: User handler for query requests.
-
-##### iotagentLib.setNotificationHandler()
-
-###### Signature
-
-```javascript
-function setNotificationHandler(newHandler)
-```
-
-###### Description
-
-Sets the new handler for incoming notifications. The notifications are sent by the Context Broker based on the IoT Agent
-subscriptions created with the `subscribe()` function.
-
-The handler must adhere to the following signature:
-
-```javascript
-function mockedHandler(device, data, callback)
-```
-
-The `device` parameter contains the device object corresponding to the entity whose changes were notified with the
-incoming notification. Take into account that multiple entities may be modified with each single notification. The
-handler will be called once for each one of those entities.
-
-The `data` parameter is an array with all the attributes that were requested in the subscription and its respective
-values.
-
-The handler is expected to call its callback once with no parameters (failing to do so may cause unexpected behaviors in
-the IoT Agent).
-
-##### iotagentLib.setCommandHandler()
-
-###### Signature
-
-```javascript
-function setCommandHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for registered entity commands. This handler will be called whenever a command request arrives, with
-the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
-all the corresponding information from the devices and return a NGSI entity with the requested values.
-
-The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
-
-```javascript
-callback(null, {
- type: "TheType",
- isPattern: false,
- id: "EntityID",
- attributes: [
- {
- name: "lumniscence",
- type: "Lumens",
- value: "432"
- }
- ]
-});
-```
-
-In the case of NGSI requests affecting multiple entities, this handler will be called multiple times, one for each
-entity, and all the results will be combined into a single response. Only IoT Agents which deal with actuator devices will include a handler for commands.
-
-###### Params
-
-- newHandler: User handler for command requests.
-
-##### iotagentLib.setMergePatchHandler()
-
-###### Signature
-
-```javascript
-function setMergePatchHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for NGSI-LD Entity [merge-patch](https://datatracker.ietf.org/doc/html/rfc7386) requests. This handler will be called whenever a merge-patch request arrives, with
-the following parameters: (`id`, `type`, `service`, `subservice`, `attributes`, `callback`). The handler must retrieve
-all the corresponding information from the devices and return a NGSI entity with the requested values.
-
-The callback must be invoked with the updated Context Element, using the information retrieved from the devices. E.g.:
-
-```javascript
-callback(null, {
- type: "TheType",
- isPattern: false,
- id: "EntityID",
- attributes: [
- {
- name: "lumniscence",
- type: "Lumens",
- value: "432"
- }
- ]
-});
-```
-
-In the case of NGSI-LD requests affecting multiple entities, this handler will be
-called multiple times. Since merge-patch is an advanced function, not all IoT Agents
-will include a handler for merge-patch.
-
-###### Params
-
-- newHandler: User handler for merge-patch requests.
-
-##### iotagentLib.setProvisioningHandler()
-
-###### Signature
-
-```javascript
-function setProvisioningHandler (newHandler)
-```
-
-###### Description
-
-Sets the new user handler for the provisioning of devices. This handler will be called every time a new device is
-created.
-
-The handler must adhere to the following signature:
-
-```javascript
-function(newDevice, callback)
-```
-
-The `newDevice` parameter will contain the newly created device. The handler is expected to call its callback with no
-parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
-
-##### iotagentLib.setRemoveDeviceHandler()
-
-###### Signature
-
-```javascript
-function setRemoveDeviceHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for the removal of a device. This handler will be called every time a device is removed.
-
-The handler must adhere to the following signature:
-
-```javascript
-function(deviceToDelete, callback)
-```
-
-The `deviceToDelete` parameter will contain the device to be deleted. The handler is expected to call its callback with
-no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
-
-##### iotagentLib.setConfigurationHandler()
-
-###### Signature
-
-```javascript
-function setConfigurationHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for the configuration updates. This handler will be called every time a new configuration is
-created or an old configuration is updated.
-
-The handler must adhere to the following signature:
-
-```javascript
-function(newConfiguration, callback)
-```
-
-The `newConfiguration` parameter will contain the newly created configuration. The handler is expected to call its
-callback with no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
-
-For the cases of multiple updates (a single Device Configuration POST that will create several device groups), the
-handler will be called once for each of the configurations (both in the case of the creations and the updates).
-
-The handler will be also called in the case of updates related to configurations. In that situation, the
-`newConfiguration` parameter contains also the fields needed to identify the configuration to be updated, i.e.,
-`service`, `subservice`, `resource` and `apikey`.
-
-##### iotagentLib.setRemoveConfigurationHandler()
-
-###### Signature
-
-```javascript
-function setRemoveConfigurationHandler(newHandler)
-```
-
-###### Description
-
-Sets the new user handler for the removal of configuratios. This handler will be called every time a configuration is
-removed.
-
-The handler must adhere to the following signature:
-
-```javascript
-function(configurationToDelete, callback)
-```
-
-The `configurationToDelete` parameter will contain the configuration to be deleted. The handler is expected to call its
-callback with no parameters (this handler should only be used for reconfiguration purposes of the IoT Agent).
-
-##### iotagentLib.getDevice()
-
-###### Signature
-
-```javascript
-function getDevice(deviceId, service, subservice, callback)
-```
-
-###### Description
-
-Retrieve all the information about a device from the device registry.
-
-###### Params
-
-- deviceId: ID of the device to be found.
-- service: Service for which the requested device.
-- subservice: Subservice inside the service for which the device is requested.
-
-##### iotagentLib.getDeviceByName()
-
-###### Signature
-
-```javascript
-function getDeviceByName(deviceName, service, subservice, callback)
-```
-
-###### Description
-
-Retrieve a device from the registry based on its entity name.
-
-###### Params
-
-- deviceName: Name of the entity associated to a device.
-- service: Service the device belongs to.
-- subservice: Division inside the service.
-
-##### iotagentLib.getDevicesByAttribute()
-
-###### Signature
-
-```javascript
-function getDevicesByAttribute(attributeName, attributeValue, service, subservice, callback)
-```
-
-###### Description
-
-Retrieve all the devices having an attribute named `name` with value `value`.
-
-###### Params
-
-- name: name of the attribute to match.
-- value: value to match in the attribute.
-- service: Service the device belongs to.
-- subservice: Division inside the service.
-
-##### iotagentLib.retrieveDevice()
-
-###### Signature
-
-```javascript
-function retrieveDevice(deviceId, apiKey, callback)
-```
-
-###### Description
-
-Retrieve a device from the device repository based on the given APIKey and DeviceID, creating one if none is found for
-the given data.
-
-###### Params
-
-- deviceId: Device ID of the device that wants to be retrieved or created.
-- apiKey: APIKey of the Device Group (or default APIKey).
-
-##### iotagentLib.mergeDeviceWithConfiguration()
-
-###### Signature
-
-```javascript
-function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuration, callback)
-```
-
-###### Description
-
-Complete the information of the device with the information in the configuration group (with precedence of the device).
-The first argument indicates what fields would be merged.
-
-###### Params
-
-- fields: Fields that will be merged.
-- defaults: Default values fot each of the fields.
-- deviceData: Device data.
-- configuration: Configuration data.
-
-##### iotagentLib.getConfiguration()
-
-###### Signature
-
-```javascript
-function getConfiguration(resource, apikey, callback)
-```
-
-###### Description
-
-Gets the device group identified by the given (`resource`, `apikey`) pair.
-
-###### Params
-
-- resource: representation of the configuration in the IoT Agent (dependent on the protocol) .
-- apikey: special key the devices will present to prove they belong to a particular configuration.
-
-##### iotagentLib.findConfiguration()
-
-###### Signature
-
-```javascript
-function findConfiguration(service, subservice, callback)
-```
-
-###### Description
-
-Find a device group based on its service and subservice.
-
-###### Params
-
-- service: name of the service of the configuration.
-- subservice: name of the subservice of the configuration.
-
-##### iotagentLib.getEffectiveApiKey()
-
-###### Signature
-
-```javascript
-function getEffectiveApiKey(service, subservice, type, callback)
-```
-
-###### Description
-
-Get the API Key for the selected service if there is any, or the default API Key if a specific one does not exist.
-
-###### Params
-
-- service: Name of the service whose API Key we are retrieving.
-- subservice: Name of the subservice whose API Key we are retrieving.
-- type: Type of the device.
-
-##### iotagentLib.subscribe()
-
-###### Signature
-
-```javascript
-function subscribe(device, triggers, content, callback)
-```
-
-###### Description
-
-Creates a subscription for the IoTA to the entity representing the selected device.
-
-###### Params
-
-- device: Object containing all the information about a particular device.
-- triggers: Array with the names of the attributes that would trigger the subscription
-- content: Array with the names of the attributes to retrieve in the notification.
-
-##### iotagentLib.unsubscribe()
-
-###### Signature
-
-```javascript
-function unsubscribe(device, id, callback)
-```
-
-###### Description
-
-Removes a single subscription from the selected device, identified by its ID.
-
-###### Params
-
-- `device`: Object containing all the information about a particular device.
-- `id`: ID of the subscription to remove.
-
-##### iotagentLib.ensureSouthboundDomain()
-
-###### Signature
-
-```javascript
-function ensureSouthboundTransaction(context, callback)
-```
-
-###### Description
-
-Ensures that the current operation is executed inside a transaction with all the information needed for the appropriate
-platform logging: start date, transaction ID and correlator in case one is needed. If the function is executed in the
-context of a previous transaction, just the context is changed (and the Transaction ID and start time are kept).
-
-###### Params
-
-- context: New context data for the transaction.
-
-##### iotagentLib.finishSouthBoundTransaction()
-
-###### Signature
-
-```javascript
-function finishSouthboundTransaction(callback)
-```
-
-###### Description
-
-Terminates the current transaction, if there is any, cleaning its context.
-
-##### iotagentLib.startServer()
-
-###### Signature
-
-```javascript
-function startServer(newConfig, iotAgent, callback)
-```
-
-###### Description
-
-Start the HTTP server either in single-thread or multi-thread (multi-core) based on the value of _multiCore_ variable
-(described in the [Configuration](installationguide.md#configuration) section). If the value is `False` (either was
-directly specified `False` in the `config.js` or it was not specified and by default is assigned `False`), it is a
-normal (single-thread) behaviour. Nevertheless, if _multiCore_ is `True`, the IoTAgent is executed in multi-thread
-environment.
-
-The number of parallel processes is calculated based on the number of available CPUs. In case of some of the process
-unexpectedly dead, a new process is created automatically to keep always the maximum of them working in parallel.
-
-> Note: `startServer()` initializes the server but it does not activate the library. The function in the Node Lib will
-> call the `iotAgent.start()` in order to complete the activation of the library. Therefore, it is expected that the IoT
-> Agent implement the `iotAgent.start()` function with the proper invocation to the `iotAgentLib.activate()`.
-
-###### Params
-
-- newConfig: Configuration of the Context Server (described in the [Configuration](installationguide.md#configuration)
- section).
-- iotAgent: The IoT Agent Objects, used to start the agent.
-- callback: The callback function.
-
-##### iotagentLib.request()
-
-###### Signature
-
-```javascript
-function request(options, callback)
-```
-
-###### Description
-
-Make a direct HTTP request using the underlying request library (currently [got](https://github.com/sindresorhus/got)),
-this is useful when creating agents which use an HTTP transport for their southbound commands, and removes the need for
-the custom IoT Agent to import its own additional request library
-
-###### Params
-
-- options: definition of the request (see
- [got options](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md) for more details). The
- following attributes are currently exposed.
- - `method` - HTTP Method
- - `searchParams` - query string params
- - `qs` - alias for query string params
- - `headers`
- - `responseType` - either `text` or `json`. `json` is the default
- - `json` - a supplied JSON object as the request body
- - `body` - any ASCII text as the request body. It takes precedence over `json` if both are provided at the same
- time (not recommended).
- - `url` - the request URL
- - `uri` - alternative alias for the request URL.
-- callback: The callback currently returns an `error` Object, the `response` and `body`. The `body` is parsed to a
- JSON object if the `responseType` is JSON.
-
-#### Generic middlewares
-
-This collection of utility middlewares is aimed to be used to north of the IoT Agent Library, as well as in other
-HTTP-based APIs of the IoT Agents. All the middlewares follow the Express convention of `(req, res, next)` objects, so
-this information will not be repeated in the descriptions for the middleware functions. All the middlewares can be added
-to the servers using the standard Express mechanisms.
-
-##### iotagentLib.middlewares.handleError()
-
-###### Signature
-
-```javascript
-function handleError(error, req, res, next)
-```
-
-###### Description
-
-Express middleware for handling errors in the IoTAs. It extracts the code information to return from the error itself
-returning 500 when no error code has been found.
-
-##### iotagentLib.middlewares.traceRequest()
-
-###### Signature
-
-```javascript
-function traceRequest(req, res, next)
-```
-
-###### Description
-
-Express middleware for tracing the complete request arriving to the IoTA in debug mode.
-
-##### iotagentLib.middlewares.changeLogLevel()
-
-###### Signature
-
-```javascript
-function changeLogLevel(req, res, next)
-```
-
-###### Description
-
-Changes the log level to the one specified in the request.
-
-##### iotagentLib.middlewares.ensureType()
-
-###### Signature
-
-```javascript
-function ensureType(req, res, next)
-```
-
-###### Description
-
-Ensures the request type is one of the supported ones.
-
-##### iotagentLib.middlewares.validateJson()
-
-###### Signature
-
-```javascript
-function validateJson(template)
-```
-
-###### Description
-
-Generates a Middleware that validates incoming requests based on the JSON Schema template passed as a parameter.
-
-Returns an Express middleware used in request validation with the given template.
-
-###### Params
-
-- _template_: JSON Schema template to validate the request.
-
-##### iotagentLib.middlewares.retrieveVersion()
-
-###### Signature
-
-```javascript
-function retrieveVersion(req, res, next)
-```
-
-###### Description
-
-Middleware that returns all the IoTA information stored in the module.
-
-##### iotagentLib.middlewares.setIotaInformation()
-
-###### Signature
-
-```javascript
-function setIotaInformation(newIoTAInfo)
-```
-
-###### Description
-
-Stores the information about the IoTAgent for further use in the `retrieveVersion()` middleware.
-
-###### Params
-
-- _newIoTAInfo_: Object containing all the IoTA Information.
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
index 98e003b50..bd0161704 100644
--- a/docker-compose-dev.yml
+++ b/docker-compose-dev.yml
@@ -3,6 +3,6 @@ version: '3'
services:
mongo:
- image: mongo:4.4
+ image: mongo:6.0.12
ports:
- "27017:27017"
diff --git a/docker/Mosquitto/Dockerfile b/docker/Mosquitto/Dockerfile
index b9bef0664..9dad4d7de 100644
--- a/docker/Mosquitto/Dockerfile
+++ b/docker/Mosquitto/Dockerfile
@@ -1,4 +1,4 @@
-ARG IMAGE_TAG=11.3-slim
+ARG IMAGE_TAG=12.10-slim
FROM debian:${IMAGE_TAG}
ARG CLEAN_DEV_TOOLS
@@ -16,7 +16,7 @@ RUN \
# Install dependencies
apt-get -y install \
wget \
- mosquitto && \
+ mosquitto mosquitto-clients && \
cp /etc/mosquitto/mosquitto.conf /etc/mosquitto/mosquitto.conf.orig && \
chmod 755 /bin/startMosquitto.sh && \
mkdir -p /var/log/mosquitto && \
diff --git a/docker/Mosquitto/README.md b/docker/Mosquitto/README.md
index c27b5cab1..04cc9fa5f 100644
--- a/docker/Mosquitto/README.md
+++ b/docker/Mosquitto/README.md
@@ -1,12 +1,16 @@
-Thi directory containts the Dockerfile (and associated files) for a container of [mosquitto MQTT broker](https://mosquitto.org).
-This container is provide as a help for users to test with MQTT, but it is just an auxiliary material in this repository.
+Thi directory containts the Dockerfile (and associated files) for a container of
+[mosquitto MQTT broker](https://mosquitto.org). This container is provide as a help for users to test with MQTT, but it
+is just an auxiliary material in this repository.
The following releases matches with eclipse-mosquitto version:
-- 2.0.0 uses mosquitto-2.0.11 from Debian 11
-- 1.6.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
-- 1.5.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
-- 1.4.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
-- 1.3.0 uses mosquitto-1.6.8-1.el7.x86_64 (from Centos7)
-- 1.2.0 uses mosquitto-1.6.7-1.el7.x86_64 (from Centos7)
-- 1.1.0 uses mosquitto-1.5.8-1.el7.x86_64 (from Centos7)
-- 1.0.0 uses mosquitto-1.4.8-1.el7.x86_64 (from Centos7)
+
+- 2.2.0 uses mosquitto-2.0.11 from Debian 12
+- 2.1.0 uses mosquitto-2.0.11 from Debian 12
+- 2.0.0 uses mosquitto-2.0.11 from Debian 11
+- 1.6.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
+- 1.5.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
+- 1.4.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
+- 1.3.0 uses mosquitto-1.6.8-1.el7.x86_64 (from Centos7)
+- 1.2.0 uses mosquitto-1.6.7-1.el7.x86_64 (from Centos7)
+- 1.1.0 uses mosquitto-1.5.8-1.el7.x86_64 (from Centos7)
+- 1.0.0 uses mosquitto-1.4.8-1.el7.x86_64 (from Centos7)
diff --git a/lib/commonConfig.js b/lib/commonConfig.js
index af51d4299..6d0a6e645 100644
--- a/lib/commonConfig.js
+++ b/lib/commonConfig.js
@@ -142,14 +142,11 @@ function processEnvironmentVariables() {
'IOTA_MONGO_PORT',
'IOTA_MONGO_DB',
'IOTA_MONGO_REPLICASET',
- 'IOTA_AUTOCAST',
'IOTA_MONGO_PASSWORD',
'IOTA_MONGO_AUTH_SOURCE',
'IOTA_MONGO_RETRIES',
'IOTA_MONGO_USER',
'IOTA_MONGO_RETRY_TIME',
- 'IOTA_SINGLE_MODE',
- 'IOTA_APPEND_MODE',
'IOTA_POLLING_EXPIRATION',
'IOTA_POLLING_DAEMON_FREQ',
'IOTA_MULTI_CORE',
@@ -159,7 +156,17 @@ function processEnvironmentVariables() {
'IOTA_FALLBACK_TENANT',
'IOTA_FALLBACK_PATH',
'IOTA_LD_SUPPORT_NULL',
- 'IOTA_LD_SUPPORT_DATASET_ID'
+ 'IOTA_LD_SUPPORT_DATASET_ID',
+ 'IOTA_LD_SUPPORT_DATA_TYPE',
+ 'IOTA_EXPRESS_LIMIT',
+ 'IOTA_USE_CB_FLOW_CONTROL',
+ 'IOTA_STORE_LAST_MEASURE',
+ 'IOTA_CMD_MODE',
+ 'IOTA_HEALTH_CHECK',
+ 'IOTA_HEALTH_CHECK_INTERVAL',
+ 'IOTA_HEALTH_CHECK_TIMEOUT',
+ 'IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS',
+ 'IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP'
];
const iotamVariables = [
'IOTA_IOTAM_URL',
@@ -171,6 +178,9 @@ function processEnvironmentVariables() {
'IOTA_IOTAM_AGENTPATH'
];
const mongoVariables = [
+ 'IOTA_MONGO_URI',
+ // FIXME: the following IOTA_MONGO_ env vars are deprecated and will
+ // be eventually removed (use IOTA_MONGO_URI)
'IOTA_MONGO_HOST',
'IOTA_MONGO_PORT',
'IOTA_MONGO_DB',
@@ -264,7 +274,12 @@ function processEnvironmentVariables() {
config.server.port = process.env.IOTA_NORTH_PORT;
}
- config.server.ldSupport = config.server.ldSupport || { null: true, datasetId: true, merge: false };
+ config.server.ldSupport = config.server.ldSupport || {
+ null: true,
+ datasetId: true,
+ merge: false,
+ dataType: 'none'
+ };
if (process.env.IOTA_LD_SUPPORT_NULL) {
config.server.ldSupport.null = process.env.IOTA_LD_SUPPORT_NULL === 'true';
@@ -275,6 +290,9 @@ function processEnvironmentVariables() {
if (process.env.IOTA_LD_SUPPORT_MERGE) {
config.server.ldSupport.datasetId = process.env.IOTA_LD_SUPPORT_MERGE === 'true';
}
+ if (process.env.IOTA_LD_SUPPORT_DATA_TYPE) {
+ config.server.ldSupport.dataType = process.env.IOTA_LD_SUPPORT_DATA_TYPE;
+ }
if (process.env.IOTA_PROVIDER_URL) {
config.providerUrl = process.env.IOTA_PROVIDER_URL;
@@ -352,6 +370,17 @@ function processEnvironmentVariables() {
config.defaultResource = process.env.IOTA_DEFAULT_RESOURCE;
}
+ // Default transport
+ if (process.env.IOTA_DEFAULT_TRANSPORT !== undefined) {
+ config.defaultTransport = process.env.IOTA_DEFAULT_TRANSPORT;
+ } else {
+ // defaultTransport is "MQTT" by default even when no ENV VAR is used
+ // and should be available in iotagent-library to allow
+ // set polling and transport for autoprovisioned devices
+ // at lib/services/devices/deviceService.js#L302
+ config.defaultTransport = 'MQTT';
+ }
+
// Default explicitAttrs
if (process.env.IOTA_EXPLICIT_ATTRS !== undefined) {
config.explicitAttrs = process.env.IOTA_EXPLICIT_ATTRS;
@@ -394,6 +423,10 @@ function processEnvironmentVariables() {
config.mongodb = {};
}
+ if (process.env.IOTA_MONGO_URI) {
+ config.mongodb.uri = process.env.IOTA_MONGO_URI;
+ }
+
if (process.env.IOTA_MONGO_HOST) {
config.mongodb.host = process.env.IOTA_MONGO_HOST;
}
@@ -445,15 +478,6 @@ function processEnvironmentVariables() {
}
}
- // Other configuration properties
- if (process.env.IOTA_SINGLE_MODE) {
- config.singleConfigurationMode = process.env.IOTA_SINGLE_MODE === 'true';
- }
-
- if (process.env.IOTA_APPEND_MODE) {
- config.appendMode = process.env.IOTA_APPEND_MODE === 'true';
- }
-
if (process.env.IOTA_POLLING_EXPIRATION) {
config.pollingExpiration = process.env.IOTA_POLLING_EXPIRATION;
}
@@ -462,10 +486,6 @@ function processEnvironmentVariables() {
config.pollingDaemonFrequency = process.env.IOTA_POLLING_DAEMON_FREQ;
}
- if (process.env.IOTA_AUTOCAST) {
- config.autocast = process.env.IOTA_AUTOCAST === 'true';
- }
-
if (process.env.IOTA_MULTI_CORE) {
config.multiCore = process.env.IOTA_MULTI_CORE === 'true';
} else {
@@ -484,6 +504,49 @@ function processEnvironmentVariables() {
? config.defaultEntityNameConjunction
: ':';
}
+ if (process.env.IOTA_EXPRESS_LIMIT) {
+ config.expressLimit = process.env.IOTA_EXPRESS_LIMIT;
+ } else {
+ config.expressLimit = config.expressLimit ? config.expressLimit : '1mb';
+ }
+ if (process.env.IOTA_USE_CB_FLOW_CONTROL) {
+ config.useCBflowControl = process.env.IOTA_USE_CB_FLOW_CONTROL === 'true';
+ } else {
+ config.useCBflowControl = config.useCBflowControl === true;
+ }
+ if (process.env.IOTA_STORE_LAST_MEASURE) {
+ config.storeLastMeasure = process.env.IOTA_STORE_LAST_MEASURE === 'true';
+ } else {
+ config.storeLastMeasure = config.storeLastMeasure === true;
+ }
+ if (process.env.IOTA_CMD_MODE) {
+ config.cmdMode = process.env.IOTA_CMD_MODE;
+ }
+ if (process.env.IOTA_HEALTH_CHECK) {
+ config.healthCheck = process.env.IOTA_HEALTH_CHECK === 'true';
+ } else {
+ config.healthCheck = config.healthCheck === true;
+ }
+ if (process.env.IOTA_HEALTH_CHECK_INTERVAL) {
+ config.healthCheckInterval = process.env.IOTA_HEALTH_CHECK_INTERVAL;
+ } else {
+ config.healthCheckInterval = config.healthCheckInterval ? config.healthCheckInterval : 20000;
+ }
+ if (process.env.IOTA_HEALTH_CHECK_TIMEOUT) {
+ config.healthCheckTimeout = process.env.IOTA_HEALTH_CHECK_TIMEOUT;
+ } else {
+ config.healthCheckTimeout = config.healthCheckTimeout ? config.healthCheckTimeout : 1500;
+ }
+ if (process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS) {
+ config.healthCheckDownAfterFails = process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS;
+ } else {
+ config.healthCheckDownAfterFails = config.healthCheckDownAfterFails ? config.healthCheckDownAfterFails : 3;
+ }
+ if (process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP) {
+ config.healthCheckConsiderHttpResponseUp = process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP === 'true';
+ } else {
+ config.healthCheckConsiderHttpResponseUp = config.healthCheckConsiderHttpResponseUp === true;
+ }
}
function setConfig(newConfig) {
@@ -501,6 +564,25 @@ function getConfig() {
return config;
}
+function getConfigForTypeInformation() {
+ // Just return relevant configuration flags
+ // avoid to include server, authentication, mongodb, orion and iotamanger info
+ const conf = {
+ timestamp: config.timestamp,
+ defaultResource: config.defaultResource,
+ explicitAttrs: config.explicitAttrs,
+ pollingExpiration: config.pollingExpiration,
+ pollingDaemonFrequency: config.pollingDaemonFrequency,
+ multiCore: config.multiCore,
+ relaxTemplateValidation: config.relaxTemplateValidation,
+ defaultEntityNameConjunction: config.defaultEntityNameConjunction,
+ defaultType: config.defaultType,
+ useCBflowControl: config.useCBflowControl,
+ storeLastMeasure: config.storeLastMeasure
+ };
+ return conf;
+}
+
function setRegistry(newRegistry) {
registry = newRegistry;
}
@@ -557,6 +639,7 @@ function getSecurityService() {
exports.setConfig = setConfig;
exports.getConfig = getConfig;
+exports.getConfigForTypeInformation = getConfigForTypeInformation;
exports.setRegistry = setRegistry;
exports.getRegistry = getRegistry;
exports.setGroupRegistry = setGroupRegistry;
diff --git a/lib/constants.js b/lib/constants.js
index b9b570274..03e35d3df 100644
--- a/lib/constants.js
+++ b/lib/constants.js
@@ -61,6 +61,7 @@ module.exports = {
NGSI_LD_TENANT_HEADER: 'NGSILD-Tenant',
NGSI_LD_PATH_HEADER: 'NGSILD-Path',
NGSI_LD_NULL: 'urn:ngsi-ld:null',
+ MEASURE: 'measure_',
//FIXME: check Keystone support this in lowercase, then change
AUTH_HEADER: 'X-Auth-Token',
X_FORWARDED_FOR_HEADER: 'x-forwarded-for',
@@ -83,9 +84,12 @@ module.exports = {
MONGO_ALARM: 'MONGO-ALARM',
ORION_ALARM: 'ORION-ALARM',
IOTAM_ALARM: 'IOTAM-ALARM',
-
+
ATTRIBUTE_DEFAULT,
DATETIME_DEFAULT,
+ CONFIGGROUP_TERM: 'groups',
+ CONFIGGROUP_API_PATH: '/iot/groups',
+
getInitialValueForType
};
diff --git a/lib/errors.js b/lib/errors.js
index 268ef3f96..f23d1e265 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -36,9 +36,15 @@ class UnregistrationError {
}
}
class EntityGenericError {
- constructor(id, type, details, code) {
+ constructor(id, type, typeInformation, details, code) {
this.name = 'ENTITY_GENERIC_ERROR';
- this.message = 'Error accesing entity data for device: ' + id + ' of type: ' + type;
+ this.message =
+ 'Error accesing entity data for device: ' +
+ id +
+ ' of type: ' +
+ type +
+ ' and ' +
+ JSON.stringify(typeInformation);
this.details = details || {};
this.code = code || 200;
}
@@ -77,36 +83,47 @@ class UnsupportedContentType {
}
}
class TypeNotFound {
- constructor(id, type) {
+ constructor(id, type, typeInformation) {
this.name = 'TYPE_NOT_FOUND';
- this.message = 'Type : ' + type + ' not found for device with id: ' + id;
+ this.message =
+ 'Type : ' + type + ' not found for device with id: ' + id + ' with: ' + JSON.stringify(typeInformation);
this.code = 500;
}
}
class MissingAttributes {
- constructor(msg) {
+ constructor(msg, device) {
this.name = 'MISSING_ATTRIBUTES';
- this.message = 'The request was not well formed:' + msg;
+ this.message = 'The request was not well formed:' + msg + ' with: ' + JSON.stringify(device);
}
}
class DeviceNotFound {
- constructor(id) {
+ constructor(id, typeInformation) {
this.name = 'DEVICE_NOT_FOUND';
- this.message = 'No device was found with id:' + id;
+ this.message = 'No device was found with id:' + id + ' and ' + JSON.stringify(typeInformation);
this.code = 404;
}
}
class DuplicateDeviceId {
- constructor(id) {
+ constructor(device) {
this.name = 'DUPLICATE_DEVICE_ID';
- this.message = 'A device with the same pair (Service, DeviceId) was found:' + id;
+ this.message =
+ 'A device with the same info (DeviceId, ApiKey, Service, Subservice) was found:' +
+ device.id +
+ ' and ' +
+ JSON.stringify(device);
this.code = 409;
}
}
class DuplicateGroup {
- constructor(res, key) {
+ constructor(group) {
this.name = 'DUPLICATE_GROUP';
- this.message = 'A device configuration already exists for resource ' + res + ' and API Key ' + key;
+ this.message =
+ 'A device configuration already exists for resource ' +
+ group.resource +
+ ' and API Key ' +
+ group.apikey +
+ ' and ' +
+ JSON.stringify(group);
this.code = 409;
}
}
@@ -170,9 +187,13 @@ class WrongSyntax {
}
}
class CommandNotFound {
- constructor(name) {
+ constructor(name, typeInformation) {
this.name = 'COMMAND_NOT_FOUND';
- this.message = "Couldn't update the command because no command with the name [" + name + '] was found.';
+ this.message =
+ "Couldn't update the command because no command with the name [" +
+ name +
+ '] was found with ' +
+ JSON.stringify(typeInformation);
this.code = 400;
}
}
@@ -201,9 +222,10 @@ class DeviceGroupNotFound {
}
}
class GroupNotFound {
- constructor(service, subservice) {
+ constructor(service, subservice, type) {
this.name = 'GROUP_NOT_FOUND';
- this.message = 'Group not found for service [' + service + '] and subservice [' + subservice + ']';
+ this.message =
+ 'Group not found for service [' + service + '] subservice [' + subservice + '] and type [' + type + ']';
this.code = 404;
}
}
@@ -229,16 +251,22 @@ class BadAnswer {
}
}
class BadTimestamp {
- constructor(payload) {
+ constructor(payload, entityName, typeInformation) {
this.name = 'BAD_TIMESTAMP';
- this.message = 'Invalid ISO8601 timestamp [' + payload + ']';
+ this.message =
+ 'Invalid ISO8601 timestamp [' +
+ payload +
+ '] in [' +
+ entityName +
+ '] with: ' +
+ JSON.stringify(typeInformation);
this.code = 400;
}
}
class BadGeocoordinates {
- constructor(payload) {
+ constructor(payload, device) {
this.name = 'BAD_GEOCOORDINATES';
- this.message = 'Invalid rfc7946 coordinates [' + payload + ']';
+ this.message = 'Invalid rfc7946 coordinates [' + payload + '] in ' + JSON.stringify(device);
this.code = 400;
}
}
diff --git a/lib/fiware-iotagent-lib.js b/lib/fiware-iotagent-lib.js
index e4862feaf..250fe98d7 100644
--- a/lib/fiware-iotagent-lib.js
+++ b/lib/fiware-iotagent-lib.js
@@ -45,22 +45,22 @@ const context = {
op: 'IoTAgentNGSI.Global'
};
+/* eslint-disable-next-line no-unused-vars */
function activateStatLogs(newConfig, callback) {
- if (newConfig.stats && newConfig.stats.interval) {
- async.series(
- [
- apply(statsRegistry.globalLoad, {
- deviceCreationRequests: 0,
- deviceRemovalRequests: 0,
- measureRequests: 0
- }),
- apply(statsRegistry.addTimerAction, statsRegistry.logStats)
- ],
- callback
- );
- } else {
- callback();
- }
+ async.series(
+ [
+ apply(statsRegistry.globalLoad, {
+ deviceCreationRequests: 0,
+ deviceRemovalRequests: 0,
+ measureRequests: 0,
+ raiseAlarm: 0,
+ releaseAlarm: 0,
+ updateEntityRequestsOk: 0,
+ updateEntityRequestsError: 0
+ })
+ ],
+ callback
+ );
}
/**
@@ -302,11 +302,13 @@ exports.update = ngsi.update;
exports.setCommandResult = ngsi.setCommandResult;
exports.listDevices = deviceService.listDevices;
exports.getDevice = deviceService.getDevice;
+exports.updateDevice = deviceService.updateDevice;
exports.getDeviceSilently = deviceService.getDeviceSilently;
exports.getDeviceByName = deviceService.getDeviceByName;
exports.getDeviceByNameAndType = deviceService.getDeviceByNameAndType;
exports.getDevicesByAttribute = deviceService.getDevicesByAttribute;
exports.mergeDeviceWithConfiguration = deviceService.mergeDeviceWithConfiguration;
+exports.findOrCreate = deviceService.findOrCreate;
exports.retrieveDevice = deviceService.retrieveDevice;
exports.getConfiguration = groupConfig.get;
exports.getConfigurationSilently = groupConfig.getSilently;
@@ -315,6 +317,7 @@ exports.setDataUpdateHandler = contextServer.setUpdateHandler;
exports.setCommandHandler = contextServer.setCommandHandler;
exports.setMergePatchHandler = contextServer.setMergePatchHandler;
exports.setDataQueryHandler = contextServer.setQueryHandler;
+exports.executeUpdateSideEffects = contextServer.executeUpdateSideEffects;
exports.setConfigurationHandler = contextServer.setConfigurationHandler;
exports.setRemoveConfigurationHandler = contextServer.setRemoveConfigurationHandler;
exports.setProvisioningHandler = contextServer.setProvisioningHandler;
@@ -349,7 +352,6 @@ exports.middlewares = middlewares;
exports.dataPlugins = {
expressionTransformation: require('./plugins/expressionPlugin'),
- bidirectionalData: require('./plugins/bidirectionalData'),
utils: require('./plugins/pluginUtils')
};
diff --git a/lib/jexlTranformsMap.js b/lib/jexlTranformsMap.js
index 4c263d5be..b424258d0 100644
--- a/lib/jexlTranformsMap.js
+++ b/lib/jexlTranformsMap.js
@@ -26,60 +26,244 @@
JEXL avaliable transformations*/
const map = {
- jsonparse: (str) => JSON.parse(str),
- jsonstringify: (obj) => JSON.stringify(obj),
+ jsonparse: (val) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation(JSON.parse)(val);
+ },
+ jsonstringify: (val) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation(JSON.stringify)(val);
+ },
indexOf: (val, char) => String(val).indexOf(char),
length: (val) => String(val).length,
trim: (val) => String(val).trim(),
substr: (val, int1, int2) => String(val).substr(int1, int2),
- addreduce: (arr) => arr.reduce((i, v) => i + v),
+ addreduce: (arr) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((arr) => arr.reduce((i, v) => i + v))(arr);
+ },
lengtharray: (arr) => arr.length,
typeof: (val) => typeof val,
- isarray: (arr) => Array.isArray(arr),
- isnan: (val) => isNaN(val),
- parseint: (val) => parseInt(val),
- parsefloat: (val) => parseFloat(val),
- toisodate: (val) => new Date(val).toISOString(),
- timeoffset: (isostr) => new Date(isostr).getTimezoneOffset(),
- tostring: (val) => val.toString(),
- urlencode: (val) => encodeURI(val),
- urldecode: (val) => decodeURI(val),
+ isarray: Array.isArray,
+ isnan: isNaN,
+ parseint: (val) => {
+ const safeParseNumber = (fn) => (val) => {
+ const result = fn(val);
+ return isNaN(result) ? null : result;
+ };
+ return safeParseNumber((val) => parseInt(val, 10))(val);
+ },
+ parsefloat: (val) => {
+ const safeParseNumber = (fn) => (val) => {
+ const result = fn(val);
+ return isNaN(result) ? null : result;
+ };
+ return safeParseNumber(parseFloat)(val);
+ },
+ toisodate: (val) => {
+ const safeDateOperation = (fn) => (val) => {
+ const date = new Date(val);
+ return isNaN(date.getTime()) ? null : fn(date);
+ };
+ return safeDateOperation((date) => date.toISOString())(val);
+ },
+ timeoffset: (val) => {
+ const safeDateOperation = (fn) => (val) => {
+ const date = new Date(val);
+ return isNaN(date.getTime()) ? null : fn(date);
+ };
+ return safeDateOperation((date) => date.getTimezoneOffset())(val);
+ },
+ tostring: (val) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((val) => val.toString())(val);
+ },
+ urlencode: encodeURI,
+ urldecode: (val) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation(decodeURI)(val);
+ },
replacestr: (str, from, to) => str.replace(from, to),
- replaceregexp: (str, reg, to) => str.replace(new RegExp(reg), to),
- replaceallstr: (str, from, to) => str.replaceAll(from, to),
- replaceallregexp: (str, reg, to) => str.replaceAll(new RegExp(reg, 'g'), to),
+ replaceregexp: (str, reg, to) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((str, reg, to) => str.replace(new RegExp(reg), to))(str, reg, to);
+ },
+ replaceallregexp: (str, reg, to) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((str, reg, to) => str.replace(new RegExp(reg, 'g'), to))(str, reg, to);
+ },
split: (str, ch) => str.split(ch),
joinarrtostr: (arr, ch) => arr.join(ch),
concatarr: (arr, arr2) => arr.concat(arr2),
mapper: (val, values, choices) => choices[values.findIndex((target) => target === val)],
thmapper: (val, values, choices) =>
- choices[
- values.reduce((acc, curr, i) => (acc === 0 || acc ? acc : val <= curr ? (acc = i) : (acc = null)), null)
- ],
+ choices[values.reduce((acc, curr, i) => (acc !== null ? acc : val <= curr ? i : null), null)],
bitwisemask: (i, mask, op, shf) =>
(op === '&' ? parseInt(i) & mask : op === '|' ? parseInt(i) | mask : op === '^' ? parseInt(i) ^ mask : i) >>
shf,
slice: (arr, init, end) => arr.slice(init, end),
- addset: (arr, x) => {
- return Array.from(new Set(arr).add(x));
- },
+ addset: (arr, x) => Array.from(new Set(arr).add(x)),
removeset: (arr, x) => {
- let s = new Set(arr);
+ const s = new Set(arr);
s.delete(x);
return Array.from(s);
},
touppercase: (val) => String(val).toUpperCase(),
tolowercase: (val) => String(val).toLowerCase(),
- floor: (val) => Math.floor(val),
- ceil: (val) => Math.ceil(val),
- round: (val) => Math.round(val),
- tofixed: (val, decimals) => Number.parseFloat(val).toFixed(decimals),
- gettime: (d) => new Date(d).getTime(),
- toisostring: (d) => new Date(d).toISOString(),
- // https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
- localestring: (d, timezone, options) => new Date(d).toLocaleString(timezone, options),
- now: () => Date.now(),
- hextostring: (val) => new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))
+ floor: Math.floor,
+ ceil: Math.ceil,
+ round: Math.round,
+ tofixed: (val, decimals) => {
+ const num = Number.parseFloat(val);
+ const dec = Number.parseInt(decimals);
+ return isNaN(num) || isNaN(dec) ? null : num.toFixed(dec);
+ },
+ gettime: (val) => {
+ const safeDateOperation = (fn) => (val) => {
+ const date = new Date(val);
+ return isNaN(date.getTime()) ? null : fn(date);
+ };
+ return safeDateOperation((date) => date.getTime())(val);
+ },
+ toisostring: (val) => {
+ const safeDateOperation = (fn) => (val) => {
+ const date = new Date(val);
+ return isNaN(date.getTime()) ? null : fn(date);
+ };
+ return safeDateOperation((date) => date.toISOString())(val);
+ },
+ localestring: (d, timezone, options) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((d, timezone, options) => new Date(d).toLocaleString(timezone, options))(
+ d,
+ timezone,
+ options
+ );
+ },
+ localestringnumber: (n, locale, options) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((n, locale, options) => {
+ if (typeof n !== 'number' || isNaN(n)) return null;
+ return n.toLocaleString(locale, options);
+ })(n, locale, options);
+ },
+ now: Date.now,
+ hextostring: (val) => {
+ const safeOperation =
+ (fn) =>
+ (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((val) => {
+ if (typeof val !== 'string' || !/^[0-9a-fA-F]+$/.test(val) || val.length % 2 !== 0) {
+ return null;
+ }
+ return new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))));
+ })(val);
+ },
+ valuePicker: (val, pick) =>
+ Object.entries(val)
+ .filter(([, v]) => v === pick)
+ .map(([k]) => k),
+ valuePickerMulti: (val, pick) =>
+ Object.entries(val)
+ .filter(([, v]) => pick.includes(v))
+ .map(([k]) => k),
+ hash: (val) => {
+ const safeOperation = (fn) => (...args) => {
+ try {
+ return fn(...args);
+ } catch (e) {
+ return null;
+ }
+ };
+ return safeOperation((val) => {
+ const str = String(val);
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash;
+ }
+ return Math.abs(hash).toString(16).toUpperCase().padStart(8, '0');
+ })(val);
+ }
};
exports.map = map;
diff --git a/lib/model/Command.js b/lib/model/Command.js
index 5205ea0f0..57698ad22 100644
--- a/lib/model/Command.js
+++ b/lib/model/Command.js
@@ -31,11 +31,20 @@ const Command = new Schema({
value: Object,
service: { type: String, lowercase: true },
subservice: String,
+ execTs: { type: Date },
+ status: String,
+ info: String,
+ onDelivered: Object,
+ onOk: Object,
+ onError: Object,
+ onInfo: Object,
+ cmdExecution: Boolean,
+ dateExpiration: { type: Date },
creationDate: { type: Date, default: Date.now }
});
-function load(db) {
- module.exports.model = db.model('Command', Command);
+function load() {
+ module.exports.model = mongoose.model('Command', Command);
module.exports.internalSchema = Command;
}
diff --git a/lib/model/Device.js b/lib/model/Device.js
index 79b7f55fe..f53812ad0 100644
--- a/lib/model/Device.js
+++ b/lib/model/Device.js
@@ -51,13 +51,20 @@ const Device = new Schema({
creationDate: { type: Date, default: Date.now },
internalAttributes: Object,
autoprovision: Boolean,
- expressionLanguage: String,
explicitAttrs: Group.ExplicitAttrsType,
- ngsiVersion: String
+ ngsiVersion: String,
+ payloadType: String,
+ useCBflowControl: Boolean,
+ storeLastMeasure: Boolean,
+ lastMeasure: Object,
+ oldCtxt: Object,
+ cmdMode: String,
+ subscriptionId: String
});
-function load(db) {
- module.exports.model = db.model('Device', Device);
+function load() {
+ Device.index({ service: 1, subservice: 1, apikey: 1, id: 1 }, { unique: true });
+ module.exports.model = mongoose.model('Device', Device);
module.exports.internalSchema = Device;
}
diff --git a/lib/model/Group.js b/lib/model/Group.js
index 8b158c4d7..14ec1c187 100644
--- a/lib/model/Group.js
+++ b/lib/model/Group.js
@@ -45,6 +45,8 @@ const Group = new Schema({
url: String,
resource: String,
apikey: String,
+ endpoint: String,
+ transport: String,
type: String,
service: { type: String, lowercase: true },
subservice: String,
@@ -59,16 +61,19 @@ const Group = new Schema({
attributes: Array,
internalAttributes: Array,
autoprovision: Boolean,
- expressionLanguage: String,
explicitAttrs: ExplicitAttrsType,
defaultEntityNameConjunction: String,
ngsiVersion: String,
- entityNameExp: String
+ entityNameExp: String,
+ payloadType: String,
+ useCBflowControl: Boolean,
+ storeLastMeasure: Boolean,
+ cmdMode: String
});
-function load(db) {
+function load() {
Group.index({ apikey: 1, resource: 1 }, { unique: true });
- module.exports.model = db.model('Group', Group);
+ module.exports.model = mongoose.model('Group', Group);
module.exports.internalSchema = Group;
}
diff --git a/lib/model/dbConn.js b/lib/model/dbConn.js
index 1ea6d8d71..dd9ffa777 100644
--- a/lib/model/dbConn.js
+++ b/lib/model/dbConn.js
@@ -21,7 +21,7 @@
* please contact with::daniel.moranjimenez@telefonica.com
*/
-/**
+/*
* This module sets up the connection with the mongodb through mongoose. This connection will be used
* in mongoose schemas to persist objects.
*/
@@ -31,7 +31,6 @@ const config = require('../commonConfig');
const constants = require('../constants');
const alarms = require('../services/common/alarmManagement');
const logger = require('logops');
-const async = require('async');
const errors = require('../errors');
let defaultDb;
const DEFAULT_DB_NAME = 'iotagent';
@@ -40,9 +39,9 @@ const context = {
};
function loadModels() {
- require('./Device').load(defaultDb);
- require('./Group').load(defaultDb);
- require('./Command').load(defaultDb);
+ require('./Device').load();
+ require('./Group').load();
+ require('./Command').load();
}
/**
@@ -50,179 +49,128 @@ function loadModels() {
*
* @this Reference to the dbConn module itself.
*/
-function init(host, db, port, options, callback) {
- /*jshint camelcase:false, validthis:true */
+function init(host, db, port, options, callback, fullUri = null) {
let url;
let retries = 0;
let lastError;
- const maxRetries =
- (config.getConfig().mongodb && config.getConfig().mongodb.retries) || constants.DEFAULT_MONGODB_RETRIES;
+ const maxRetries = config.getConfig().mongodb?.retries || constants.DEFAULT_MONGODB_RETRIES;
function addPort(item) {
- return item + ':' + port;
- }
-
- function commaConcat(previous, current, currentIndex) {
- if (currentIndex !== 0) {
- previous += ',';
- }
-
- previous += current;
-
- return previous;
+ return `${item}:${port}`;
}
- const hosts = host.split(',').map(addPort).reduce(commaConcat, '');
-
- url = 'mongodb://' + hosts + '/' + db;
-
- if (options.extraArgs) {
- if (options.extraArgs instanceof Object && Object.keys(options.extraArgs).length > 0) {
- url += '?';
- url += Object.entries(options.extraArgs)
- .map(function ([k, v]) {
- return encodeURIComponent(k) + '=' + encodeURIComponent(v);
- })
- .join('&');
- }
- delete options.extraArgs;
- }
+ if (fullUri) {
+ url = fullUri;
+ } else {
+ url = 'mongodb://';
- /* eslint-disable-next-line no-unused-vars */
- function createConnectionHandler(error, results) {
- if (defaultDb) {
- logger.info(context, 'Successfully connected to MongoDB.');
- module.exports.db = defaultDb;
- loadModels();
- } else {
- logger.error(context, 'MONGODB-002: Error found after [%d] attempts: %s', retries, error || lastError);
+ if (options.auth) {
+ url += `${encodeURIComponent(options.auth.user)}:${encodeURIComponent(options.auth.password)}@`;
}
- callback(error);
- }
+ const hosts = host.split(',').map(addPort).join(',');
+ url += `${hosts}/${db}`;
- function retryCheck() {
- return !defaultDb && retries < maxRetries;
+ if (options.extraArgs) {
+ const query = new URLSearchParams(options.extraArgs).toString();
+ if (query) {
+ url += `?${query}`;
+ }
+ delete options.extraArgs;
+ }
}
- function connectionAttempt(url, options, callback) {
- logger.info(context, 'Attempting to connect to MongoDB instance with url %j. Attempt %d', url, retries);
- // FIXME: useNewUrlParser is no longer used in underlying mongodb driver 4.x
- // (see https://github.com/mongodb/node-mongodb-native/blob/HEAD/etc/notes/CHANGES_4.0.0.md)
- // but not sure if current mongoose version is still using mongodb 3.x internally
- // probably mongodb-connectionoptions-test.js needs to be fixed if useNewUrlParser is removed at the end
- options.useNewUrlParser = true;
- mongoose.set('useCreateIndex', true);
- /* eslint-disable-next-line no-unused-vars */
- const candidateDb = mongoose.createConnection(url, options, function (error, result) {
- if (error) {
- logger.error(context, 'MONGODB-001: Error trying to connect to MongoDB: %s', error);
- lastError = error;
- } else {
- defaultDb = candidateDb;
+ function connectionAttempt(callback) {
+ logger.info(context, `Attempting to connect to MongoDB at ${url}. Attempt ${retries + 1}`);
+ mongoose
+ .connect(url, options)
+ .then(() => {
+ defaultDb = mongoose.connection;
+ logger.info(context, 'Successfully connected to MongoDB.');
+ loadModels();
defaultDb.on('error', function (error) {
logger.error(context, 'Mongo Driver error: %j', error);
+ lastError = error;
alarms.raise(constants.MONGO_ALARM, error);
});
- /* eslint-disable-next-line no-unused-vars */
- defaultDb.on('connecting', function (error) {
- logger.debug(context, 'Mongo Driver connecting');
- });
-
- defaultDb.on('connected', function () {
- logger.debug(context, 'Mongo Driver connected');
- });
- defaultDb.on('reconnected', function () {
- logger.debug(context, 'Mongo Driver reconnected');
- });
- defaultDb.on('disconnected', function () {
- logger.debug(context, 'Mongo Driver disconnected');
- });
- defaultDb.on('reconnectFailed', function () {
+ defaultDb.on('connecting', () => logger.debug(context, 'Mongo Driver connecting'));
+ defaultDb.on('connected', () => logger.debug(context, 'Mongo Driver connected'));
+ defaultDb.on('reconnected', () => logger.debug(context, 'Mongo Driver reconnected'));
+ defaultDb.on('disconnected', () => logger.debug(context, 'Mongo Driver disconnected'));
+ defaultDb.on('reconnectFailed', () => {
logger.error(context, 'MONGODB-004: MongoDB connection was lost');
process.exit(1);
});
- defaultDb.on('disconnecting', function () {
- logger.debug(context, 'Mongo Driver disconnecting');
- });
- defaultDb.on('open', function () {
- logger.debug(context, 'Mongo Driver open');
- });
- defaultDb.on('close', function () {
- logger.debug(context, 'Mongo Driver close');
- });
- }
-
- callback();
- });
- }
-
- function tryCreateConnection(callback) {
- const attempt = async.apply(connectionAttempt, url, options, callback);
- const seconds =
- (config.getConfig().mongodb && config.getConfig().mongodb.retryTime) ||
- constants.DEFAULT_MONGODB_RETRY_TIME;
-
- retries++;
-
- if (retries === 1) {
- logger.info(context, 'First connection attempt');
- attempt();
- } else {
- logger.info(context, 'Waiting %d seconds before attempting again.', seconds);
- setTimeout(attempt, seconds * 1000);
- }
+ defaultDb.on('disconnecting', () => logger.debug(context, 'Mongo Driver disconnecting'));
+ defaultDb.on('open', () => logger.debug(context, 'Mongo Driver open'));
+ defaultDb.on('close', () => logger.debug(context, 'Mongo Driver close'));
+ callback();
+ })
+ .catch((err) => {
+ logger.error(context, `MONGODB-001: Error trying to connect to MongoDB: ${err}`);
+ lastError = err;
+ retries++;
+ if (retries < maxRetries) {
+ const retryTime = config.getConfig().mongodb?.retryTime || constants.DEFAULT_MONGODB_RETRY_TIME;
+ logger.info(context, `Retrying in ${retryTime} seconds...`);
+ setTimeout(() => connectionAttempt(callback), retryTime * 1000);
+ } else {
+ logger.error(
+ context,
+ 'MONGODB-002: Error to connect found after %d attempts: %s',
+ retries,
+ lastError
+ );
+ callback(err);
+ }
+ });
}
- defaultDb = null;
- async.whilst(retryCheck, tryCreateConnection, createConnectionHandler);
+ connectionAttempt(callback);
}
function configureDb(callback) {
- /*jshint camelcase:false, validthis:true */
const currentConfig = config.getConfig();
- if (
- (currentConfig.deviceRegistry && currentConfig.deviceRegistry.type === 'mongodb') ||
- (currentConfig.stats && currentConfig.stats.persistence === true)
- ) {
- if (!currentConfig.mongodb || !currentConfig.mongodb.host) {
- logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
- callback(new errors.BadConfiguration('No host found for MongoDB driver'));
- } else {
- let dbName = currentConfig.mongodb.db;
- const port = currentConfig.mongodb.port || 27017;
- const options = {};
-
- if (!currentConfig.mongodb.db) {
- dbName = DEFAULT_DB_NAME;
- }
-
- if (currentConfig.mongodb.replicaSet) {
- options.replicaSet = currentConfig.mongodb.replicaSet;
- }
-
- if (currentConfig.mongodb.user && currentConfig.mongodb.password) {
- options.auth = {};
- options.auth.user = currentConfig.mongodb.user;
- options.auth.password = currentConfig.mongodb.password;
- }
+ if (currentConfig.deviceRegistry?.type === 'mongodb') {
+ const mongoCfg = currentConfig.mongodb;
- if (currentConfig.mongodb.authSource) {
- options.authSource = currentConfig.mongodb.authSource;
- }
+ if (mongoCfg?.uri) {
+ const options = {};
+ logger.info(context, `Using full MongoDB URI from configuration`);
+ init(null, null, null, options, callback, mongoCfg.uri);
+ return;
+ }
- if (currentConfig.mongodb.ssl) {
- options.ssl = currentConfig.mongodb.ssl;
- }
+ if (!mongoCfg?.host) {
+ logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
+ callback(new errors.BadConfiguration('No host found for MongoDB driver'));
+ return;
+ }
- if (currentConfig.mongodb.extraArgs) {
- options.extraArgs = currentConfig.mongodb.extraArgs;
+ const dbName = mongoCfg.db || DEFAULT_DB_NAME;
+ const port = mongoCfg.port || 27017;
+ const options = {};
+
+ if (mongoCfg.replicaSet) options.replicaSet = mongoCfg.replicaSet;
+ if (mongoCfg.ssl) options.ssl = mongoCfg.ssl;
+ if (mongoCfg.extraArgs) options.extraArgs = mongoCfg.extraArgs;
+
+ if (mongoCfg.user && mongoCfg.password) {
+ options.auth = {
+ user: mongoCfg.user,
+ password: mongoCfg.password
+ };
+ if (mongoCfg.authSource) {
+ options.extraArgs = {
+ ...options.extraArgs,
+ authSource: mongoCfg.authSource
+ };
}
-
- init(config.getConfig().mongodb.host, dbName, port, options, callback);
}
+
+ init(mongoCfg.host, dbName, port, options, callback);
} else {
callback();
}
diff --git a/lib/plugins/bidirectionalData.js b/lib/plugins/bidirectionalData.js
deleted file mode 100644
index 81320a909..000000000
--- a/lib/plugins/bidirectionalData.js
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-const async = require('async');
-const apply = async.apply;
-const _ = require('underscore');
-const parser = require('./expressionPlugin');
-const logger = require('logops');
-const config = require('../commonConfig');
-const subscriptions = require('../services/ngsi/subscriptionService');
-const deviceService = require('../services/devices/deviceService');
-const context = {
- op: 'IoTAgentNGSI.BidirectionalPlugin'
-};
-
-/**
- * Extract a list of all the bidirectional attributes (those containing reverse expressions) from a device object.
- * If the device belongs to a Configuration Group, its attributes are also considered.
- *
- * @param {Object} device Device data object.
- * @param {Object} group Configuration Group data object.
- */
-function extractBidirectionalAttributes(device, group, callback) {
- let attributeList;
-
- function isBidirectional(item) {
- return item.reverse;
- }
-
- if (device.active) {
- attributeList = device.active.filter(isBidirectional);
- } else {
- attributeList = [];
- }
-
- if (group && group.attributes) {
- attributeList = attributeList.concat(group.attributes.filter(isBidirectional));
- }
-
- logger.debug(context, 'Extracting attribute list');
-
- callback(null, attributeList);
-}
-
-/**
- * Extract all the variables that exists in the collection of reverse attribute expressions of an attribute.
- *
- * @param {Object} item Attribute with a collection of reverse expressions.
- * @return {Array} List of variables in all the collection of reverse expressions.
- */
-function extractVariables(item) {
- let variables;
-
- function extractFromExpression(value) {
- if (value.expression) {
- return parser.extractVariables(value.expression);
- }
- return [];
- }
-
- if (item.reverse) {
- variables = _.uniq(_.flatten(item.reverse.map(extractFromExpression)));
- }
-
- logger.debug(context, 'Extracted variables: %j', variables);
-
- return variables;
-}
-
-/**
- * Send a subscription for each reverse attribute defined for a device.
- *
- * @param {Object} device Device data object.
- * @param {Array} attributeList List of active attributes for subscription.
- */
-function sendSubscriptions(device, attributeList, callback) {
- function sendSingleSubscriptionNgsi2(item, innerCb) {
- const variables = extractVariables(item);
-
- subscriptions.subscribe(device, [item.name], variables, function handleSubscription(error, subId) {
- if (error) {
- innerCb(error);
- } else {
- innerCb(null, {
- id: subId.substr(subId.lastIndexOf('/') + 1),
- triggers: [item.name]
- });
- }
- });
- }
-
- logger.debug(context, 'Sending bidirectionality subscriptions for device [%s]', device.id);
-
- async.map(attributeList, sendSingleSubscriptionNgsi2, callback);
-}
-
-/**
- * Add the list of generated subscription IDs to the device object.
- *
- * @param {Array} subscriptionMaps List of subscription IDs to be saved.
- * @param {Object} device Device data object.
- * @return {Object} Modified device object.
- */
-function updateDeviceWithSubscriptionIds(subscriptionMaps, device) {
- if (!device.subscriptions) {
- device.subscriptions = [];
- }
-
- device.subscriptions = device.subscriptions.concat(subscriptionMaps);
-
- return device;
-}
-
-/**
- * Middleware to handle incoming Configuration group provisions. Should check for the existence of reverse active
- * attributes and create subscriptions for the modifciation of those values.
- *
- * @param {Object} newGroup Configuration Group data object.
- */
-function handleGroupProvision(newGroup, callback) {
- callback(null, newGroup);
-}
-
-/**
- * Get a list of all the reverse transformations of a device that can be processed with the information reported by
- * the incoming notification.
- *
- * @param {Array} list List of attributes to be checked
- * @param {Array} values List of reported attributes (with their name, type and value).
- */
-function getReverseTransformations(list, values, callback) {
- const availableData = _.pluck(values, 'name');
- let transformations = [];
-
- function getVariable(expression) {
- return parser.extractVariables(expression);
- }
-
- if (list && list.length > 0) {
- for (let i = 0; i < list.length; i++) {
- if (list[i].reverse && list[i].reverse.length > 0) {
- const expressions = _.pluck(list[i].reverse, 'expression');
- const variables = _.uniq(_.flatten(expressions.map(getVariable)));
-
- if (_.difference(variables, availableData).length === 0) {
- transformations = transformations.concat(list[i].reverse);
- }
- }
- }
- }
-
- logger.debug(context, 'Got the following transformations: %j', transformations);
-
- callback(null, transformations);
-}
-
-/**
- * Apply a list of transformations to the reported values,
- * generating a new array of values with the additional data.
- * For NGSI-v2 this consists of value transformations only
- *
- * @param {Array} values List of reported attributes (with their name, type and value).
- * @param {Object} typeInformation Provisioning information about the device represented by the entity.
- * @param {Array} transformations List of transformations to apply (with their name, type and expression).
- */
-function processTransformationsNGSIv2(values, typeInformation, transformations, callback) {
- let cleanedExpression;
- const ctx = parser.extractContext(values);
- let resultTransformations = [];
-
- for (let j = 0; j < 2; j++) {
- if (transformations[j]) {
- resultTransformations = resultTransformations.concat(transformations[j]);
- }
- }
-
- for (let i = 0; i < resultTransformations.length; i++) {
- cleanedExpression = resultTransformations[i].expression;
-
- values.push({
- name: resultTransformations[i].object_id,
- type: resultTransformations[i].type,
- value: parser.parse(cleanedExpression, ctx, 'String', typeInformation)
- });
- }
-
- callback(null, values);
-}
-
-/**
- * Apply a list of transformations to the reported values,
- * generating a new array of values with the additional data.
- * For NGSI-LD the output includes value, metadata and datasetId
- *
- * @param {Array} values List of reported attributes (with their name, type and value).
- * @param {Object} typeInformation Provisioning information about the device represented by the entity.
- * @param {Array} transformations List of transformations to apply (with their name, type and expression).
- */
-function processTransformationsNGSILD(values, typeInformation, transformations, callback) {
- let cleanedExpression;
- const defaultValues = [];
- const datasetIds = {};
-
- // Split incoming values into those with and without datasetId
- values.forEach((value) => {
- if (value.datasetId) {
- datasetIds[value.datasetId] = datasetIds[value.datasetId] || [];
- datasetIds[value.datasetId].push(value);
- } else {
- defaultValues.push(value);
- }
- });
-
- let resultTransformations = [];
-
- for (let j = 0; j < 2; j++) {
- if (transformations[j]) {
- resultTransformations = resultTransformations.concat(transformations[j]);
- }
- }
-
- // Process Transformations using the default datasetId
- // This is the direct equivalent of the NGSI-v2 function
- if (!_.isEmpty(defaultValues)) {
- for (let i = 0; i < resultTransformations.length; i++) {
- cleanedExpression = resultTransformations[i].expression;
- values.push({
- name: resultTransformations[i].object_id,
- type: resultTransformations[i].type,
- metadata: {},
- value: parser.parse(cleanedExpression, parser.extractContext(defaultValues), 'String', typeInformation)
- });
- }
- }
-
- // Process Transformations using an explicit datasetId
- // There is no equivalent with NGSI-v2
- _.keys(datasetIds).forEach((datasetId) => {
- for (let i = 0; i < resultTransformations.length; i++) {
- cleanedExpression = resultTransformations[i].expression;
- values.push({
- name: resultTransformations[i].object_id,
- type: resultTransformations[i].type,
- datasetId,
- metadata: {},
- value: parser.parse(
- cleanedExpression,
- parser.extractContext(datasetIds[datasetId]),
- 'String',
- typeInformation
- )
- });
- }
- });
- callback(null, values);
-}
-
-/**
- * Apply a list of transformations to the reported values, generating a new array of values with the additional data.
- *
- * @param {Array} values List of reported attributes (with their name, type and value).
- * @param {Object} typeInformation Provisioning information about the device represented by the entity.
- * @param {Array} transformations List of transformations to apply (with their name, type and expression).
- */
-function processTransformations(values, typeInformation, transformations, callback) {
- if (config.checkNgsiLD({})) {
- processTransformationsNGSILD(values, typeInformation, transformations, callback);
- } else {
- processTransformationsNGSIv2(values, typeInformation, transformations, callback);
- }
-}
-
-/**
- * Middleware to handle incoming Configuration group provisions. Should check for the existence of reverse active
- * attributes and create subscriptions for the modifciation of those values.
- *
- * @param {Object} device Device data object.
- */
-function handleDeviceProvision(device, callback) {
- deviceService.findConfigurationGroup(device, function (error, group) {
- if (error) {
- callback(error);
- } else {
- async.waterfall(
- [apply(extractBidirectionalAttributes, device, group), apply(sendSubscriptions, device)],
- function (error, subscriptionMaps) {
- if (error) {
- callback(error);
- } else {
- device = updateDeviceWithSubscriptionIds(subscriptionMaps, device);
- callback(null, device);
- }
- }
- );
- }
- });
-}
-
-/**
- * Handles an incoming notification, modifying the reported values if the device has any bidirectional expression
- * defined for its active attributes.
- *
- * @param {Object} device Device data object.
- * @param {Array} values List of notified values.
- */
-function handleNotification(device, values, callback) {
- deviceService.findConfigurationGroup(device, function (error, group) {
- const deviceAttributes = device.active || [];
- const groupAttributes = (group && group.attributes) || [];
-
- if (deviceAttributes.length > 0 || groupAttributes.length > 0) {
- logger.debug(context, 'Processing active attributes notification');
-
- async.waterfall(
- [
- apply(async.series, [
- apply(getReverseTransformations, deviceAttributes, values),
- apply(getReverseTransformations, groupAttributes, values)
- ]),
- apply(processTransformations, values, device)
- ],
- function (error, results) {
- callback(error, device, results);
- }
- );
- } else {
- callback(null, device, values);
- }
- });
-}
-
-exports.deviceProvision = handleDeviceProvision;
-exports.groupProvision = handleGroupProvision;
-exports.notification = handleNotification;
diff --git a/lib/plugins/expressionPlugin.js b/lib/plugins/expressionPlugin.js
index f5804fd9b..70df7a4e9 100644
--- a/lib/plugins/expressionPlugin.js
+++ b/lib/plugins/expressionPlugin.js
@@ -57,13 +57,8 @@ function contextAvailable(expression, context, typeInformation) {
return jexlParser.contextAvailable(expression, context);
}
-function extractVariables(expression) {
- return jexlParser.extractVariables(expression);
-}
-
exports.parse = parse;
exports.setJEXLTransforms = setJEXLTransforms;
exports.applyExpression = applyExpression;
exports.extractContext = extractContext;
exports.contextAvailable = contextAvailable;
-exports.extractVariables = extractVariables;
diff --git a/lib/plugins/jexlParser.js b/lib/plugins/jexlParser.js
index 31865e34d..0b5edf399 100644
--- a/lib/plugins/jexlParser.js
+++ b/lib/plugins/jexlParser.js
@@ -28,8 +28,6 @@
/* eslint-disable no-unused-vars */
const jexl = require('jexl');
-const grammar = require('jexl/dist/grammar').getGrammar();
-const Lexer = require('jexl/dist/Lexer');
const errors = require('../errors');
const logger = require('logops');
const fillService = require('../services/common/domain').fillService;
@@ -47,7 +45,7 @@ function parse(expression, context, callback) {
result = jexl.evalSync(expression, context);
//avoid undefined result
result = result !== undefined ? result : null;
- logger.debug(logContext, 'parse expression "[%j]" over "[%j]" result "[%j]" ', expression, context, result);
+ logger.debug(logContext, 'parse expression %j over %j result %j ', expression, context, result);
} catch (e) {
error = new errors.InvalidExpression(expression);
if (callback) {
@@ -64,31 +62,6 @@ function parse(expression, context, callback) {
}
}
-function extractVariables(expression) {
- const inst = new Lexer(grammar);
- let variables = [];
-
- try {
- const tokens = inst.tokenize(expression);
-
- // Keep only root attributes, removing the dot and sub-attributes. For example, if we have
- // a.0.b, a.1.b and a.2.b, we will only keep a
- // Additionaly, it will remove the function calls, since they are also detected as identifiers
- variables = tokens.filter(function (token, index, array) {
- return (
- (token.type === ' ' && array[index - 1].type !== 'dot') ||
- (token.type === 'identifier' && array[index + 1].type !== 'openParen')
- );
- });
-
- // Return only array of values
- return variables.map((a) => a.value);
- } catch (e) {
- logger.warn(logContext, 'Wrong expression found "[%j]" error: "[%s]", it will be ignored', expression, e);
- return false;
- }
-}
-
function extractContext(attributeList) {
const context = {};
let value;
@@ -125,12 +98,24 @@ function extractContext(attributeList) {
function applyExpression(expression, context, typeInformation) {
logContext = fillService(logContext, typeInformation);
+ // Delete null values from context. Related:
+ // https://github.com/telefonicaid/iotagent-node-lib/issues/1440
+ // https://github.com/TomFrost/Jexl/issues/133
+ deleteNullsAndNaN(context);
const result = parse(expression, context);
- logger.debug(logContext, 'applyExpression "[%j]" over "[%j]" result "[%j]" ', expression, context, result);
+ logger.debug(logContext, 'applyExpression %j over %j result %j ', expression, context, result);
const expressionResult = result !== undefined ? result : expression;
return expressionResult;
}
+function deleteNullsAndNaN(object) {
+ for (let key in object) {
+ if (object[key] === null || Number.isNaN(object[key])) {
+ delete object[key];
+ }
+ }
+}
+
function isTransform(identifier) {
return jexl.getTransform(identifier) !== (null || undefined);
}
@@ -141,14 +126,14 @@ function contextAvailable(expression, context) {
jexl.evalSync(expression, context);
return true;
} catch (e) {
- logger.warn(logContext, 'Wrong expression found "[%j]" over "[%j]", it will be ignored', expression, context);
+ logger.info(logContext, 'Wrong expression found %j over %j, it will be ignored', expression, context);
return false;
}
}
function checkTransformationMap(tranformsMap) {
let error = null;
- let message = 'No trasformations were added to JEXL Parser';
+ let message = 'No transformations were added to JEXL Parser';
let resultMap = {};
if (typeof tranformsMap != 'object') {
@@ -169,7 +154,7 @@ function checkTransformationMap(tranformsMap) {
}
}
if (wrongList.length === 0) {
- message = 'Trasformations can be added to JEXL parser';
+ message = 'Transformations can be added to JEXL parser';
} else {
message = wrongList.toString() + ' must be a function';
}
@@ -185,11 +170,12 @@ function setTransforms(configMap) {
if (!error) {
//merge baseTransformation with provided map
jexl.addTransforms(cleanTranformsMap);
+ logger.info(logContext, message);
+ } else {
+ logger.warn(logContext, message);
}
- logger.info(logContext, message);
}
-exports.extractVariables = extractVariables;
exports.extractContext = extractContext;
exports.contextAvailable = contextAvailable;
exports.applyExpression = applyExpression;
diff --git a/lib/request-shim.js b/lib/request-shim.js
index f1aab860e..0b009bf36 100644
--- a/lib/request-shim.js
+++ b/lib/request-shim.js
@@ -53,13 +53,14 @@ function getOptions(options) {
headers: options.headers,
throwHttpErrors: options.throwHttpErrors || false,
retry: options.retry || 0,
- responseType: options.responseType || 'json'
+ responseType: options.responseType || 'json',
+ timeout: options.timeout || undefined
};
// got library is not properly documented, so it is not clear which takes precedence
// among body, json and form (see https://stackoverflow.com/q/70754880/1485926).
// Thus, we are enforcing our own precedence with the "else if" chain below.
- // Behaviour is consistent with the one described at usermanual.md#iotagentlibrequest
+ // Behaviour is consistent with the one described at development.md#iotagentlibrequest
if (options.method === 'GET' || options.method === 'HEAD' || options.method === 'OPTIONS') {
// Do nothing - Never add a body
@@ -70,7 +71,7 @@ function getOptions(options) {
// json takes precedence over form
httpOptions.json = options.json;
} else if (options.form) {
- // Note that we don't consider 'form' part of the function API (check usermanual.md#iotagentlibrequest)
+ // Note that we don't consider 'form' part of the function API (check development.md#iotagentlibrequest)
// but we are preparing the code anyway as a safe measure
httpOptions.form = options.form;
}
diff --git a/lib/services/commands/commandRegistryMemory.js b/lib/services/commands/commandRegistryMemory.js
index b1948d044..e0a59577c 100644
--- a/lib/services/commands/commandRegistryMemory.js
+++ b/lib/services/commands/commandRegistryMemory.js
@@ -75,7 +75,12 @@ function updateCommand(service, subservice, deviceId, command, callback) {
callback(null, foundCommand);
} else {
- callback(new errors.CommandNotFound(command.name));
+ const deviceInfo = {
+ service,
+ subservice,
+ deviceId
+ };
+ callback(new errors.CommandNotFound(command.name, deviceInfo));
}
}
@@ -144,7 +149,12 @@ function remove(service, subservice, deviceId, name, callback) {
delete registeredCommands[foundCommand._id];
callback(null, foundCommand);
} else {
- callback(new errors.CommandNotFound(name));
+ const deviceInfo = {
+ service,
+ subservice,
+ deviceId
+ };
+ callback(new errors.CommandNotFound(name, deviceInfo));
}
}
diff --git a/lib/services/commands/commandRegistryMongoDB.js b/lib/services/commands/commandRegistryMongoDB.js
index 31efedc3a..a0561e8ca 100644
--- a/lib/services/commands/commandRegistryMongoDB.js
+++ b/lib/services/commands/commandRegistryMongoDB.js
@@ -22,7 +22,7 @@
*/
const logger = require('logops');
-const dbService = require('../../model/dbConn');
+const mongoose = require('mongoose');
const intoTrans = require('../common/domain').intoTrans;
const errors = require('../../errors');
const Command = require('../../model/Command');
@@ -39,24 +39,32 @@ function findCommand(service, subservice, deviceId, name, callback) {
name
};
- logger.debug(context, 'Looking for command [%s] for device [%s]', name, deviceId);
+ logger.debug(context, 'Looking for command [%s] for device [%s] with [%j]', name, deviceId, queryObj);
const query = Command.model.findOne(queryObj);
query.select({ __v: 0 });
- query.exec(function handleGet(error, data) {
- if (error) {
+ query
+ .exec({})
+ .then((data) => {
+ if (data) {
+ callback(null, data);
+ } else {
+ logger.debug(
+ context,
+ 'Command for DeviceID [%j] with name [%j] not found with [%j]',
+ deviceId,
+ name,
+ queryObj
+ );
+ callback(new errors.CommandNotFound(name, queryObj));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting command: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (data) {
- callback(null, data);
- } else {
- logger.debug(context, 'Command for DeviceID [%j] with name [%j] not found', deviceId, name);
- callback(new errors.CommandNotFound(name));
- }
- });
+ });
}
function updateCommand(service, subservice, deviceId, command, callback) {
@@ -66,9 +74,14 @@ function updateCommand(service, subservice, deviceId, command, callback) {
} else {
commandDAO.value = command.value;
- commandDAO.save(function (error) {
- callback(error, commandDAO.toObject());
- });
+ commandDAO
+ .save({})
+ .then((commandDAOs) => {
+ callback(null, commandDAOs.toObject());
+ })
+ .catch((error) => {
+ callback(error);
+ });
}
});
}
@@ -76,7 +89,21 @@ function updateCommand(service, subservice, deviceId, command, callback) {
function createCommand(service, subservice, deviceId, command, callback) {
/* eslint-disable-next-line new-cap */
const commandObj = new Command.model();
- const attributeList = ['name', 'type', 'value'];
+ const attributeList = [
+ 'name',
+ 'type',
+ 'value',
+ // new Command fields
+ 'execTs',
+ 'status',
+ 'info',
+ 'onDelivered',
+ 'onOk',
+ 'onError',
+ 'onInfo',
+ 'cmdExecution',
+ 'dateExpiration'
+ ];
for (let i = 0; i < attributeList.length; i++) {
commandObj[attributeList[i]] = command[attributeList[i]];
@@ -88,15 +115,15 @@ function createCommand(service, subservice, deviceId, command, callback) {
logger.debug(context, 'Storing command for deviceId [%s] with name [%s]', deviceId, command.name);
- commandObj.save(function saveHandler(error, commandDAO) {
- if (error) {
+ commandObj
+ .save({})
+ .then((commandDAO) => {
+ callback(null, commandDAO.toObject());
+ })
+ .catch((error) => {
logger.debug(context, 'Error storing command information: %s', error);
-
callback(new errors.InternalDbError(error));
- } else {
- callback(null, commandDAO.toObject());
- }
- });
+ });
}
function addCommand(service, subservice, deviceId, command, callback) {
@@ -123,11 +150,28 @@ function listCommands(service, subservice, deviceId, callback) {
condition.deviceId = deviceId;
const query = Command.model.find(condition).sort();
-
- async.series([query.exec.bind(query), Command.model.countDocuments.bind(Command.model, condition)], function (
- error,
- results
- ) {
+ const queryCount = Command.model.countDocuments(condition);
+ function funcQuery(cb) {
+ query
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ function funcQueryCount(cb) {
+ queryCount
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ async.series([funcQuery, funcQueryCount], function (error, results) {
callback(error, {
count: results[1],
commands: results[0].map(toObjectFn)
@@ -149,21 +193,29 @@ function remove(service, subservice, deviceId, name, callback) {
if (error) {
callback(error);
} else {
- Command.model.deleteOne({ _id: command._id }, function (error, commandResult) {
- if (error) {
+ const query = Command.model.deleteOne({ _id: command._id });
+ query
+ .exec({})
+ .then((commandResult) => {
+ if (commandResult && commandResult.result && commandResult.result.n === 1) {
+ logger.debug(context, 'Command [%s] successfully removed.', name);
+
+ callback(null, commandResult);
+ } else {
+ const deviceInfo = {
+ service,
+ subservice,
+ deviceId,
+ name
+ };
+ logger.debug(context, 'Command [%s] not found for removal with %j', name, deviceInfo);
+ callback(new errors.CommandNotFound(name, deviceInfo));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting command: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (commandResult && commandResult.result && commandResult.result.n === 1) {
- logger.debug(context, 'Command [%s] successfully removed.', name);
-
- callback(null, commandResult);
- } else {
- logger.debug(context, 'Command [%s] not found for removal.', name);
-
- callback(new errors.CommandNotFound(name));
- }
- });
+ });
}
});
}
@@ -179,35 +231,48 @@ function listToObject(commandList) {
}
function removeFromDate(creationDate, callback) {
- const query = { creationDate: { $lt: creationDate } };
+ const condition = { creationDate: { $lt: creationDate } };
if (Command.model) {
- Command.model.find(query).exec(function (error, commandList) {
- if (!error && commandList && commandList.length > 0) {
- Command.model.deleteOne(query, function (error) {
- if (error) {
- logger.debug(context, 'Internal MongoDB Error removing expired commands: %s', error);
-
- callback(new errors.InternalDbError(error));
- } else {
- logger.debug(
- context,
- 'Expired commands successfully removed from MongoDB for date [%s]',
- creationDate
- );
-
- callback(null, listToObject(commandList));
- }
- });
- }
- });
+ const query = Command.model.find(condition);
+ query
+ .exec({})
+ .then((commandList) => {
+ if (commandList && commandList.length > 0) {
+ const queryDel = Command.model.deleteOne(condition);
+ queryDel
+ .exec({})
+ .then(() => {
+ logger.debug(
+ context,
+ 'Expired commands successfully removed from MongoDB for date [%s]',
+ creationDate
+ );
+ callback(null, listToObject(commandList));
+ })
+ .catch((error) => {
+ logger.debug(context, 'Internal MongoDB Error removing expired commands: %s', error);
+ callback(new errors.InternalDbError(error));
+ });
+ }
+ })
+ .catch((error) => {
+ logger.debug(context, 'Internal MongoDB Error looking for expired commands: %s', error);
+ });
} else {
callback(null, []);
}
}
function clear(callback) {
- dbService.db.db.dropDatabase(callback);
+ mongoose.connection
+ .dropDatabase()
+ .then(() => {
+ callback(null);
+ })
+ .catch((error) => {
+ callback(error);
+ });
}
function init(newConfig, callback) {
diff --git a/lib/services/commands/commandService.js b/lib/services/commands/commandService.js
index b6452579e..af766a6a0 100644
--- a/lib/services/commands/commandService.js
+++ b/lib/services/commands/commandService.js
@@ -43,7 +43,7 @@ function listCommands(service, subservice, deviceId, callback) {
}
function addCommand(service, subservice, deviceId, command, callback) {
- logger.debug(context, 'Adding command [%j] to the queue for device [%s]', command, deviceId);
+ logger.debug(context, 'Adding command [%j] to the queue for deviceId [%s]', command, deviceId);
config.getCommandRegistry().add(service, subservice, deviceId, command, callback);
}
@@ -73,13 +73,13 @@ function addCommandDevice(service, subservice, device, command, callback) {
}
function updateCommand(service, subservice, deviceId, name, value, callback) {
- logger.debug(context, 'Updating command [%s] for device [%s] with value [%s]', name, deviceId, value);
+ logger.debug(context, 'Updating command [%s] for deviceId [%s] with value [%s]', name, deviceId, value);
config.getCommandRegistry().update(service, subservice, deviceId, value, callback);
}
function removeCommand(service, subservice, deviceId, name, callback) {
- logger.debug(context, 'Removing command [%s] from device [%s]', name, deviceId);
+ logger.debug(context, 'Removing command [%s] from deviceId [%s]', name, deviceId);
config.getCommandRegistry().remove(service, subservice, deviceId, name, callback);
}
@@ -120,7 +120,7 @@ function markAsExpired(command) {
async.waterfall(
[
- apply(deviceService.getDevice, command.deviceId, command.service, command.subservice),
+ apply(deviceService.getDevice, command.deviceId, null, command.service, command.subservice),
getGroup,
calculateTypeInformation,
updateExpiredCommand
diff --git a/lib/services/common/alarmManagement.js b/lib/services/common/alarmManagement.js
index fa6e18beb..fad0cc147 100644
--- a/lib/services/common/alarmManagement.js
+++ b/lib/services/common/alarmManagement.js
@@ -22,6 +22,7 @@
*/
let alarmRepository = {};
+const statsRegistry = require('../stats/statsRegistry');
const logger = require('logops');
const context = {
op: 'IoTAgentNGSI.Alarms'
@@ -41,6 +42,7 @@ function raise(alarmName, description) {
};
logger.error(context, 'Raising [%s]: %j', alarmName, description);
+ statsRegistry.add('raiseAlarm', 1, function () {});
}
}
@@ -53,6 +55,7 @@ function release(alarmName) {
if (alarmRepository[alarmName]) {
delete alarmRepository[alarmName];
logger.error(context, 'Releasing [%s]', alarmName);
+ statsRegistry.add('releaseAlarm', 1, function () {});
}
}
diff --git a/lib/services/common/domain.js b/lib/services/common/domain.js
index a81dded30..de1faf413 100644
--- a/lib/services/common/domain.js
+++ b/lib/services/common/domain.js
@@ -68,7 +68,7 @@ function requestDomain(req, res, next) {
reqDomain.path = req.path;
reqDomain.op = req.url;
reqDomain.start = Date.now();
-
+ reqDomain.from = req.ip || req.connection.remoteAddress;
reqDomain.add(req);
reqDomain.add(res);
@@ -145,14 +145,15 @@ function ensureSouthboundTransaction(context, callback) {
if (context.op) {
reqDomain.op = context.op;
}
-
if (context.srv) {
reqDomain.service = context.srv;
}
-
if (context.subsrv) {
reqDomain.subservice = context.subsrv;
}
+ if (context.from) {
+ reqDomain.from = context.from;
+ }
}
return reqDomain.run(callback);
diff --git a/lib/services/common/genericMiddleware.js b/lib/services/common/genericMiddleware.js
index 5f632faea..ea5383986 100644
--- a/lib/services/common/genericMiddleware.js
+++ b/lib/services/common/genericMiddleware.js
@@ -23,11 +23,15 @@
const logger = require('logops');
const revalidator = require('revalidator');
+const statsRegistry = require('../stats/statsRegistry');
const errors = require('../../errors');
+const fillService = require('./domain').fillService;
+const config = require('../../commonConfig');
let iotaInformation;
-const context = {
+let context = {
op: 'IoTAgentNGSI.GenericMiddlewares'
};
+const { getHealthState } = require('./health');
/**
* Express middleware for handling errors in the IoTAs. It extracts the code information to return from the error itself
@@ -39,7 +43,10 @@ const context = {
/* eslint-disable-next-line no-unused-vars */
function handleError(error, req, res, next) {
let code = 500;
-
+ context = fillService(context, {
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ });
logger.debug(context, 'Error [%s] handling request: %s', error.name, error.message);
if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
@@ -56,7 +63,11 @@ function handleError(error, req, res, next) {
* Express middleware for tracing the complete request arriving to the IoTA in debug mode.
*/
function traceRequest(req, res, next) {
- logger.debug(context, 'Request for path [%s] from [%s]', req.path, req.get('host'));
+ context = fillService(context, {
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ });
+ logger.debug(context, 'Request for path [%s] query [%j] from [%s]', req.path, req.query, req.get('host'));
if (req.is('json') || req.is('application/ld+json')) {
logger.debug(context, 'Body:\n\n%s\n\n', JSON.stringify(req.body, null, 4));
@@ -129,6 +140,10 @@ function validateJson(template) {
if (errorList.valid) {
next();
} else {
+ context = fillService(context, {
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ });
logger.debug(context, 'Errors found validating request: %j', errorList);
next(new errors.BadRequest('Errors found validating request.'));
}
@@ -144,7 +159,21 @@ function validateJson(template) {
/* eslint-disable-next-line no-unused-vars */
function retrieveVersion(req, res, next) {
- res.status(200).json(iotaInformation);
+ res.status(200).json({
+ ...iotaInformation,
+ ...(config.getConfig().healthCheck && { connections: getHealthState() })
+ });
+}
+
+/* eslint-disable-next-line no-unused-vars */
+function getReady(req, res, next) {
+ statsRegistry.getGlobal('ready', function (err, ready) {
+ if (ready) {
+ res.status(200).json({});
+ } else {
+ res.status(503).json({});
+ }
+ });
}
/**
@@ -162,5 +191,6 @@ exports.changeLogLevel = changeLogLevel;
exports.ensureType = ensureType;
exports.validateJson = validateJson;
exports.retrieveVersion = retrieveVersion;
+exports.getReady = getReady;
exports.setIotaInformation = setIotaInformation;
exports.getLogLevel = getLogLevel;
diff --git a/lib/services/common/health.js b/lib/services/common/health.js
new file mode 100644
index 000000000..49bb82859
--- /dev/null
+++ b/lib/services/common/health.js
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::daniel.moranjimenez@telefonica.com
+ */
+
+const request = require('../../request-shim');
+const statsRegistry = require('../stats/statsRegistry');
+
+let healthState = {
+ contextBroker: {
+ ok: null,
+ configured: false,
+ url: null,
+ lastOk: null,
+ lastError: null,
+ latencyMs: null,
+ consecutiveFails: 0
+ },
+ iotagentManager: {
+ ok: null,
+ configured: false,
+ url: null,
+ lastOk: null,
+ lastError: null,
+ latencyMs: null,
+ consecutiveFails: 0
+ },
+ mongodb: {
+ ok: null,
+ configured: false,
+ url: null,
+ lastOk: null,
+ lastError: null,
+ latencyMs: null,
+ consecutiveFails: 0
+ },
+ mqtt: {
+ ok: null,
+ configured: false,
+ url: null,
+ lastOk: null,
+ lastError: null,
+ latencyMs: null,
+ consecutiveFails: 0
+ }
+};
+
+let healthTimer = null;
+
+function getHealthState() {
+ return healthState;
+}
+
+function normalizeBaseUrl(baseUrl) {
+ if (!baseUrl || typeof baseUrl !== 'string') {
+ return null;
+ }
+ const trimmed = baseUrl.trim();
+ if (!trimmed) {
+ return null;
+ }
+ // "orion:1026" is converted to "http://orion:1026"
+ if (!/^https?:\/\//i.test(trimmed)) {
+ return `http://${trimmed}`;
+ }
+ return trimmed;
+}
+
+function buildUrl(base, path) {
+ const safePath = typeof path === 'string' && path.trim() ? path.trim() : '';
+ return new URL(safePath, base).toString();
+}
+
+function fillMongoUrlFromConnection(conn) {
+ try {
+ const client = conn.getClient(); // MongoClient native
+ const desc = client.topology?.description;
+
+ if (!desc || !desc.servers || desc.servers.size === 0) {
+ return null;
+ }
+
+ // Ger first server
+ const [server] = desc.servers.values();
+
+ const host = server.address; // ie.:: "mongo:27017"
+ const dbName = conn.name; // database name
+
+ return `mongodb://${host}/${dbName}`;
+ } catch (e) {
+ return null;
+ }
+}
+/**
+ * Init periodic checks (cached) againts CB and IotAgent-Mananger
+ */
+function startHealthChecks({
+ contextBrokerUrl,
+ iotagentManagerUrl,
+ deviceRegistryType = 'memory',
+ configMqtt,
+ intervalMs,
+ timeoutMs,
+ downAfterFails,
+ considerHttpResponseUp,
+ // Allow change endpoint to use
+ managerPath = '/iot/protocols',
+ cbPath = '/version'
+ // if UP but response was 404
+}) {
+ const cbBase = normalizeBaseUrl(contextBrokerUrl);
+ const iotaMngrBase = normalizeBaseUrl(iotagentManagerUrl);
+ const mongoEnabled = deviceRegistryType && deviceRegistryType !== 'memory';
+
+ // Set as configured / not configured
+ healthState.contextBroker.configured = Boolean(cbBase);
+ healthState.contextBroker.url = cbBase;
+ statsRegistry.set('contextBrokerConfigured', healthState.contextBroker.configured, function () {});
+ statsRegistry.set('contextBrokerUrl', healthState.contextBroker.url, function () {});
+ if (!cbBase) {
+ healthState.contextBroker.ok = null;
+ healthState.contextBroker.lastError = 'Not configured';
+ healthState.contextBroker.consecutiveFails = 0;
+ }
+ healthState.iotagentManager.configured = Boolean(iotaMngrBase);
+ healthState.iotagentManager.url = iotaMngrBase;
+ statsRegistry.set('iotagentManagerConfigured', healthState.iotagentManager.configured, function () {});
+ statsRegistry.set('iotagentManagerUrl', healthState.iotagentManager.url, function () {});
+ if (!iotaMngrBase) {
+ healthState.iotagentManager.ok = null;
+ healthState.iotagentManager.lastError = 'Not configured';
+ healthState.iotagentManager.consecutiveFails = 0;
+ }
+ healthState.mongodb.configured = Boolean(mongoEnabled);
+ statsRegistry.set('mongodbConfigured', healthState.mongodb.configured, function () {});
+ if (!mongoEnabled) {
+ healthState.mongodb.ok = null;
+ healthState.mongodb.lastOk = null;
+ healthState.mongodb.lastError = 'Not configured (deviceRegistry.type=memory)';
+ healthState.mongodb.latencyMs = null;
+ healthState.mongodb.consecutiveFails = 0;
+ }
+ let Device;
+ if (mongoEnabled) {
+ try {
+ Device = require('../../model/Device');
+ } catch (e) {
+ // Not available
+ Device = null;
+ }
+ }
+ let mqtt;
+ const mqttEnabled = configMqtt && configMqtt.disabled !== true && configMqtt.host && configMqtt.port;
+ healthState.mqtt.configured = Boolean(mqttEnabled);
+ statsRegistry.set('mqttConfigured', healthState.mqtt.configured, function () {});
+ if (!mqttEnabled) {
+ healthState.mqtt.ok = null;
+ healthState.mqtt.url = null;
+ healthState.mqtt.lastOk = null;
+ healthState.mqtt.lastError = 'Not configured or disabled';
+ healthState.mqtt.latencyMs = null;
+ healthState.mqtt.consecutiveFails = 0;
+ }
+ if (mqttEnabled && !healthState.mqtt.url) {
+ mqtt = require('mqtt');
+ healthState.mqtt.url = `mqtt://${configMqtt.host}:${configMqtt.port}`;
+ statsRegistry.set('mqttUrl', healthState.mqtt.url, function () {});
+ }
+
+ // If none configured, then timer is not started
+ if (!cbBase && !iotaMngrBase && !mongoEnabled && !mqttEnabled) {
+ if (healthTimer) {
+ clearInterval(healthTimer);
+ }
+ healthTimer = null;
+ return { enabled: false };
+ }
+
+ function doRequest(url) {
+ return new Promise((resolve, reject) => {
+ request(
+ {
+ method: 'GET',
+ url,
+ throwHttpErrors: false,
+ retry: 0,
+ responseType: 'text',
+ timeout: { request: timeoutMs }
+ },
+ /* eslint-disable-next-line consistent-return */
+ (err, response, body) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve({ response, body });
+ }
+ );
+ });
+ }
+ async function ping(name, base, urlPath) {
+ let url;
+ try {
+ url = buildUrl(base, urlPath);
+ } catch (e) {
+ // wrong base
+ healthState[name].ok = false;
+ healthState[name].lastError = `Invalid base URL: ${e.message || e}`;
+ healthState[name].consecutiveFails += 1;
+ statsRegistry.set(name + 'OK', false, function () {});
+ statsRegistry.set(name + 'LastError', healthState[name].lastError, function () {});
+ statsRegistry.add(name + 'consecutiveFails', 1, function () {});
+ return;
+ }
+ const t0 = Date.now();
+ try {
+ const { response } = await doRequest(url);
+ const ms = Date.now() - t0;
+ const statusCode = response?.statusCode;
+ const ok = considerHttpResponseUp
+ ? typeof statusCode === 'number'
+ ? statusCode < 500
+ : false
+ : typeof statusCode === 'number'
+ ? statusCode >= 200 && statusCode < 400
+ : false;
+ if (ok) {
+ healthState[name].ok = true;
+ healthState[name].lastOk = new Date().toISOString();
+ healthState[name].lastError = null;
+ healthState[name].latencyMs = ms;
+ healthState[name].consecutiveFails = 0;
+ } else {
+ const fails = (healthState[name].consecutiveFails || 0) + 1;
+ healthState[name].consecutiveFails = fails;
+ healthState[name].latencyMs = ms;
+ healthState[name].lastError = `HTTP ${statusCode} in ${url}`;
+ if (fails >= downAfterFails) {
+ healthState[name].ok = false;
+ }
+ }
+ statsRegistry.set(name + 'OK', healthState[name].ok, function () {});
+ statsRegistry.set(name + 'LastOk', healthState[name].lastOk, function () {});
+ statsRegistry.set(name + 'LastError', healthState[name].lastError, function () {});
+ statsRegistry.set(name + 'LatencyMs', healthState[name].latencyMs, function () {});
+ statsRegistry.set(name + 'ConsecutiveFails', healthState[name].consecutiveFails, function () {});
+ } catch (e) {
+ const ms = Date.now() - t0;
+ const fails = (healthState[name].consecutiveFails || 0) + 1;
+ healthState[name].consecutiveFails = fails;
+ healthState[name].latencyMs = ms;
+ healthState[name].lastError = `${e.message || e} (${url})`;
+ statsRegistry.set(name + 'ConsecutiveFails', fails, function () {});
+ statsRegistry.set(name + 'LatencyMs', ms, function () {});
+ statsRegistry.set(name + 'LastError', healthState[name].lastError, function () {});
+ if (fails >= downAfterFails) {
+ healthState[name].ok = false;
+ statsRegistry.set(name + 'OK', false, function () {});
+ }
+ }
+ }
+ async function pingMongo() {
+ const t0 = Date.now();
+ if (!mongoEnabled) {
+ return;
+ }
+ const conn = Device?.model?.db; // mongoose Connection
+ const rs = conn?.readyState;
+ if (conn && !healthState.mongodb.url) {
+ healthState.mongodb.url = fillMongoUrlFromConnection(conn);
+ statsRegistry.set('mongodbUrl', healthState.mongodb.url, function () {});
+ }
+ if (!conn || rs !== 1 || !conn.db) {
+ const ms = Date.now() - t0;
+ const fails = (healthState.mongodb.consecutiveFails || 0) + 1;
+ healthState.mongodb.consecutiveFails = fails;
+ healthState.mongodb.latencyMs = ms;
+ healthState.mongodb.lastError = `Mongo not ready via Device.model (readyState=${rs})`;
+ statsRegistry.set('mongodbConsecutiveFails', fails, function () {});
+ statsRegistry.set('mongodbLatencyMs', ms, function () {});
+ statsRegistry.set('mongodbLastError', healthState.mongodb.lastError, function () {});
+ if (fails >= downAfterFails) {
+ healthState.mongodb.ok = false;
+ statsRegistry.set('mongodbOK', false, function () {});
+ }
+ return;
+ }
+ let timer;
+ try {
+ const controller = new AbortController();
+ timer = setTimeout(() => controller.abort(), timeoutMs);
+ await conn.db.admin().command({ ping: 1 }, { signal: controller.signal });
+ const ms = Date.now() - t0;
+ healthState.mongodb.ok = true;
+ healthState.mongodb.lastOk = new Date().toISOString();
+ healthState.mongodb.lastError = null;
+ healthState.mongodb.latencyMs = ms;
+ healthState.mongodb.consecutiveFails = 0;
+ statsRegistry.set('mongodbOK', true, function () {});
+ statsRegistry.set('mongodbLastOK', healthState.mongodb.lastOk, function () {});
+ statsRegistry.set('mongodbLastError', null, function () {});
+ statsRegistry.set('mongodbLatencyMs', ms, function () {});
+ statsRegistry.set('mongodbConsecutiveFails', 0, function () {});
+ } catch (e) {
+ const ms = Date.now() - t0;
+ const fails = (healthState.mongodb.consecutiveFails || 0) + 1;
+ healthState.mongodb.consecutiveFails = fails;
+ healthState.mongodb.latencyMs = ms;
+ healthState.mongodb.lastError = e?.message || String(e);
+ statsRegistry.set('mongodbConsecutiveFails', fails, function () {});
+ statsRegistry.set('mongodbLatencyMs', ms, function () {});
+ statsRegistry.set('mongodbLastError', healthState.mongodb.lastError, function () {});
+ if (fails >= downAfterFails) {
+ healthState.mongodb.ok = false;
+ statsRegistry.set('mongodbOK', false, function () {});
+ }
+ } finally {
+ if (timer) {
+ clearTimeout(timer);
+ }
+ }
+ }
+ function pingMqtt(configMqtt, timeoutMs, downAfterFails) {
+ /* eslint-disable-next-line consistent-return */
+ return new Promise((resolve) => {
+ const t0 = Date.now();
+ if (!healthState.mqtt.configured) {
+ return resolve();
+ }
+ const url = `mqtt://${configMqtt.host}:${configMqtt.port}`;
+ const client = mqtt.connect(url, {
+ clientId: `healthcheck_${Math.random().toString(16).slice(2)}`,
+ username: configMqtt.username,
+ password: configMqtt.password,
+ keepalive: Number(configMqtt.keepalive) || 60,
+ reconnectPeriod: 0, // do not retry
+ connectTimeout: timeoutMs,
+ clean: true
+ });
+ let finished = false;
+ const done = (ok, errMsg) => {
+ if (finished) {
+ return;
+ }
+ finished = true;
+ const ms = Date.now() - t0;
+ if (ok) {
+ healthState.mqtt.ok = true;
+ healthState.mqtt.lastOk = new Date().toISOString();
+ healthState.mqtt.lastError = null;
+ healthState.mqtt.latencyMs = ms;
+ healthState.mqtt.consecutiveFails = 0;
+ } else {
+ const fails = (healthState.mqtt.consecutiveFails || 0) + 1;
+ healthState.mqtt.consecutiveFails = fails;
+ healthState.mqtt.latencyMs = ms;
+ healthState.mqtt.lastError = errMsg || `MQTT connect failed to ${url}`;
+ if (fails >= downAfterFails) {
+ healthState.mqtt.ok = false;
+ }
+ }
+ statsRegistry.set('mqttOK', healthState.mqtt.ok, function () {});
+ statsRegistry.set('mqttLastOk', healthState.mqtt.lastOk, function () {});
+ statsRegistry.set('mqttLastError', healthState.mqtt.lastError, function () {});
+ statsRegistry.set('mqttLatencyMs', healthState.mqtt.latencyMs, function () {});
+ statsRegistry.set('mqttConsecutiveFails', healthState.mqtt.consecutiveFails, function () {});
+ client.end(true);
+ resolve();
+ };
+ client.once('connect', () => done(true));
+ client.once('error', (err) => done(false, err.message));
+ client.once('close', () => {
+ if (!finished) {
+ done(false, 'Connection closed before connect');
+ }
+ });
+ // Fallback timeout
+ setTimeout(() => {
+ if (!finished) {
+ done(false, `MQTT timeout after ${timeoutMs}ms`);
+ }
+ }, timeoutMs + 50);
+ });
+ }
+ async function refresh() {
+ const tasks = [];
+ if (cbBase) {
+ tasks.push(ping('contextBroker', cbBase, cbPath));
+ }
+ if (iotaMngrBase) {
+ tasks.push(ping('iotagentManager', iotaMngrBase, managerPath));
+ }
+ if (mongoEnabled) {
+ tasks.push(pingMongo(timeoutMs, downAfterFails));
+ }
+ if (mqttEnabled) {
+ tasks.push(pingMqtt(configMqtt, timeoutMs, downAfterFails));
+ }
+ await Promise.allSettled(tasks);
+ }
+ // first execution
+ refresh();
+
+ // and then
+ if (healthTimer) {
+ clearInterval(healthTimer);
+ }
+ healthTimer = setInterval(refresh, intervalMs);
+ healthTimer.unref?.(); // allow shutdown
+
+ return { enabled: true };
+}
+
+module.exports = {
+ getHealthState,
+ startHealthChecks
+};
diff --git a/lib/services/common/iotManagerService.js b/lib/services/common/iotManagerService.js
index 8abd9c4f7..c1296e97e 100644
--- a/lib/services/common/iotManagerService.js
+++ b/lib/services/common/iotManagerService.js
@@ -58,10 +58,15 @@ function register(callback) {
timestamp: service.timestamp,
autoprovision: service.autoprovision,
explicitAttrs: service.explicitAttrs,
- expressionLanguage: service.expressionLanguage,
defaultEntityNameConjunction: service.defaultEntityNameConjunction,
ngsiVersion: service.ngsiVersion,
- entityNameExp: service.entityNameExp
+ entityNameExp: service.entityNameExp,
+ payloadType: service.payloadType,
+ endpoint: service.endpoint,
+ transport: service.transport,
+ useCBflowControl: service.useCBflowControl,
+ storeLastMeasure: service.storeLastMeasure,
+ cmdMode: service.cmdMode
};
}
diff --git a/lib/services/devices/deviceRegistryMemory.js b/lib/services/devices/deviceRegistryMemory.js
index 8227620fe..93f94f034 100644
--- a/lib/services/devices/deviceRegistryMemory.js
+++ b/lib/services/devices/deviceRegistryMemory.js
@@ -54,7 +54,7 @@ function storeDevice(newDevice, callback) {
}
if (registeredDevices[newDevice.service][newDevice.id]) {
- callback(new errors.DuplicateDeviceId(newDevice.id));
+ callback(new errors.DuplicateDeviceId(newDevice));
} else {
registeredDevices[newDevice.service][newDevice.id] = deepClone(newDevice);
registeredDevices[newDevice.service][newDevice.id].creationDate = Date.now();
@@ -71,7 +71,7 @@ function storeDevice(newDevice, callback) {
* @param {String} service Service of the device to remove.
* @param {String} subservice Subservice inside the service for the removed device.
*/
-function removeDevice(id, service, subservice, callback) {
+function removeDevice(id, apikey, service, subservice, callback) {
const services = Object.keys(registeredDevices);
for (let i = 0; i < services.length; i++) {
@@ -141,7 +141,7 @@ function listDevices(type, service, subservice, limit, offset, callback) {
});
}
-function getDevice(id, service, subservice, callback) {
+function getDevice(id, apikey, service, subservice, callback) {
if (registeredDevices[service] && registeredDevices[service][id]) {
callback(null, registeredDevices[service][id]);
} else {
@@ -176,7 +176,7 @@ function getByName(name, service, subservice, callback) {
getByNameAndType(name, null, service, subservice, callback);
}
-function update(device, callback) {
+function update(previousDevice, device, callback) {
registeredDevices[device.service][device.id] = deepClone(device);
callback(null, device);
}
@@ -210,8 +210,44 @@ function getDevicesByAttribute(name, value, service, subservice, callback) {
}
}
+function storeDeviceField(fieldName, fieldValue, typeInformation, callback) {
+ if (
+ typeInformation &&
+ typeInformation.id &&
+ typeInformation.apikey &&
+ typeInformation.service &&
+ typeInformation.subservice
+ ) {
+ getDevice(
+ typeInformation.id,
+ typeInformation.apikey,
+ typeInformation.service,
+ typeInformation.subservice,
+ function (error, data) {
+ if (!error) {
+ if (data) {
+ if (fieldName === 'lastMeasure') {
+ data.lastMeasure = { timestamp: new Date().toISOString(), measure: fieldValue };
+ } else {
+ data[fieldName] = fieldValue;
+ }
+ }
+ if (callback) {
+ callback(null, data);
+ }
+ } else {
+ callback(error, null);
+ }
+ }
+ );
+ } else {
+ callback(null, null);
+ }
+}
+
exports.getDevicesByAttribute = getDevicesByAttribute;
exports.store = storeDevice;
+exports.storeDeviceField = storeDeviceField;
exports.update = update;
exports.remove = removeDevice;
exports.list = listDevices;
diff --git a/lib/services/devices/deviceRegistryMongoDB.js b/lib/services/devices/deviceRegistryMongoDB.js
index 4aa2d5d21..86456a1de 100644
--- a/lib/services/devices/deviceRegistryMongoDB.js
+++ b/lib/services/devices/deviceRegistryMongoDB.js
@@ -22,7 +22,7 @@
*/
const logger = require('logops');
-const dbService = require('../../model/dbConn');
+const mongoose = require('mongoose');
const config = require('../../commonConfig');
const fillService = require('./../common/domain').fillService;
const alarmsInt = require('../common/alarmManagement').intercept;
@@ -55,28 +55,15 @@ const attributeList = [
'polling',
'timestamp',
'explicitAttrs',
- 'expressionLanguage',
- 'ngsiVersion'
+ 'ngsiVersion',
+ 'subscriptions',
+ 'payloadType',
+ 'useCBflowControl',
+ 'storeLastMeasure',
+ 'cmdMode',
+ 'subscriptionId'
];
-/**
- * Generates a handler for the save device operations. The handler will take the customary error and the saved device
- * as the parameters (and pass the serialized DAO as the callback value).
- *
- * @return {Function} The generated handler.
- */
-function saveDeviceHandler(callback) {
- return function saveHandler(error, deviceDAO) {
- if (error) {
- logger.debug(fillService(context, deviceDAO), 'Error storing device information: %s', error);
-
- callback(new errors.InternalDbError(error));
- } else {
- callback(null, deviceDAO.toObject());
- }
- };
-}
-
/**
* Create a new register for a device. The device object should contain the id, type and registrationId
*
@@ -96,21 +83,22 @@ function storeDevice(newDevice, callback) {
logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type);
- deviceObj.save(function saveHandler(error, deviceDAO) {
- if (error) {
+ deviceObj
+ .save({})
+ .then((deviceDAO) => {
+ callback(null, deviceDAO.toObject());
+ })
+ .catch((error) => {
if (error.code === 11000) {
logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error);
- callback(new errors.DuplicateDeviceId(newDevice.id));
+ callback(new errors.DuplicateDeviceId(newDevice), newDevice);
} else {
logger.debug(context, 'Error storing device information: %s', error);
callback(new errors.InternalDbError(error));
}
- } else {
- callback(null, deviceDAO.toObject());
- }
- });
+ });
}
/**
@@ -120,26 +108,28 @@ function storeDevice(newDevice, callback) {
* @param {String} service Service of the device to remove.
* @param {String} subservice Subservice inside the service for the removed device.
*/
-function removeDevice(id, service, subservice, callback) {
+function removeDevice(id, apikey, service, subservice, callback) {
const condition = {
id,
service,
subservice
};
-
+ if (apikey) {
+ condition.apikey = apikey;
+ }
logger.debug(context, 'Removing device with id [%s]', id);
- Device.model.deleteOne(condition, function (error) {
- if (error) {
- logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
-
- callback(new errors.InternalDbError(error));
- } else {
+ const query = Device.model.deleteOne(condition);
+ query
+ .exec({})
+ .then(() => {
logger.debug(context, 'Device [%s] successfully removed.', id);
-
callback(null);
- }
- });
+ })
+ .catch((error) => {
+ logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
+ callback(new errors.InternalDbError(error));
+ });
}
/**
@@ -167,7 +157,7 @@ function listDevices(type, service, subservice, limit, offset, callback) {
}
const query = Device.model.find(condition).sort();
-
+ const queryCount = Device.model.countDocuments(condition);
if (limit) {
query.limit(parseInt(limit, 10));
}
@@ -176,10 +166,27 @@ function listDevices(type, service, subservice, limit, offset, callback) {
query.skip(parseInt(offset, 10));
}
- async.series([query.exec.bind(query), Device.model.countDocuments.bind(Device.model, condition)], function (
- error,
- results
- ) {
+ function funcQuery(cb) {
+ query
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ function funcQueryCount(cb) {
+ queryCount
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ async.series([funcQuery, funcQueryCount], function (error, results) {
callback(error, {
count: results[1],
devices: results[0]
@@ -190,39 +197,45 @@ function listDevices(type, service, subservice, limit, offset, callback) {
function findOneInMongoDB(queryParams, id, callback) {
const query = Device.model.findOne(queryParams);
query.select({ __v: 0 });
-
- query.lean().exec(function handleGet(error, data) {
- if (error) {
+ query.lean();
+
+ query
+ .exec({})
+ .then((data) => {
+ if (data) {
+ context = fillService(context, data);
+ logger.debug(context, 'Device data found: %j', data);
+ callback(null, data);
+ } else {
+ logger.debug(context, 'Device [%s] not found.', id);
+ callback(new errors.DeviceNotFound(id, queryParams));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (data) {
- context = fillService(context, data);
- logger.debug(context, 'Device data found: %j', data);
- callback(null, data);
- } else {
- logger.debug(context, 'Device [%s] not found.', id);
-
- callback(new errors.DeviceNotFound(id));
- }
- });
+ });
}
/**
* Internal function used to find a device in the DB.
*
* @param {String} id ID of the Device to find.
+ * @param {String} Apikey Apikey of the Device to find.
* @param {String} service Service the device belongs to (optional).
* @param {String} subservice Division inside the service (optional).
*/
-function getDeviceById(id, service, subservice, callback) {
+function getDeviceById(id, apikey, service, subservice, callback) {
const queryParams = {
id,
service,
subservice
};
+ if (apikey) {
+ queryParams.apikey = apikey;
+ }
context = fillService(context, queryParams);
- logger.debug(context, 'Looking for device with id [%s].', id);
+ logger.debug(context, 'Looking for device with id [%s] and queryParams [%j].', id, queryParams);
findOneInMongoDB(queryParams, id, callback);
}
@@ -233,8 +246,8 @@ function getDeviceById(id, service, subservice, callback) {
* @param {String} service Service the device belongs to.
* @param {String} subservice Division inside the service.
*/
-function getDevice(id, service, subservice, callback) {
- getDeviceById(id, service, subservice, function (error, data) {
+function getDevice(id, apikey, service, subservice, callback) {
+ getDeviceById(id, apikey, service, subservice, function (error, data) {
if (error) {
callback(error);
} else {
@@ -245,9 +258,9 @@ function getDevice(id, service, subservice, callback) {
function getByNameAndType(name, type, service, servicepath, callback) {
context = fillService(context, { service, subservice: servicepath });
- let optionsQuery = {
- name: name,
- service: service,
+ const optionsQuery = {
+ name,
+ service,
subservice: servicepath
};
if (type) {
@@ -257,20 +270,23 @@ function getByNameAndType(name, type, service, servicepath, callback) {
const query = Device.model.findOne(optionsQuery);
query.select({ __v: 0 });
-
- query.lean().exec(function handleGet(error, data) {
- if (error) {
+ query.lean();
+ query
+ .exec({})
+ .then((data) => {
+ if (data) {
+ context = fillService(context, data);
+ logger.debug(context, 'Device data found: %j', data);
+ callback(null, data);
+ } else {
+ logger.debug(context, 'Device [%s] not found.', name);
+ callback(new errors.DeviceNotFound(name, optionsQuery));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (data) {
- callback(null, data);
- } else {
- logger.debug(context, 'Device [%s] not found.', name);
-
- callback(new errors.DeviceNotFound(name));
- }
- });
+ });
}
function getByName(name, service, servicepath, callback) {
@@ -283,9 +299,9 @@ function getByName(name, service, servicepath, callback) {
*
* @param {Object} device Device object with the new values to write.
*/
-function update(device, callback) {
+function update(previousDevice, device, callback) {
logger.debug(context, 'Storing updated values for device [%s]:\n%s', device.id, JSON.stringify(device, null, 4));
- getDeviceById(device.id, device.service, device.subservice, function (error, data) {
+ getDevice(device.id, previousDevice.apikey, device.service, device.subservice, function (error, data) {
if (error) {
callback(error);
} else {
@@ -296,6 +312,7 @@ function update(device, callback) {
data.internalAttributes = device.internalAttributes;
data.commands = device.commands;
data.endpoint = device.endpoint;
+ data.transport = device.transport;
data.polling = device.polling;
data.name = device.name;
data.type = device.type;
@@ -303,11 +320,27 @@ function update(device, callback) {
data.registrationId = device.registrationId;
data.explicitAttrs = device.explicitAttrs;
data.ngsiVersion = device.ngsiVersion;
+ data.timestamp = device.timestamp;
+ data.subscriptions = device.subscriptions;
+ data.payloadType = device.payloadType;
+ data.useCBflowControl = device.useCBflowControl;
+ data.storeLastMeasure = device.storeLastMeasure;
+ data.lastMeasure = device.lastMeasure;
+ data.cmdMode = device.cmdMode;
+ data.subscriptionId = device.subscriptionId;
/* eslint-disable-next-line new-cap */
const deviceObj = new Device.model(data);
deviceObj.isNew = false;
- deviceObj.save(saveDeviceHandler(callback));
+ deviceObj
+ .save({})
+ .then((deviceDAO) => {
+ callback(null, deviceDAO.toObject());
+ })
+ .catch((error) => {
+ logger.debug(fillService(context, device), 'Error storing device information: %s', error);
+ callback(new errors.InternalDbError(error));
+ });
}
});
}
@@ -316,7 +349,14 @@ function update(device, callback) {
* Cleans all the information in the database, leaving it in a clean state.
*/
function clear(callback) {
- dbService.db.db.dropDatabase(callback);
+ mongoose.connection
+ .dropDatabase()
+ .then(() => {
+ callback(null);
+ })
+ .catch((error) => {
+ callback(error);
+ });
}
function itemToObject(i) {
@@ -344,24 +384,72 @@ function getDevicesByAttribute(name, value, service, subservice, callback) {
const query = Device.model.find(filter);
query.select({ __v: 0 });
-
- query.exec(function handleGet(error, devices) {
- if (error) {
+ query.lean();
+ query
+ .exec({})
+ .then((devices) => {
+ if (devices) {
+ callback(null, devices.map(itemToObject));
+ } else {
+ logger.debug(context, 'Device [%s] not found.', name);
+ callback(new errors.DeviceNotFound(name, filter));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (devices) {
- callback(null, devices.map(itemToObject));
- } else {
- logger.debug(context, 'Device [%s] not found.', name);
+ });
+}
- callback(new errors.DeviceNotFound(name));
- }
- });
+function storeDeviceField(fieldName, fieldValue, typeInformation, callback) {
+ if (
+ typeInformation &&
+ typeInformation.id &&
+ typeInformation.apikey &&
+ typeInformation.service &&
+ typeInformation.subservice
+ ) {
+ getDevice(
+ typeInformation.id,
+ typeInformation.apikey,
+ typeInformation.service,
+ typeInformation.subservice,
+ function (error, data) {
+ if (error) {
+ callback(error);
+ } else {
+ if (fieldName === 'lastMeasure') {
+ data.lastMeasure = { timestamp: new Date().toISOString(), measure: fieldValue };
+ } else {
+ data[fieldName] = fieldValue;
+ }
+ /* eslint-disable-next-line new-cap */
+ const deviceObj = new Device.model(data);
+ deviceObj.isNew = false;
+ deviceObj
+ .save({})
+ .then((deviceDao) => {
+ callback(null, deviceDao.toObject());
+ })
+ .catch((error) => {
+ logger.debug(
+ fillService(context, deviceObj),
+ 'Error storing device information: %s',
+ error
+ );
+ callback(new errors.InternalDbError(error));
+ });
+ }
+ }
+ );
+ } else {
+ callback(null, null);
+ }
}
exports.getDevicesByAttribute = alarmsInt(constants.MONGO_ALARM, getDevicesByAttribute);
exports.store = alarmsInt(constants.MONGO_ALARM, storeDevice);
+exports.storeDeviceField = alarmsInt(constants.MONGO_ALARM, storeDeviceField);
exports.update = alarmsInt(constants.MONGO_ALARM, update);
exports.remove = alarmsInt(constants.MONGO_ALARM, removeDevice);
exports.list = alarmsInt(constants.MONGO_ALARM, listDevices);
diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js
index 7236d3a82..415ed6ef6 100644
--- a/lib/services/devices/deviceService.js
+++ b/lib/services/devices/deviceService.js
@@ -40,6 +40,8 @@ const registrationUtils = require('./registrationUtils');
const subscriptions = require('../ngsi/subscriptionService');
const expressionPlugin = require('./../../plugins/expressionPlugin');
const pluginUtils = require('./../../plugins/pluginUtils');
+const alarms = require('../common/alarmManagement');
+const constants = require('../../constants');
const _ = require('underscore');
const context = {
op: 'IoTAgentNGSI.DeviceService'
@@ -178,20 +180,33 @@ function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuratio
if (configuration && configuration.ngsiVersion) {
deviceData.ngsiVersion = configuration.ngsiVersion;
}
- if (configuration && configuration.expressionLanguage && deviceData.expressionLanguage === undefined) {
- deviceData.expressionLanguage = configuration.expressionLanguage;
- }
if (configuration && configuration.explicitAttrs !== undefined && deviceData.explicitAttrs === undefined) {
deviceData.explicitAttrs = configuration.explicitAttrs;
}
-
+ if (configuration && configuration.entityNameExp !== undefined) {
+ deviceData.entityNameExp = configuration.entityNameExp;
+ }
+ if (configuration && configuration.timestamp !== undefined && deviceData.timestamp === undefined) {
+ deviceData.timestamp = configuration.timestamp;
+ }
+ if (configuration && configuration.payloadType !== undefined && deviceData.payloadType === undefined) {
+ deviceData.payloadType = configuration.payloadType;
+ }
+ if (configuration && configuration.useCBflowControl !== undefined && deviceData.useCBflowControl === undefined) {
+ deviceData.useCBflowControl = configuration.useCBflowControl;
+ }
+ if (configuration && configuration.storeLastMeasure !== undefined && deviceData.storeLastMeasure === undefined) {
+ deviceData.storeLastMeasure = configuration.storeLastMeasure;
+ }
+ if (configuration && configuration.cmdMode !== undefined && deviceData.cmdMode === undefined) {
+ deviceData.cmdMode = configuration.cmdMode;
+ }
logger.debug(context, 'deviceData after merge with conf: %j', deviceData);
callback(null, deviceData);
}
/**
- * Find the configuration group belonging to a given device, with a different criteria depending on whether the
- * agent is in single configuration mode or node.
+ * Find the configuration group belonging to a given device
*
* @param {Object} deviceObj Device data.
*/
@@ -218,19 +233,15 @@ function findConfigurationGroup(deviceObj, callback) {
callback(null, effectiveGroup);
}
- if (config.getConfig().singleConfigurationMode === true) {
- config.getGroupRegistry().find(deviceObj.service, deviceObj.subservice, handlerGroupFind);
- } else {
- config
- .getGroupRegistry()
- .findTypeSilently(
- deviceObj.service,
- deviceObj.subservice,
- deviceObj.type,
- deviceObj.apikey,
- handlerGroupFindByType
- );
- }
+ config
+ .getGroupRegistry()
+ .findTypeSilently(
+ deviceObj.service,
+ deviceObj.subservice,
+ deviceObj.type,
+ deviceObj.apikey,
+ handlerGroupFindByType
+ );
}
/**
@@ -249,12 +260,13 @@ function registerDevice(deviceObj, callback) {
function checkDuplicates(deviceObj, innerCb) {
config.getRegistry().getSilently(
deviceObj.id,
+ deviceObj.apikey,
deviceObj.service,
deviceObj.subservice,
/* eslint-disable-next-line no-unused-vars */
function (error, device) {
if (!error) {
- innerCb(new errors.DuplicateDeviceId(deviceObj.id));
+ innerCb(new errors.DuplicateDeviceId(deviceObj), device);
} else {
innerCb();
}
@@ -265,7 +277,7 @@ function registerDevice(deviceObj, callback) {
function prepareDeviceData(deviceObj, configuration, callback) {
const deviceData = _.clone(deviceObj);
let selectedConfiguration;
-
+ logger.debug(context, 'Prepare device data:\n%s', JSON.stringify(deviceData, null, 4));
if (!deviceData.type) {
if (configuration && configuration.type) {
deviceData.type = configuration.type;
@@ -287,7 +299,20 @@ function registerDevice(deviceObj, callback) {
deviceData.ngsiVersion = configuration.ngsiVersion;
}
}
-
+ // Set polling and transport for autoprovisioned devices
+ if (!deviceData.transport && config.getConfig().defaultTransport) {
+ deviceData.transport =
+ configuration && configuration.transport
+ ? configuration.transport
+ : config.getConfig().defaultTransport;
+ }
+ if (deviceData.transport === 'HTTP') {
+ if (deviceData.endpoint) {
+ deviceData.polling = false;
+ } else {
+ deviceData.polling = !(configuration && configuration.endpoint);
+ }
+ }
if (!deviceData.name) {
let entityName = null;
if (configuration && configuration.entityNameExp !== undefined && configuration.entityNameExp !== '') {
@@ -295,7 +320,7 @@ function registerDevice(deviceObj, callback) {
let attrList = pluginUtils.getIdTypeServSubServiceFromDevice(deviceData);
attrList = deviceData.staticAttributes ? attrList.concat(deviceData.staticAttributes) : attrList;
attrList = configuration.staticAttributes ? attrList.concat(configuration.staticAttributes) : attrList;
- let ctxt = expressionPlugin.extractContext(attrList);
+ const ctxt = expressionPlugin.extractContext(attrList);
try {
entityName = expressionPlugin.applyExpression(configuration.entityNameExp, ctxt, deviceData);
} catch (e) {
@@ -358,22 +383,24 @@ function registerDevice(deviceObj, callback) {
callback(error);
} else {
deviceObj.registrationId = results.registrationId;
+ deviceObj.subscriptionId = results.subscriptionId;
deviceObj.name = deviceData.name;
deviceObj.service = deviceData.service;
deviceObj.subservice = deviceData.subservice;
deviceObj.type = deviceData.type;
- deviceObj.staticAttributes = deviceData.staticAttributes;
- deviceObj.commands = deviceData.commands;
- deviceObj.lazy = deviceData.lazy;
- if ('timestamp' in deviceData && deviceData.timestamp !== undefined) {
- deviceObj.timestamp = deviceData.timestamp;
- }
- if ('explicitAttrs' in deviceData && deviceData.explicitAttrs !== undefined) {
- deviceObj.explicitAttrs = deviceData.explicitAttrs;
- }
+ deviceObj.staticAttributes = deviceObj.staticAttributes ? deviceObj.staticAttributes : [];
+ deviceObj.commands = deviceData.commands ? deviceData.commands : [];
+ deviceObj.lazy = deviceObj.lazy ? deviceObj.lazy : [];
if ('apikey' in deviceData && deviceData.apikey !== undefined) {
deviceObj.apikey = deviceData.apikey;
}
+ if ('transport' in deviceData && deviceData.transport !== undefined) {
+ deviceObj.transport = deviceData.transport;
+ }
+ if ('polling' in deviceData && deviceData.polling !== undefined) {
+ deviceObj.polling = deviceData.polling;
+ }
+ logger.debug(context, 'Storing device :\n%s', JSON.stringify(deviceObj, null, 4));
config.getRegistry().store(deviceObj, callback);
}
}
@@ -414,7 +441,7 @@ function removeAllSubscriptions(device, callback) {
* @param {String} service Service of the device to unregister.
* @param {String} subservice Subservice inside the service for the unregisterd device.
*/
-function unregisterDevice(id, service, subservice, callback) {
+function unregisterDevice(id, apikey, service, subservice, callback) {
function processContextUnregister(body, innerCallback) {
innerCallback(null);
}
@@ -423,9 +450,9 @@ function unregisterDevice(id, service, subservice, callback) {
innerCallback(null);
}
- logger.debug(context, 'Removing device register in Device Service');
+ logger.debug(context, 'Removing device %j %j %j %j register in Device Service', id, apikey, service, subservice);
- config.getRegistry().get(id, service, subservice, function (error, device) {
+ config.getRegistry().get(id, apikey, service, subservice, function (error, device) {
if (error) {
callback(error);
} else {
@@ -449,7 +476,7 @@ function unregisterDevice(id, service, subservice, callback) {
processUnsubscribes,
apply(registrationUtils.sendRegistrations, true, mergedDevice),
processContextUnregister,
- apply(config.getRegistry().remove, id, service, subservice)
+ apply(config.getRegistry().remove, id, apikey, service, subservice)
],
callback
);
@@ -460,8 +487,8 @@ function unregisterDevice(id, service, subservice, callback) {
});
}
-function updateRegisterDevice(deviceObj, entityInfoUpdated, callback) {
- deviceHandler.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
+function updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback) {
+ deviceHandler.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
}
/**
@@ -524,8 +551,18 @@ function listDevices(service, subservice, limit, offset, callback) {
* @param {String} service Service for which the requested device.
* @param {String} subservice Subservice inside the service for which the device is requested.
*/
-function getDevice(deviceId, service, subservice, callback) {
- config.getRegistry().get(deviceId, service, subservice, callback);
+function getDevice(deviceId, apikey, service, subservice, callback) {
+ config.getRegistry().get(deviceId, apikey, service, subservice, callback);
+}
+
+/**
+ * Update a device from the device registry.
+ *
+ * @param {String} device JSON object contain the device to update.
+ */
+function updateDevice(device, callback) {
+ logger.debug(context, 'updateDevice %j', device);
+ config.getRegistry().update(device, device, callback);
}
/**
@@ -535,8 +572,8 @@ function getDevice(deviceId, service, subservice, callback) {
* @param {String} service Service for which the requested device.
* @param {String} subservice Subservice inside the service for which the device is requested.
*/
-function getDeviceSilently(deviceId, service, subservice, callback) {
- config.getRegistry().getSilently(deviceId, service, subservice, callback);
+function getDeviceSilently(deviceId, apikey, service, subservice, callback) {
+ config.getRegistry().getSilently(deviceId, apikey, service, subservice, callback);
}
/**
@@ -603,13 +640,26 @@ function checkRegistry(fn) {
};
}
-function findOrCreate(deviceId, group, callback) {
- getDeviceSilently(deviceId, group.service, group.subservice, function (error, device) {
+function findOrCreate(deviceId, apikey, group, callback) {
+ getDeviceSilently(deviceId, apikey, group.service, group.subservice, function (error, device) {
if (!error && device) {
- callback(null, device, group);
+ if (
+ (!('apikey' in device) || device.apikey === undefined) &&
+ 'apikey' in group &&
+ group.apikey !== undefined
+ ) {
+ logger.info(context, 'Update provisioned device %j with measure/group apikey %j', device, group.apikey);
+ device.apikey = group.apikey; // group apikey is the same of current measure apikey
+ updateDevice(device, function (error) {
+ callback(error, device, group);
+ });
+ } else {
+ callback(null, device, group);
+ }
} else if (error.name === 'DEVICE_NOT_FOUND') {
const newDevice = {
id: deviceId,
+ apikey,
service: group.service,
subservice: group.subservice,
type: group.type
@@ -618,19 +668,9 @@ function findOrCreate(deviceId, group, callback) {
if (config.getConfig().iotManager && config.getConfig().iotManager.protocol) {
newDevice.protocol = config.getConfig().iotManager.protocol;
}
-
- if ('timestamp' in group && group.timestamp !== undefined) {
- newDevice.timestamp = group.timestamp;
- }
if ('ngsiVersion' in group && group.ngsiVersion !== undefined) {
newDevice.ngsiVersion = group.ngsiVersion;
}
- if ('explicitAttrs' in group && group.explicitAttrs !== undefined) {
- newDevice.explicitAttrs = group.explicitAttrs;
- }
- if ('expressionLanguage' in group && group.expressionLanguage !== undefined) {
- newDevice.expressionLanguage = group.expressionLanguage;
- }
if (
(!('apikey' in newDevice) || newDevice.apikey === undefined) &&
'apikey' in group &&
@@ -642,7 +682,14 @@ function findOrCreate(deviceId, group, callback) {
if (group.autoprovision === undefined || group.autoprovision === true) {
logger.debug(context, 'Registering autoprovision of Device %j for its conf %j', newDevice, group);
registerDevice(newDevice, function (error, device) {
- callback(error, device, group);
+ if (error && error.name === 'DUPLICATE_DEVICE_ID') {
+ alarms.release(constants.MONGO_ALARM, error);
+ logger.warn(context, 'Error %j already registered autoprovisioned device: %j ', error, device);
+ callback(null, device, group);
+ } else {
+ logger.debug(context, 'registered autoprovisioned device: %j ', device);
+ callback(error, device, group);
+ }
});
} else {
logger.info(
@@ -651,7 +698,7 @@ function findOrCreate(deviceId, group, callback) {
newDevice,
group
);
- callback(new errors.DeviceNotFound(deviceId));
+ callback(new errors.DeviceNotFound(deviceId, newDevice));
}
} else {
callback(error);
@@ -676,14 +723,14 @@ function retrieveDevice(deviceId, apiKey, callback) {
} else {
logger.error(context, "Couldn't find device data for APIKey [%s] and DeviceId[%s]", deviceId, apiKey);
- callback(new errors.DeviceNotFound(deviceId));
+ callback(new errors.DeviceNotFound(deviceId, { apikey: apiKey }));
}
});
} else {
async.waterfall(
[
- apply(groupService.get, config.getConfig().defaultResource || '', apiKey),
- apply(findOrCreate, deviceId),
+ apply(groupService.getSilently, config.getConfig().defaultResource || '', apiKey),
+ apply(findOrCreate, deviceId, apiKey),
apply(
mergeDeviceWithConfiguration,
['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'],
@@ -695,9 +742,14 @@ function retrieveDevice(deviceId, apiKey, callback) {
}
}
+function storeDeviceField(fieldName, fieldValue, typeInformation, callback) {
+ config.getRegistry().storeDeviceField(fieldName, fieldValue, typeInformation, callback);
+}
+
exports.listDevices = intoTrans(context, checkRegistry)(listDevices);
exports.listDevicesWithType = intoTrans(context, checkRegistry)(listDevicesWithType);
exports.getDevice = intoTrans(context, checkRegistry)(getDevice);
+exports.updateDevice = intoTrans(context, checkRegistry)(updateDevice);
exports.getDeviceSilently = intoTrans(context, checkRegistry)(getDeviceSilently);
exports.getDevicesByAttribute = intoTrans(context, checkRegistry)(getDevicesByAttribute);
exports.getDeviceByName = intoTrans(context, checkRegistry)(getDeviceByName);
@@ -708,5 +760,7 @@ exports.unregister = intoTrans(context, unregisterDevice);
exports.clearRegistry = intoTrans(context, checkRegistry)(clearRegistry);
exports.retrieveDevice = intoTrans(context, checkRegistry)(retrieveDevice);
exports.mergeDeviceWithConfiguration = mergeDeviceWithConfiguration;
+exports.findOrCreate = findOrCreate;
exports.findConfigurationGroup = findConfigurationGroup;
+exports.storeDeviceField = storeDeviceField;
exports.init = init;
diff --git a/lib/services/devices/devices-NGSI-LD.js b/lib/services/devices/devices-NGSI-LD.js
index dd962f188..0a94d3db1 100644
--- a/lib/services/devices/devices-NGSI-LD.js
+++ b/lib/services/devices/devices-NGSI-LD.js
@@ -33,7 +33,6 @@ const logger = require('logops');
const config = require('../../commonConfig');
const ngsiLD = require('../ngsi/entities-NGSI-LD');
const utils = require('../northBound/restUtils');
-const moment = require('moment');
const _ = require('underscore');
const registrationUtils = require('./registrationUtils');
const NGSIv2 = require('./devices-NGSI-v2');
@@ -56,53 +55,6 @@ function jsonConcat(json1, json2) {
}
}
-/**
- * Creates the response handler for the initial entity creation request using NGSI-LD.
- * This handler basically deals with the errors that could have been rised during
- * the communication with the Context Broker.
- *
- * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
- * @param {Object} newDevice Device object that will be stored in the database.
- * @return {function} Handler to pass to the request() function.
- */
-function createInitialEntityHandlerNgsiLD(deviceData, newDevice, callback) {
- return function handleInitialEntityResponse(error, response, body) {
- if (error) {
- logger.error(
- context,
- 'ORION-001: Connection error creating inital entity in the Context Broker: %s',
- error
- );
-
- alarms.raise(constants.ORION_ALARM, error);
-
- callback(error);
- }
- // Handling different response codes for batch entity upsert in NGSI-LD specification:
- // - In v1.2.1, response code is 200
- // - In v1.3.1, response code is 201 if some created entities, 204 if just updated existing
- else if (
- response &&
- (response.statusCode === 200 || response.statusCode === 201 || response.statusCode === 204)
- ) {
- alarms.release(constants.ORION_ALARM);
- logger.debug(context, 'Initial entity created successfully.');
- callback(null, newDevice);
- } else {
- logger.error(
- context,
- 'Protocol error connecting to the Context Broker [%d]: %s',
- response.statusCode,
- body
- );
-
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body);
-
- callback(errorObj);
- }
- };
-}
-
/**
* Creates the response handler for the update entity request using NGSI-LD.
* This handler basically deals with the errors
@@ -140,7 +92,7 @@ function updateEntityHandlerNgsiLD(deviceData, updatedDevice, callback) {
body
);
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body);
+ const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, deviceData, body);
callback(errorObj);
}
@@ -155,53 +107,7 @@ function updateEntityHandlerNgsiLD(deviceData, updatedDevice, callback) {
* @param {Object} newDevice Device object that will be stored in the database.
*/
function createInitialEntityNgsiLD(deviceData, newDevice, callback) {
- let json = {
- id: String(deviceData.name),
- type: deviceData.type
- };
-
- jsonConcat(json, NGSIv2.formatAttributes(deviceData.active, false));
- jsonConcat(json, NGSIv2.formatAttributes(deviceData.staticAttributes, true));
- jsonConcat(json, NGSIv2.formatCommands(deviceData.commands));
-
- if (
- ('timestamp' in deviceData && deviceData.timestamp !== undefined
- ? deviceData.timestamp
- : config.getConfig().timestamp) &&
- !utils.isTimestampedNgsi2(json)
- ) {
- logger.debug(context, 'config.timestamp %s %s', deviceData.timestamp, config.getConfig().timestamp);
-
- json[constants.TIMESTAMP_ATTRIBUTE] = {
- type: constants.TIMESTAMP_TYPE_NGSI2,
- value: moment()
- };
- }
-
- json = ngsiLD.formatAsNGSILD(json);
-
- const options = {
- url: config.getConfig().contextBroker.url + '/ngsi-ld/v1/entityOperations/upsert/',
- method: 'POST',
- json: [json],
- headers: {
- 'fiware-service': deviceData.service,
- 'fiware-servicepath': deviceData.subservice,
- 'NGSILD-Tenant': deviceData.service,
- 'NGSILD-Path': deviceData.subservice,
- 'Content-Type': 'application/ld+json'
- }
- };
-
- if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
- options.url = deviceData.cbHost + '/ngsi-ld/v1/entityOperations/upsert/';
- } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
- options.url = 'http://' + deviceData.cbHost + '/ngsi-ld/v1/entityOperations/upsert/';
- }
-
- logger.debug(context, 'deviceData: %j', deviceData);
- logger.debug(context, 'Creating initial entity in the Context Broker:\n %s', JSON.stringify(options, null, 4));
- utils.executeWithSecurity(options, newDevice, createInitialEntityHandlerNgsiLD(deviceData, newDevice, callback));
+ callback(null, newDevice);
}
/**
@@ -246,7 +152,7 @@ function updateEntityNgsiLD(deviceData, updatedDevice, callback) {
) {
options.json[constants.TIMESTAMP_ATTRIBUTE] = {
type: constants.TIMESTAMP_TYPE_NGSI2,
- value: moment()
+ value: new Date().toISOString()
};
}
@@ -275,15 +181,16 @@ function updateEntityNgsiLD(deviceData, updatedDevice, callback) {
*
* @param {Object} deviceObj Object with all the device information (mandatory).
*/
-function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
+function updateRegisterDeviceNgsiLD(deviceObj, previousDevice, entityInfoUpdated, callback) {
if (!deviceObj.id || !deviceObj.type) {
- callback(new errors.MissingAttributes('Id or device missing'));
+ callback(new errors.MissingAttributes('Id or device missing', deviceObj));
return;
}
- logger.debug(context, 'Update provisioned LD device in Device Service');
+ logger.debug(context, 'Update provisioned LD device in Device Service %j %j', deviceObj, entityInfoUpdated);
function combineWithNewDevice(newDevice, oldDevice, callback) {
+ logger.debug(context, 'combineWithNewDevice %j %j', newDevice, oldDevice);
if (oldDevice) {
oldDevice.internalId = newDevice.internalId;
oldDevice.lazy = newDevice.lazy;
@@ -308,7 +215,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
callback(null, oldDevice);
} else {
- callback(new errors.DeviceNotFound(newDevice.id));
+ callback(new errors.DeviceNotFound(newDevice.id, newDevice));
}
}
@@ -369,26 +276,38 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
if (entityInfoUpdated) {
async.waterfall(
[
- apply(config.getRegistry().get, deviceObj.id, deviceObj.service, deviceObj.subservice),
+ apply(
+ config.getRegistry().get,
+ deviceObj.id,
+ previousDevice.apikey,
+ deviceObj.service,
+ deviceObj.subservice
+ ),
apply(extractDeviceDifference, deviceObj),
createInitialEntityNgsiLD,
apply(combineWithNewDevice, deviceObj),
apply(registrationUtils.sendRegistrations, false),
apply(registrationUtils.processContextRegistration, deviceObj),
- config.getRegistry().update
+ apply(config.getRegistry().update, previousDevice)
],
callback
);
} else {
async.waterfall(
[
- apply(config.getRegistry().get, deviceObj.id, deviceObj.service, deviceObj.subservice),
+ apply(
+ config.getRegistry().get,
+ deviceObj.id,
+ previousDevice.apikey,
+ deviceObj.service,
+ deviceObj.subservice
+ ),
apply(extractDeviceDifference, deviceObj),
updateEntityNgsiLD,
apply(combineWithNewDevice, deviceObj),
apply(registrationUtils.sendRegistrations, false),
apply(registrationUtils.processContextRegistration, deviceObj),
- config.getRegistry().update
+ apply(config.getRegistry().update, previousDevice)
],
callback
);
diff --git a/lib/services/devices/devices-NGSI-mixed.js b/lib/services/devices/devices-NGSI-mixed.js
index 24c1771a9..c0e47d2a5 100644
--- a/lib/services/devices/devices-NGSI-mixed.js
+++ b/lib/services/devices/devices-NGSI-mixed.js
@@ -53,11 +53,11 @@ function createInitialEntityNgsiMixed(deviceData, newDevice, callback) {
*
* @param {Object} deviceObj Object with all the device information (mandatory).
*/
-function updateRegisterDeviceNgsiMixed(deviceObj, entityInfoUpdated, callback) {
+function updateRegisterDeviceNgsiMixed(deviceObj, previousDevice, entityInfoUpdated, callback) {
if (config.checkNgsiLD(deviceObj)) {
- deviceHandlerLD.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
+ deviceHandlerLD.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
} else {
- deviceHandlerV2.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
+ deviceHandlerV2.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
}
}
diff --git a/lib/services/devices/devices-NGSI-v2.js b/lib/services/devices/devices-NGSI-v2.js
index e7c862973..6f4f3aae7 100644
--- a/lib/services/devices/devices-NGSI-v2.js
+++ b/lib/services/devices/devices-NGSI-v2.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
+ * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
*
* This file is part of fiware-iotagent-lib
*
@@ -41,7 +41,6 @@ const registrationUtils = require('./registrationUtils');
const _ = require('underscore');
const utils = require('../northBound/restUtils');
const NGSIv2 = require('../ngsi/entities-NGSI-v2');
-const moment = require('moment');
const context = {
op: 'IoTAgentNGSI.Devices-v2'
};
@@ -61,46 +60,6 @@ function jsonConcat(json1, json2) {
}
}
-/**
- * Creates the response handler for the initial entity creation request using NGSIv2.
- * This handler basically deals with the errors that could have been rised during
- * the communication with the Context Broker.
- *
- * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
- * @param {Object} newDevice Device object that will be stored in the database.
- * @return {function} Handler to pass to the request() function.
- */
-function createInitialEntityHandlerNgsi2(deviceData, newDevice, callback) {
- return function handleInitialEntityResponse(error, response, body) {
- if (error) {
- logger.error(
- context,
- 'ORION-001: Connection error creating inital entity in the Context Broker: %s',
- error
- );
-
- alarms.raise(constants.ORION_ALARM, error);
-
- callback(error);
- } else if (response && response.statusCode === 204) {
- alarms.release(constants.ORION_ALARM);
- logger.debug(context, 'Initial entity created successfully.');
- callback(null, newDevice);
- } else {
- logger.error(
- context,
- 'Protocol error connecting to the Context Broker [%d]: %s',
- response.statusCode,
- body
- );
-
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body, response.statusCode);
-
- callback(errorObj);
- }
- };
-}
-
/**
* Creates the response handler for the update entity request using NGSIv2. This handler basically deals with the errors
* that could have been rised during the communication with the Context Broker.
@@ -129,7 +88,13 @@ function updateEntityHandlerNgsi2(deviceData, updatedDevice, callback) {
body
);
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body, response.statusCode);
+ const errorObj = new errors.EntityGenericError(
+ deviceData.id,
+ deviceData.type,
+ deviceData,
+ body,
+ response.statusCode
+ );
callback(errorObj);
}
@@ -199,62 +164,102 @@ function formatCommandsNgsi2(originalVector) {
return attributeList;
}
-/**
- * Creates the initial entity representing the device in the Context Broker using NGSIv2.
- * This is important mainly to allow the rest of the updateContext operations to be performed.
- *
- * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
- * @param {Object} newDevice Device object that will be stored in the database.
- */
-function createInitialEntityNgsi2(deviceData, newDevice, callback) {
- const options = {
- url: config.getConfig().contextBroker.url + '/v2/entities?options=upsert',
- method: 'POST',
- json: {
- id: String(deviceData.name),
- type: deviceData.type
- },
- headers: {
- 'fiware-service': deviceData.service,
- 'fiware-servicepath': deviceData.subservice,
- 'fiware-correlator': (domain.active && domain.active.corr) || uuid.v4()
- }
- };
+function formatCommandsBySubsNgsi2(originalVector) {
+ const attributeList = {};
- if (deviceData.cbHost && deviceData.cbHost.indexOf('://') !== -1) {
- options.url = deviceData.cbHost + '/v2/entities?options=upsert';
- } else if (deviceData.cbHost && deviceData.cbHost.indexOf('://') === -1) {
- options.url = 'http://' + deviceData.cbHost + '/v2/entities?options=upsert';
+ if (originalVector && originalVector.length) {
+ for (let i = 0; i < originalVector.length; i++) {
+ attributeList[originalVector[i].name] = {
+ type: 'command',
+ value: null
+ };
+ }
}
- jsonConcat(options.json, formatAttributesNgsi2(deviceData.active, false));
- jsonConcat(options.json, formatAttributesNgsi2(deviceData.staticAttributes, true));
- jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands));
+ return attributeList;
+}
- for (const att in options.json) {
- try {
- // Format any GeoJSON attrs properly
- options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
- } catch (error) {
- return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
+function createInitialEntityHandlerNgsi2(deviceData, newDevice, callback) {
+ return function handleInitialEntityResponse(error, response, body) {
+ if (error) {
+ logger.error(
+ context,
+ 'ORION-001: Connection error creating inital entity in the Context Broker: %s',
+ error
+ );
+
+ alarms.raise(constants.ORION_ALARM, error);
+
+ callback(error);
+ } else if (response && response.statusCode === 204) {
+ alarms.release(constants.ORION_ALARM);
+ logger.debug(context, 'Initial entity created successfully.');
+ callback(null, newDevice);
+ } else {
+ logger.error(
+ context,
+ 'Protocol error connecting to the Context Broker [%d]: %s',
+ response.statusCode,
+ body
+ );
+
+ const errorObj = new errors.EntityGenericError(newDevice.id, newDevice.type, body, response.statusCode);
+
+ callback(errorObj);
}
+ };
+}
+
+/*
+ * This methods makes a bypass in updateRegisterDeviceNgsi2 to allow not change
+ * extractDeviceDifference and combineWithNewDevice methods
+ */
+function createInitialEntityNgsi2(newDevice, deviceObj, callback) {
+ logger.debug(context, 'createInitialEntityNgsiv2 called with newDevice: %j', newDevice);
+
+ const cmdModeConfig = config.getConfig().cmdMode;
+
+ const isLegacy =
+ (!newDevice.cmdMode && (!cmdModeConfig || cmdModeConfig === 'legacy')) || newDevice.cmdMode === 'legacy';
+ if (isLegacy) {
+ logger.debug(context, 'Old cmdMode with newDevice: %j', deviceObj);
+ return callback(null, deviceObj);
}
- logger.debug(context, 'deviceData: %j', deviceData);
- if (
- ('timestamp' in deviceData && deviceData.timestamp !== undefined
- ? deviceData.timestamp
- : config.getConfig().timestamp) &&
- !utils.isTimestampedNgsi2(options.json)
- ) {
- logger.debug(context, 'config.timestamp %s %s', deviceData.timestamp, config.getConfig().timestamp);
- options.json[constants.TIMESTAMP_ATTRIBUTE] = {
- type: constants.TIMESTAMP_TYPE_NGSI2,
- value: moment()
- };
+ if (!Array.isArray(newDevice.commands) || newDevice.commands.length === 0) {
+ logger.debug(context, 'No initial entity due to no commands by subs in newDevice: %j', newDevice);
+ return callback(null, deviceObj);
+ }
+
+ const entityPayload = {
+ id: String(newDevice.name),
+ type: newDevice.type
+ };
+ jsonConcat(entityPayload, formatCommandsBySubsNgsi2(newDevice.commands));
+
+ let url = config.getConfig().contextBroker.url + '/v2/entities?options=upsert';
+ if (newDevice.cbHost) {
+ url = newDevice.cbHost.includes('://')
+ ? newDevice.cbHost + '/v2/entities?options=upsert'
+ : 'http://' + newDevice.cbHost + '/v2/entities?options=upsert';
}
+
+ const headers = {
+ 'fiware-correlator': (domain.active && domain.active.corr) || uuid.v4()
+ };
+ if (newDevice.service) headers['fiware-service'] = newDevice.service;
+ if (newDevice.subservice) headers['fiware-servicepath'] = newDevice.subservice;
+
+ const options = { url, method: 'POST', json: entityPayload, headers };
+
+ logger.debug(context, 'Creating initial entity due to commands by subs with newDevice: %j', newDevice);
logger.debug(context, 'Creating initial entity in the Context Broker:\n %s', JSON.stringify(options, null, 4));
- utils.executeWithSecurity(options, newDevice, createInitialEntityHandlerNgsi2(deviceData, newDevice, callback));
+
+ return utils.executeWithSecurity(
+ options,
+ newDevice,
+ createInitialEntityHandlerNgsi2(newDevice, deviceObj, callback)
+ );
}
/**
@@ -264,6 +269,7 @@ function createInitialEntityNgsi2(deviceData, newDevice, callback) {
* @param {Object} updatedDevice Device object that will be stored in the database.
*/
function updateEntityNgsi2(deviceData, updatedDevice, callback) {
+ logger.debug(context, 'updateEntityNgsi2 called with deviceData: %j updatedDevice: %j', deviceData, updatedDevice);
const options = {
url: config.getConfig().contextBroker.url + '/v2/entities/' + String(deviceData.name) + '/attrs',
method: 'POST',
@@ -287,14 +293,21 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
jsonConcat(options.json, formatAttributesNgsi2(deviceData.active, false));
jsonConcat(options.json, formatAttributesNgsi2(deviceData.staticAttributes, true));
- jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands));
+ if (
+ (!updatedDevice.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
+ (updatedDevice && updatedDevice.cmdMode === 'legacy')
+ ) {
+ jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands));
+ } else {
+ jsonConcat(options.json, formatCommandsBySubsNgsi2(deviceData.commands));
+ }
for (const att in options.json) {
try {
// Format any GeoJSON attrs properly
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
} catch (error) {
- return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
+ return callback(new errors.BadGeocoordinates(JSON.stringify(options.json), deviceData));
}
}
@@ -306,7 +319,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
) {
options.json[constants.TIMESTAMP_ATTRIBUTE] = {
type: constants.TIMESTAMP_TYPE_NGSI2,
- value: moment()
+ value: new Date().toISOString()
};
}
@@ -331,15 +344,22 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
*
* @param {Object} deviceObj Object with all the device information (mandatory).
*/
-function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
+function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated, callback) {
if (!deviceObj.id || !deviceObj.type) {
- callback(new errors.MissingAttributes('Id or device missing'));
+ callback(new errors.MissingAttributes('Id or device missing', deviceObj));
return;
}
- logger.debug(context, 'Update provisioned v2 device in Device Service');
+ logger.debug(
+ context,
+ 'Update provisioned v2 device %j with Device %j entityInfoUpdated %j',
+ previousDevice,
+ deviceObj,
+ entityInfoUpdated
+ );
function combineWithNewDevice(newDevice, oldDevice, callback) {
+ logger.debug(context, 'combineWithNewDevice %j %j', newDevice, oldDevice);
if (oldDevice) {
oldDevice.internalId = newDevice.internalId;
oldDevice.lazy = newDevice.lazy;
@@ -356,17 +376,30 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
if ('explicitAttrs' in newDevice && newDevice.explicitAttrs !== undefined) {
oldDevice.explicitAttrs = newDevice.explicitAttrs;
}
- if ('expressionLanguage' in newDevice && newDevice.expressionLanguage !== undefined) {
- oldDevice.expressionLanguage = newDevice.expressionLanguage;
- }
if ('apikey' in newDevice && newDevice.apikey !== undefined) {
- oldDevice.explicitAttrs = newDevice.apikey;
+ oldDevice.apikey = newDevice.apikey;
+ }
+ if ('payloadType' in newDevice && newDevice.payloadType !== undefined) {
+ oldDevice.payloadType = newDevice.payloadType;
+ }
+ if ('endpoint' in newDevice && newDevice.endpoint !== undefined) {
+ oldDevice.endpoint = newDevice.endpoint;
+ }
+ if ('transport' in newDevice && newDevice.transport !== undefined) {
+ oldDevice.transport = newDevice.transport;
+ }
+ if ('useCBflowControl' in newDevice && newDevice.useCBflowControl !== undefined) {
+ oldDevice.useCBflowControl = newDevice.useCBflowControl;
+ }
+ if ('storeLastMeasure' in newDevice && newDevice.storeLastMeasure !== undefined) {
+ oldDevice.storeLastMeasure = newDevice.storeLastMeasure;
+ }
+ if ('cmdMode' in newDevice && newDevice.cmdMode !== undefined) {
+ oldDevice.cmdMode = newDevice.cmdMode;
}
- oldDevice.endpoint = newDevice.endpoint || oldDevice.endpoint;
-
callback(null, oldDevice);
} else {
- callback(new errors.DeviceNotFound(newDevice.id));
+ callback(new errors.DeviceNotFound(newDevice.id, newDevice));
}
}
@@ -411,7 +444,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
if (entityInfoUpdated) {
jsonConcat(deviceData.active, oldDevice.active);
jsonConcat(deviceData.lazy, oldDevice.lazy);
- jsonConcat(deviceData.commands, oldDevice.commands);
+ deviceData.commands = deviceData.commands.concat(oldDevice.commands);
jsonConcat(deviceData.staticAttributes, oldDevice.staticAttributes);
if (oldDevice.name !== newDevice.name) {
deviceData.name = newDevice.name;
@@ -420,33 +453,51 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
deviceData.type = newDevice.type;
}
}
-
+ logger.debug(
+ context,
+ 'extractDeviceDifference newDevice %j oldDevice %j difference %j',
+ newDevice,
+ oldDevice,
+ deviceData
+ );
callback(null, deviceData, oldDevice);
}
if (entityInfoUpdated) {
async.waterfall(
[
- apply(config.getRegistry().get, deviceObj.id, deviceObj.service, deviceObj.subservice),
+ apply(
+ config.getRegistry().get,
+ deviceObj.id,
+ previousDevice.apikey, // it could be updated
+ deviceObj.service,
+ deviceObj.subservice
+ ),
apply(extractDeviceDifference, deviceObj),
createInitialEntityNgsi2,
apply(combineWithNewDevice, deviceObj),
apply(registrationUtils.sendRegistrations, false),
apply(registrationUtils.processContextRegistration, deviceObj),
- config.getRegistry().update
+ apply(config.getRegistry().update, previousDevice)
],
callback
);
} else {
async.waterfall(
[
- apply(config.getRegistry().get, deviceObj.id, deviceObj.service, deviceObj.subservice),
+ apply(
+ config.getRegistry().get,
+ deviceObj.id,
+ previousDevice.apikey,
+ deviceObj.service,
+ deviceObj.subservice
+ ),
apply(extractDeviceDifference, deviceObj),
updateEntityNgsi2,
apply(combineWithNewDevice, deviceObj),
apply(registrationUtils.sendRegistrations, false),
apply(registrationUtils.processContextRegistration, deviceObj),
- config.getRegistry().update
+ apply(config.getRegistry().update, previousDevice)
],
callback
);
diff --git a/lib/services/devices/registrationUtils.js b/lib/services/devices/registrationUtils.js
index 10bbf48fe..6c833c6dc 100644
--- a/lib/services/devices/registrationUtils.js
+++ b/lib/services/devices/registrationUtils.js
@@ -36,6 +36,7 @@ const context = {
};
const async = require('async');
const utils = require('../northBound/restUtils');
+const subscriptionService = require('../ngsi/subscriptionService');
const NGSI_LD_URN = 'urn:ngsi-ld:';
@@ -203,6 +204,23 @@ function sendUnregistrationsNgsiLD(deviceData, callback) {
return callback(null, deviceData);
}
+function formatAttributes(originalVector) {
+ const attributeList = [];
+ if (originalVector && originalVector.length) {
+ for (let i = 0; i < originalVector.length; i++) {
+ attributeList.push(originalVector[i].name);
+ }
+ }
+ return attributeList;
+}
+
+function mergeWithSameName(old, current) {
+ if (old.indexOf(current) < 0) {
+ old.push(current);
+ }
+ return old;
+}
+
/**
* Sends a Context Provider registration or unregistration request to the Context Broker using NGSIv2.
*
@@ -210,26 +228,6 @@ function sendUnregistrationsNgsiLD(deviceData, callback) {
* @param {Object} deviceData Object containing all the deviceData needed to send the registration.
*/
function sendRegistrationsNgsi2(unregister, deviceData, callback) {
- function formatAttributes(originalVector) {
- const attributeList = [];
-
- if (originalVector && originalVector.length) {
- for (let i = 0; i < originalVector.length; i++) {
- attributeList.push(originalVector[i].name);
- }
- }
-
- return attributeList;
- }
-
- function mergeWithSameName(old, current) {
- if (old.indexOf(current) < 0) {
- old.push(current);
- }
-
- return old;
- }
-
// FIXME: When https://github.com/telefonicaid/fiware-orion/issues/3007 is merged into master branch,
// this function should use the new API. This is just a temporary solution which implies deleting the
// registration and creating a new one.
@@ -299,6 +297,61 @@ function sendRegistrationsNgsi2(unregister, deviceData, callback) {
utils.executeWithSecurity(options, deviceData, createRegistrationHandlerNgsi2(unregister, deviceData, callback));
}
+function sendUnsubscriptionsNgsi2(deviceData, callback) {
+ if (deviceData.subscriptionId) {
+ logger.debug(
+ context,
+ 'Sending v2 device unsubscriptions to Context Broker at [%s]',
+ config.getConfig().contextBroker.url
+ );
+ logger.debug(context, 'Using the following subscriptionId %j', deviceData.subscriptionId);
+ subscriptionService.unsubscribe(deviceData, deviceData.subscriptionId, callback);
+ } else {
+ logger.debug(context, 'No subscription found for unregister');
+ return callback(null, deviceData);
+ }
+}
+
+function sendSubscriptionsNgsi2(unregister, deviceData, callback) {
+ function updateSubscriptionNgsi2(deviceData, callback) {
+ const functions = [];
+
+ function removeSubscriptionId(deviceData, unregistrationResult, callback) {
+ delete deviceData.subscriptionId;
+ return callback(null, deviceData);
+ }
+
+ functions.push(async.apply(sendSubscriptionsNgsi2, true, deviceData));
+ functions.push(async.apply(removeSubscriptionId, deviceData));
+ functions.push(async.apply(sendSubscriptionsNgsi2, false));
+ async.waterfall(functions, callback);
+ }
+
+ if (unregister) {
+ return sendUnsubscriptionsNgsi2(deviceData, callback);
+ }
+ if (deviceData.subscriptionId) {
+ return updateSubscriptionNgsi2(deviceData, callback);
+ }
+ const attrs = [].concat(formatAttributes(deviceData.commands)).reduce(mergeWithSameName, []);
+
+ if (attrs.length === 0) {
+ logger.debug(context, 'Subscription is not needed. Device without commands');
+ return callback(null, deviceData);
+ }
+ const trigger = attrs; // one subscription for all commands
+ const content = attrs;
+ const attrsFormat = 'normalized';
+
+ logger.debug(
+ context,
+ 'Sending v2 device subscriptions to Context Broker at [%s]',
+ config.getConfig().contextBroker.url
+ );
+ logger.debug(context, 'Using the following trigger %j and content %j', trigger, content);
+ subscriptionService.subscribe(deviceData, trigger, content, attrsFormat, callback);
+}
+
/**
* Sends a Context Provider registration or unregistration request to the Context Broker using NGSI-LD.
*
@@ -323,14 +376,14 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
properties.push(element.name);
});
- if (lazy.length > 0){
- operations.push('retrieveOps');
+ if (lazy.length > 0) {
+ operations.push('retrieveOps');
}
- if (commands.length > 0){
- operations.push('updateOps');
+ if (commands.length > 0) {
+ operations.push('updateOps');
}
- if (supportMerge){
- operations.push('mergeEntity');
+ if (supportMerge) {
+ operations.push('mergeEntity');
}
if (properties.length === 0) {
@@ -368,8 +421,8 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
endpoint: config.getConfig().providerUrl,
contextSourceInfo: [
{
- 'key': 'jsonldContext',
- 'value': config.getConfig().contextBroker.jsonLdContext
+ key: 'jsonldContext',
+ value: config.getConfig().contextBroker.jsonLdContext
}
],
'@context': config.getConfig().contextBroker.jsonLdContext
@@ -405,7 +458,14 @@ function sendRegistrations(unregister, deviceData, callback) {
sendRegistrationsNgsiLD(unregister, deviceData, callback);
break;
default:
- sendRegistrationsNgsi2(unregister, deviceData, callback);
+ if (
+ (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
+ (deviceData && deviceData.cmdMode === 'legacy')
+ ) {
+ sendRegistrationsNgsi2(unregister, deviceData, callback);
+ } else {
+ sendSubscriptionsNgsi2(unregister, deviceData, callback);
+ }
break;
}
}
@@ -421,7 +481,14 @@ function processContextRegistration(deviceData, body, callback) {
const newDevice = _.clone(deviceData);
if (body) {
- newDevice.registrationId = body.registrationId;
+ if (
+ (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
+ (deviceData && deviceData.cmdMode === 'legacy')
+ ) {
+ newDevice.registrationId = body.registrationId;
+ } else {
+ newDevice.subscriptionId = body.subscriptionId;
+ }
}
callback(null, newDevice);
diff --git a/lib/services/groups/groupRegistryMemory.js b/lib/services/groups/groupRegistryMemory.js
index 760fa9663..5d0173175 100644
--- a/lib/services/groups/groupRegistryMemory.js
+++ b/lib/services/groups/groupRegistryMemory.js
@@ -27,7 +27,6 @@ let registeredGroups = {};
const logger = require('logops');
const intoTrans = require('../common/domain').intoTrans;
const errors = require('../../errors');
-const config = require('../../commonConfig');
const _ = require('underscore');
const context = {
op: 'IoTAgentNGSI.InMemoryGroupRegister'
@@ -51,7 +50,7 @@ function exists(group) {
function createGroup(group, callback) {
if (exists(group)) {
- callback(new errors.DuplicateGroup(group.resource, group.apikey));
+ callback(new errors.DuplicateGroup(group));
} else {
const storeGroup = _.clone(group);
@@ -62,7 +61,7 @@ function createGroup(group, callback) {
logger.debug(
context,
- 'Storing device group for service [%s] and subservice [%s]',
+ 'Storing device group id %s for service [%s] and subservice [%s]',
storeGroup._id,
storeGroup.service,
storeGroup.subservice
@@ -131,31 +130,7 @@ function clear(callback) {
callback();
}
-function findSingleConfigurationMode(service, subservice, callback) {
- let result;
-
- for (const i in registeredGroups) {
- if (
- registeredGroups.hasOwnProperty(i) &&
- registeredGroups[i].service === service &&
- registeredGroups[i].subservice === subservice
- ) {
- result = registeredGroups[i];
- break;
- }
- }
-
- if (result) {
- callback(null, result);
- } else {
- callback(new errors.DeviceGroupNotFound(service, subservice));
- }
-}
-
function find(service, subservice, callback) {
- if (config.getConfig().singleConfigurationMode === true) {
- return findSingleConfigurationMode(service, subservice, callback);
- }
const result = [];
for (const i in registeredGroups) {
@@ -169,6 +144,7 @@ function find(service, subservice, callback) {
}
if (result.length > 0) {
+ logger.debug(context, 'groups found %j', result);
return callback(null, {
count: result.length,
services: result
@@ -194,7 +170,7 @@ function findBy(fields) {
/* eslint-disable-next-line prefer-rest-params */
const callback = arguments[i];
- logger.debug(context, 'Looking for device params %j', fields);
+ logger.debug(context, 'Looking for group with params %j', fields);
for (const p in registeredGroups) {
if (registeredGroups.hasOwnProperty(p)) {
@@ -214,6 +190,7 @@ function findBy(fields) {
}
if (result) {
+ logger.debug(context, 'group found %j', result);
callback(null, result);
} else {
callback(new errors.DeviceGroupNotFound('n/a', 'n/a'));
@@ -236,6 +213,7 @@ function getSingleGroup(resource, apikey, callback) {
}
if (result) {
+ logger.debug(context, 'single group found %j', result);
callback(null, result);
} else {
callback(new errors.DeviceGroupNotFound(resource, apikey));
@@ -253,6 +231,7 @@ function getSingleGroupType(type, callback) {
}
if (result) {
+ logger.debug(context, 'single group type found %j', result);
callback(null, result);
} else {
callback(new errors.DeviceGroupNotFound(type));
@@ -268,7 +247,7 @@ function update(id, body, callback) {
groupToModify[i] = body[i];
}
}
-
+ logger.debug(context, 'groupd to update %j', groupToModify);
callback(null, groupToModify);
} else {
callback(new errors.DeviceGroupNotFound(id));
@@ -287,8 +266,8 @@ exports.list = intoTrans(context, listGroups);
exports.init = intoTrans(context, init);
exports.find = intoTrans(context, find);
exports.findBy = intoTrans(context, findBy);
-exports.findType = intoTrans(context, findBy(['service', 'subservice', 'type']));
-exports.findTypeSilently = intoTrans(context, findBy(['service', 'subservice', 'type']));
+exports.findType = intoTrans(context, findBy(['service', 'subservice', 'type', 'apikey']));
+exports.findTypeSilently = intoTrans(context, findBy(['service', 'subservice', 'type', 'apikey']));
exports.findSilently = intoTrans(context, findBy(['service', 'subservice']));
exports.get = intoTrans(context, getSingleGroup);
exports.getSilently = intoTrans(context, getSingleGroup);
diff --git a/lib/services/groups/groupRegistryMongoDB.js b/lib/services/groups/groupRegistryMongoDB.js
index c80d29d0e..ac1076300 100644
--- a/lib/services/groups/groupRegistryMongoDB.js
+++ b/lib/services/groups/groupRegistryMongoDB.js
@@ -24,7 +24,7 @@
*/
const logger = require('logops');
-const dbService = require('../../model/dbConn');
+const mongoose = require('mongoose');
const intoTrans = require('../common/domain').intoTrans;
const fillService = require('./../common/domain').fillService;
const alarmsInt = require('../common/alarmManagement').intercept;
@@ -41,6 +41,8 @@ const attributeList = [
'resource',
'apikey',
'type',
+ 'endpoint',
+ 'transport',
'service',
'subservice',
'description',
@@ -58,28 +60,13 @@ const attributeList = [
'expressionLanguage',
'defaultEntityNameConjunction',
'ngsiVersion',
- 'entityNameExp'
+ 'entityNameExp',
+ 'payloadType',
+ 'useCBflowControl',
+ 'storeLastMeasure',
+ 'cmdMode'
];
-/**
- * Generates a handler for the save device group operations. The handler will take the customary error and the saved
- * device group as the parameters (and pass the serialized DAO as the callback value).
- *
- * @return {Function} The generated handler.
- */
-function saveGroupHandler(groupDAO, callback) {
- /* eslint-disable-next-line no-unused-vars */
- return function saveHandler(error, result) {
- if (error) {
- logger.debug(fillService(context, groupDAO), 'Error storing device group information: %s', error);
-
- callback(new errors.InternalDbError(error));
- } else {
- callback(null, groupDAO.toObject());
- }
- };
-}
-
function createGroup(group, callback) {
/* eslint-disable-next-line new-cap */
const groupObj = new Group.model();
@@ -96,9 +83,12 @@ function createGroup(group, callback) {
groupObj.apikey,
groupObj.resource
);
-
- groupObj.save(function saveHandler(error, groupDAO) {
- if (error) {
+ groupObj
+ .save({})
+ .then((groupDAO) => {
+ callback(null, groupDAO.toObject());
+ })
+ .catch((error) => {
if (error.code === 11000) {
logger.debug(
context,
@@ -106,17 +96,12 @@ function createGroup(group, callback) {
group.resource,
group.apikey
);
-
- callback(new errors.DuplicateGroup(group.resource, group.apikey));
+ callback(new errors.DuplicateGroup(group));
} else {
logger.debug(context, 'Error storing device group information: %s', error);
-
callback(new errors.InternalDbError(error));
}
- } else {
- callback(null, groupDAO.toObject());
- }
- });
+ });
}
/**
@@ -138,7 +123,7 @@ function listGroups(service, limit, offset, callback) {
}
const query = Group.model.find(condition).sort();
-
+ const queryCount = Group.model.countDocuments(condition);
if (limit) {
query.limit(parseInt(limit, 10));
}
@@ -146,11 +131,27 @@ function listGroups(service, limit, offset, callback) {
if (offset) {
query.skip(parseInt(offset, 10));
}
-
- async.series([query.exec.bind(query), Group.model.countDocuments.bind(Group.model, condition)], function (
- error,
- results
- ) {
+ function funcQuery(cb) {
+ query
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ function funcQueryCount(cb) {
+ queryCount
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ async.series([funcQuery, funcQueryCount], function (error, results) {
callback(error, {
count: results[1],
services: results[0].map(toObjectFn)
@@ -165,21 +166,22 @@ function getById(id, callback) {
const query = Group.model.findOne({ _id: id });
query.select({ __v: 0 });
- query.lean().exec(function handleGet(error, data) {
- if (error) {
+ query
+ .exec({})
+ .then((data) => {
+ if (data) {
+ context = fillService(context, data);
+ logger.debug(context, 'Device group data found: %j', data);
+ callback(null, data);
+ } else {
+ logger.debug(context, 'Device group [%s] not found.', id);
+ callback(new errors.DeviceGroupNotFound(id));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting group: %s', error);
-
callback(new errors.InternalDbError(error));
- } else if (data) {
- context = fillService(context, data);
- logger.debug(context, 'Device group data found: %j', data);
- callback(null, data);
- } else {
- logger.debug(context, 'Device group [%s] not found.', id);
-
- callback(new errors.DeviceGroupNotFound(id));
- }
- });
+ });
}
/**
@@ -200,11 +202,28 @@ function find(service, subservice, callback) {
condition.subservice = subservice;
const query = Group.model.find(condition).sort();
-
- async.series([query.exec.bind(query), Group.model.countDocuments.bind(Group.model, condition)], function (
- error,
- results
- ) {
+ const queryCount = Group.model.countDocuments(condition);
+ function funcQuery(cb) {
+ query
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ function funcQueryCount(cb) {
+ queryCount
+ .exec({})
+ .then((res) => {
+ cb(null, res);
+ })
+ .catch((error) => {
+ cb(error);
+ });
+ }
+ async.series([funcQuery, funcQueryCount], function (error, results) {
callback(error, {
count: results[1],
services: results[0].map(toObjectFn)
@@ -215,19 +234,24 @@ function find(service, subservice, callback) {
function findOneInMongoDB(queryObj, fields, callback) {
const query = Group.model.findOne(queryObj);
query.select({ __v: 0 });
- query.lean().exec(function handleGet(error, data) {
- if (error) {
+ query.lean();
+
+ query
+ .exec({})
+ .then((data) => {
+ if (data) {
+ context = fillService(context, data);
+ logger.debug(context, 'Device group data found: %j', data);
+ callback(null, data);
+ } else {
+ logger.debug(context, 'Device group for fields [%j] not found: [%j]', fields, queryObj);
+ callback(new errors.DeviceGroupNotFound(fields, queryObj));
+ }
+ })
+ .catch((error) => {
logger.debug(context, 'Internal MongoDB Error getting group: %s', error);
callback(new errors.InternalDbError(error));
- } else if (data) {
- context = fillService(context, data);
- logger.debug(context, 'Device group data found: %j', data);
- callback(null, data);
- } else {
- logger.debug(context, 'Device group for fields [%j] not found: [%j]', fields, queryObj);
- callback(new errors.DeviceGroupNotFound(fields, queryObj));
- }
- });
+ });
}
function findBy(fields) {
@@ -266,7 +290,15 @@ function update(id, body, callback) {
/* eslint-disable-next-line new-cap */
const groupObj = new Group.model(group);
groupObj.isNew = false;
- groupObj.save(saveGroupHandler(groupObj, callback));
+ groupObj
+ .save({})
+ .then((groupDAO) => {
+ callback(null, groupDAO.toObject());
+ })
+ .catch((error) => {
+ logger.debug(fillService(context, group), 'Error storing device group information: %s', error);
+ callback(new errors.InternalDbError(error));
+ });
}
});
}
@@ -278,16 +310,18 @@ function remove(id, callback) {
if (error) {
callback(error);
} else {
- Group.model.deleteOne({ _id: id }, function (error) {
- if (error) {
- logger.debug(context, 'Internal MongoDB Error getting device: %s', error);
+ const query = Group.model.deleteOne({ _id: id });
+ query
+ .exec({})
+ .then(() => {
+ logger.debug(context, 'Device group [%s] successfully removed.', id);
+ callback(null, deviceGroup);
+ })
+ .catch((error) => {
+ logger.debug(context, 'Internal MongoDB Error getting device group: %s', error);
callback(new errors.InternalDbError(error));
- } else {
- logger.debug(context, 'Device [%s] successfully removed.', id);
- callback(null, deviceGroup);
- }
- });
+ });
}
});
}
@@ -297,7 +331,14 @@ function init(newConfig, callback) {
}
function clear(callback) {
- dbService.db.db.dropDatabase(callback);
+ mongoose.connection
+ .dropDatabase()
+ .then(() => {
+ callback(null);
+ })
+ .catch((error) => {
+ callback(error);
+ });
}
exports.create = alarmsInt(constants.MONGO_ALARM + '_01', intoTrans(context, createGroup));
diff --git a/lib/services/groups/groupService.js b/lib/services/groups/groupService.js
index 89a1db5f4..1faa6ccec 100644
--- a/lib/services/groups/groupService.js
+++ b/lib/services/groups/groupService.js
@@ -48,7 +48,7 @@ function validateGroup(group, callback) {
return function (error, foundGroup) {
logger.debug(context, 'generateDuplicateHander error %j and foundGroup %j', error, foundGroup);
if (!error || (foundGroup && foundGroup.count > 0)) {
- innerCb(new errors.DuplicateGroup(group.resource, group.apikey));
+ innerCb(new errors.DuplicateGroup(group));
} else {
innerCb();
}
@@ -59,10 +59,6 @@ function validateGroup(group, callback) {
config.getGroupRegistry().getSilently(group.resource, group.apikey, generateDuplicateHandler(innerCb));
}
- function checkServiceAndSubservice(innerCb) {
- config.getGroupRegistry().find(group.service, group.subservice, generateDuplicateHandler(innerCb));
- }
-
function checkMandatoryParams(innerCb) {
if (!group.service) {
innerCb(new errors.MissingConfigParams(['service']));
@@ -74,22 +70,15 @@ function validateGroup(group, callback) {
return;
}
- if (config.getConfig().singleConfigurationMode === false) {
- if (!group.type) {
- innerCb(new errors.MissingConfigParams(['type']));
- return;
- }
+ if (!group.type) {
+ innerCb(new errors.MissingConfigParams(['type']));
+ return;
}
innerCb();
}
validations.push(checkApiKeyAndResource);
-
- if (config.getConfig().singleConfigurationMode === true) {
- validations.push(checkServiceAndSubservice);
- }
-
validations.push(checkMandatoryParams);
async.series(validations, callback);
@@ -134,7 +123,7 @@ function listGroups(service, limit, offset, callback) {
}
function checkServiceIdentity(service, subservice, deviceGroup, callback) {
- if (deviceGroup.service === service && deviceGroup.subservice === subservice) {
+ if (deviceGroup.service === service.toLowerCase() && deviceGroup.subservice === subservice) {
callback(null, deviceGroup);
} else {
callback(new errors.MismatchedService(service, subservice));
@@ -172,7 +161,7 @@ function remove(service, subservice, resource, apikey, device, callback) {
}
function unregisterDevice(device, cb) {
- deviceService.unregister(device.id, service, subservice, function (error) {
+ deviceService.unregister(device.id, device.apikey, service, subservice, function (error) {
if (error) {
cb(error);
}
@@ -302,8 +291,14 @@ function getEffectiveApiKey(service, subservice, type, callback) {
logger.debug(context, 'Using default API Key: %s', config.getConfig().defaultKey);
callback(null, config.getConfig().defaultKey);
} else {
- logger.error(context, 'Could not find any API Key information for device.');
- callback(new errors.GroupNotFound(service, subservice));
+ logger.error(
+ context,
+ 'Could not find any APIKey information for device in service %s subservice %s and type %s',
+ service,
+ subservice,
+ type
+ );
+ callback(new errors.GroupNotFound(service, subservice, type));
}
}
diff --git a/lib/services/ngsi/entities-NGSI-LD.js b/lib/services/ngsi/entities-NGSI-LD.js
index b20096323..68e54482b 100644
--- a/lib/services/ngsi/entities-NGSI-LD.js
+++ b/lib/services/ngsi/entities-NGSI-LD.js
@@ -28,20 +28,17 @@
const request = require('../../request-shim');
const alarms = require('../common/alarmManagement');
const errors = require('../../errors');
-const utils = require('../northBound/restUtils');
const pluginUtils = require('../../plugins/pluginUtils');
const config = require('../../commonConfig');
const constants = require('../../constants');
const jexlParser = require('../../plugins/jexlParser');
const expressionPlugin = require('../../plugins/expressionPlugin');
-const compressTimestampPlugin = require('../../plugins/compressTimestamp');
const moment = require('moment-timezone');
const logger = require('logops');
const _ = require('underscore');
const context = {
op: 'IoTAgentNGSI-LD'
};
-const NGSIv2 = require('./entities-NGSI-v2');
const NGSIUtils = require('./ngsiUtils');
const NGSI_LD_URN = 'urn:ngsi-ld:';
@@ -62,10 +59,13 @@ function convertAttrNGSILD(attr) {
return undefined;
}
let obj = { type: 'Property', value: attr.value };
+ let hasValueType = true;
switch (attr.type.toLowerCase()) {
// Properties
case 'property':
+ hasValueType = false;
+ break;
case 'string':
case 'text':
case 'textunrestricted':
@@ -99,24 +99,26 @@ function convertAttrNGSILD(attr) {
}
break;
+ case 'object':
+ case 'array':
+ try {
+ obj.value = JSON.parse(attr.value);
+ } catch (e) {
+ // Do nothing
+ }
+ break;
+
// Temporal Properties
case 'datetime':
- obj.value = {
- '@type': 'DateTime',
- '@value': moment.tz(attr.value, 'Etc/UTC').toISOString()
- };
+ obj.value = moment.tz(attr.value, 'Etc/UTC').toISOString();
break;
case 'date':
- obj.value = {
- '@type': 'Date',
- '@value': moment.tz(attr.value, 'Etc/UTC').format(moment.HTML5_FMT.DATE)
- };
+ obj.value = moment.tz(attr.value, 'Etc/UTC').format(moment.HTML5_FMT.DATE);
break;
case 'time':
- obj.value = {
- '@type': 'Time',
- '@value': moment.tz(attr.value, 'Etc/UTC').format(moment.HTML5_FMT.TIME_SECONDS)
- };
+ obj.value = moment
+ .tz(new Date('0000-01-01 ' + attr.value), 'Etc/UTC')
+ .format(moment.HTML5_FMT.TIME_SECONDS);
break;
// GeoProperties
@@ -124,37 +126,44 @@ function convertAttrNGSILD(attr) {
case 'point':
case 'geo:point':
case 'geo:json':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('Point', attr.value);
break;
case 'linestring':
case 'geo:linestring':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('LineString', attr.value);
break;
case 'polygon':
case 'geo:polygon':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('Polygon', attr.value);
break;
case 'multipoint':
case 'geo:multipoint':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('MultiPoint', attr.value);
break;
case 'multilinestring':
case 'geo:multilinestring':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('MultiLineString', attr.value);
break;
case 'multipolygon':
case 'geo:multipolygon':
+ hasValueType = false;
obj.type = 'GeoProperty';
obj.value = NGSIUtils.getLngLats('MultiPolygon', attr.value);
break;
// Relationships
case 'relationship':
+ hasValueType = false;
obj.type = 'Relationship';
obj.object = attr.value;
delete obj.value;
@@ -162,13 +171,57 @@ function convertAttrNGSILD(attr) {
// LanguageProperties
case 'languageproperty':
+ hasValueType = false;
obj.type = 'LanguageProperty';
obj.languageMap = attr.value;
delete obj.value;
break;
+ // VocabProperties
+ case 'vocabproperty':
+ hasValueType = false;
+ obj.type = 'VocabProperty';
+ obj.vocab = attr.value;
+ delete obj.value;
+ break;
+ // JsonProperties
+ case 'jsonproperty':
+ hasValueType = false;
+ obj.type = 'JsonProperty';
+ obj.json = attr.value;
+ delete obj.value;
+ break;
+ // ListProperties
+ case 'listproperty':
+ hasValueType = false;
+ obj.type = 'ListProperty';
+ obj.listValue = attr.value;
+ delete obj.value;
+ break;
+ // ListRelationship
+ case 'listrelationship':
+ hasValueType = false;
+ obj.type = 'ListRelationship';
+ obj.listObject = attr.value;
+ delete obj.value;
+ break;
+
default:
- obj.value = { '@type': attr.type, '@value': attr.value };
+ obj.value = attr.value;
+ }
+
+ if (hasValueType) {
+ switch (config.getConfig().server.ldSupport.dataType) {
+ case '@type':
+ obj.value = {
+ '@type': attr.type,
+ '@value': obj.value
+ };
+ break;
+ case 'valueType':
+ obj.valueType = attr.type;
+ break;
+ }
}
if (!!obj && attr.metadata) {
@@ -265,7 +318,7 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
} else if (
response &&
operationName === 'update' &&
- (response.statusCode === 200 || response.statusCode === 204)
+ (response.statusCode === 200 || response.statusCode === 204 || response.statusCode === 201)
) {
logger.info(context, 'Received the following response from the CB: Value updated successfully\n');
alarms.release(constants.ORION_ALARM);
@@ -305,9 +358,9 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
}
if (errorField !== undefined) {
- callback(new errors.DeviceNotFound(entityName));
+ callback(new errors.DeviceNotFound(entityName, typeInformation));
} else {
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
+ callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
}
} else {
logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
@@ -315,7 +368,15 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
body = JSON.parse(body);
}
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body, response.statusCode));
+ callback(
+ new errors.EntityGenericError(
+ entityName,
+ typeInformation.type,
+ typeInformation,
+ body,
+ response.statusCode
+ )
+ );
}
};
}
@@ -340,7 +401,7 @@ function sendQueryValueNgsiLD(entityName, attributes, typeInformation, token, ca
options.method = 'GET';
if (!typeInformation || !typeInformation.type) {
- callback(new errors.TypeNotFound(null, entityName));
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
return;
}
@@ -419,561 +480,389 @@ function addLinkedEntities(typeInformation, json) {
* @param {Object} typeInformation Configuration information for the device.
* @param {String} token User token to identify against the PEP Proxies (optional).
*/
-function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, callback) {
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
- entityName,
- attributes,
- typeInformation
- );
- const payload = [
- {
- id: entityName
+/**
+ * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
+ * array should comply to the NGSIv2's attribute format.
+ *
+ * @param {String} entityName Name of the entity to register.
+ * @param {Array} measures measure array containing the values to update.
+ * @param {Object} typeInformation Configuration information for the device.
+ * @param {String} token User token to identify against the PEP Proxies (optional).
+ */
+function sendUpdateValueNgsiLD(entityName, originMeasures, originTypeInformation, token, callback) {
+ //aux function used to builf JEXL context.
+ //it returns a flat object from an Attr array
+ function reduceAttrToPlainObject(attrs, initObj = {}) {
+ if (attrs !== undefined && Array.isArray(attrs)) {
+ return attrs.reduce((result, item) => {
+ result[item.name] = item.value;
+ return result;
+ }, initObj);
+ } else {
+ return initObj;
}
- ];
-
- const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
-
- if (typeInformation && typeInformation.type) {
- payload[0].type = typeInformation.type;
}
+ //Make a clone and overwrite
+ const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
- if (config.getConfig().appendMode === false) {
- payload.actionType = 'update';
- } else {
- payload.actionType = 'append';
+ //Check mandatory information: type
+ if (!originTypeInformation || !originTypeInformation.type) {
+ callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
+ return;
}
- const options = NGSIUtils.createRequestObject(url, typeInformation, token);
- options.method = 'POST';
+ const payload = []; //will store the final payload
+ let entities = {};
- if (typeInformation && typeInformation.staticAttributes) {
- attributes = attributes.concat(typeInformation.staticAttributes);
- }
+ const currentIsoDate = new Date().toISOString();
+ const currentMoment = moment(currentIsoDate);
+ //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
+ const mustInsertTimeInstant =
+ originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
- if (!typeInformation || !typeInformation.type) {
- callback(new errors.TypeNotFound(null, entityName));
- return;
+ // Check if measures is a single measure or a array of measures (a multimeasure)
+ if (originMeasures[0] && !originMeasures[0][0]) {
+ originMeasures = [originMeasures];
}
- const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
- logger.debug(context, 'sendUpdateValueNgsiLD \n idTypeSSS are %j ', idTypeSSSList);
- const measureAttrsForCtxt = [];
- // Check explicitAttrs: adds all final needed attributes to payload
- if (
- typeInformation.explicitAttrs === undefined ||
- (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
- // explicitAttrs is not defined => default case: all attrs should be included
- ) {
- // This loop adds all measure values (attributes) into payload entities (entity[0])
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && attributes[i].type) {
- payload[0][attributes[i].name] = {
- value: attributes[i].value,
- type: attributes[i].type
- };
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
- if (metadata) {
- payload[0][attributes[i].name].metadata = metadata;
- }
- } else {
- callback(new errors.BadRequest(null, entityName));
- return;
- }
- }
- logger.debug(context, 'sendUpdateValueNgsiLD \n pre-initial non-explicitAttrs payload=%j', payload);
- // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
- if (typeInformation.active) {
- typeInformation.active.forEach((attr) => {
- if (attr.expression) {
- if (attr.object_id) {
- payload[0][attr.object_id] = {
- value: payload[0][attr.object_id] ? payload[0][attr.object_id].value : undefined,
- type: attr.type,
- object_id: attr.object_id
- };
- } else {
- payload[0][attr.name] = {
- value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
- type: attr.type
- };
- }
- }
- });
- }
- } else {
- let selectedAttrs = [];
- if (typeof typeInformation.explicitAttrs === 'string') {
- // explicitAttrs is a jexlExpression
- // This ctxt should include all possible attrs
- const attributesCtxt = [];
- if (typeInformation.static) {
- typeInformation.static.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- // Measures
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && attributes[i].type) {
- const measureAttr = {
- name: attributes[i].name,
- value: attributes[i].value,
- type: attributes[i].type
- };
- attributesCtxt.push(measureAttr);
- }
- }
- // This context is just to calculate explicitAttrs when is an expression
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
- // typeInformation.active attrs with expressions expanded by current ctxt
- if (typeInformation.active) {
- typeInformation.active.forEach(function (att) {
- if (att.expression) {
- if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
- const expandedAttr = {
- name: att.name,
- value: att.expression, // it doesn't matter final value here
- type: att.type
- };
- attributesCtxt.push(expandedAttr);
- ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
- }
- }
- });
- }
- // calculate expression for explicitAttrs
- try {
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
- if (res === true) {
- // like explicitAttrs == true
- // selectAttrs should be measures which are defined attributes
- typeInformation.active.forEach((attr) => {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- });
- } else if (res === false) {
- // like explicitAttrs == false
- // selectAttrs should be measures and defined attributes
- typeInformation.active.forEach((attr) => {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- });
- for (let i = 0; i < attributes.length; i++) {
- selectedAttrs.push(attributes[i].name);
- }
- } else {
- selectedAttrs = res; // TBD: Check ensure is an array of strings
- }
- if (selectedAttrs.length === 0) {
- // implies do nothing
- logger.info(
- context,
- 'sendUpdateValueNgsiLD \n none selectedAttrs with %j and ctxt %j',
- typeInformation.explicitAttrs,
- ctxt
- );
- return callback(null);
- }
- } catch (e) {
- // nothing to do: exception is already logged at info level
- }
+ for (let measures of originMeasures) {
+ entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
- typeInformation.active.forEach((attr) => {
- if (selectedAttrs.includes(attr.name)) {
- selectedAttrs.push(attr.object_id);
- }
- });
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
- // TBD: selectedAttrs could be a boolean as a result of applyExpression
- // explicitAtts is true => Add measures which are defined attributes
- typeInformation.active.forEach((attr) => {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- });
- }
- // This loop adds selected measured values (attributes) into payload entities (entity[0])
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
- const attr = typeInformation.active.find((obj) => {
- return obj.name === attributes[i].name;
- });
- payload[0][attributes[i].name] = {
- value: attributes[i].value,
- type: attributes[i].type
- };
- // ensure payload has attr with proper object_id
- if (attr && attr.object_id) {
- payload[0][attributes[i].name].object_id = attr.object_id;
- }
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
- if (metadata) {
- payload[0][attributes[i].name].metadata = metadata;
- }
- } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
- const att = {
- name: attributes[i].name,
- type: attributes[i].type,
- value: attributes[i].value
- };
- measureAttrsForCtxt.push(att);
+ let plainMeasures = null; //will contain measures POJO
+ //Make a clone and overwrite
+ const typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
+
+ //Rename all measures with matches with id and type to measure_id and measure_type
+ for (const measure of measures) {
+ if (measure.name === 'id' || measure.name === 'type') {
+ measure.name = constants.MEASURE + measure.name;
}
}
+
+ //Make a copy of measures in an plain object: plainMeasures
+ plainMeasures = reduceAttrToPlainObject(measures);
+ //Build the initital JEXL Context
+ //All the measures (avoid references make another copy instead)
+ jexlctxt = reduceAttrToPlainObject(measures);
+ //All the static
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
+ //id type Service and Subservice
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
+
logger.debug(
context,
- 'sendUpdateValueNgsiLD \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
- payload,
- selectedAttrs
+ 'sendUpdateValueNgsiLD loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
+ entityName,
+ plainMeasures,
+ typeInformation,
+ jexlctxt,
+ mustInsertTimeInstant
);
- // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
- if (typeInformation.active) {
- typeInformation.active.forEach((attr) => {
- if (selectedAttrs.includes(attr.name)) {
- if (attr.object_id) {
- payload[0][attr.object_id] = {
- value: payload[0][attr.object_id]
- ? payload[0][attr.object_id].value
- : payload[0][attr.name]
- ? payload[0][attr.name].value
- : undefined,
- type: attr.type,
- object_id: attr.object_id
- };
- } else {
- payload[0][attr.name] = {
- value: payload[0][attr.name] ? payload[0][attr.name].value : undefined,
- type: attr.type
- };
- }
- }
- });
+ //Now we can calculate the EntityName of primary entity
+ let entityNameCalc = null;
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
+ try {
+ logger.debug(context, 'sendUpdateValueNgsiLD entityNameExp %j', typeInformation.entityNameExp);
+ entityNameCalc = expressionPlugin.applyExpression(
+ typeInformation.entityNameExp,
+ jexlctxt,
+ typeInformation
+ );
+ } catch (e) {
+ logger.debug(
+ context,
+ 'Error evaluating expression for entityName: %j with context: %j',
+ typeInformation.entityNameExp,
+ jexlctxt
+ );
+ }
}
- } // END check explicitAttrs
- logger.debug(context, 'sendUpdateValueNgsiLD \n initial payload=%j', payload);
- const currentEntity = payload[0];
+ entityName = entityNameCalc ? entityNameCalc : entityName;
+ //enrich JEXL context
+ jexlctxt.entity_name = entityName;
- // Prepare attributes for expresionPlugin
- const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
-
- // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
- let attsArrayFiltered = [];
- if (attsArray) {
- attsArrayFiltered = attsArray.filter((obj) => {
- return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
- });
- }
- let attributesCtxt = [...attsArrayFiltered]; // just copy
- if (typeInformation.static) {
- typeInformation.static.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- if (measureAttrsForCtxt) {
- measureAttrsForCtxt.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- attributesCtxt = attributesCtxt.concat(idTypeSSSList);
- const ctxt = expressionPlugin.extractContext(attributesCtxt);
- logger.debug(context, 'sendUpdateValueNgsiLD \n initial ctxt %j ', ctxt);
-
- // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
- // attributes without expressions should be processed before
- logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity %j ', currentEntity);
- if (typeInformation.active && typeInformation.active.length > 0) {
- for (const k in currentEntity) {
- typeInformation.active.forEach(function (att) {
- if (
- (att.object_id && att.object_id === k && att.expression) ||
- (att.name && att.name === k && att.expression)
- ) {
- const m = currentEntity[k];
- delete currentEntity[k];
- currentEntity[k] = m; // put into the end of currentEntity
- }
- });
+ let preprocessedAttr = [];
+ //Add Raw Static, Lazy, Command and Actives attr attributes
+ if (typeInformation && typeInformation.staticAttributes) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
}
- }
- logger.debug(context, 'sendUpdateValueNgsiLD \n currentEntity sorted %j ', currentEntity);
- let timestampValue;
- // Loop for each final attribute to apply alias, multientity and expressions
- for (const j in currentEntity) {
- // discard id and type
- if (j !== 'id' || j !== 'type') {
- // Apply Mapping Alias: object_id in attributes are in typeInformation.active
- let attr;
- let newAttr = payload[0][j];
- if (typeInformation.active) {
- attr = typeInformation.active.find((obj) => {
- return obj.object_id === j;
- });
- }
- if (!attr) {
- if (typeInformation.lazy) {
- attr = typeInformation.lazy.find((obj) => {
- return obj.object_id === j;
- });
- }
- }
- if (!attr) {
- if (typeInformation.active) {
- attr = typeInformation.active.find((obj) => {
- return obj.name === j;
- });
- }
- }
- if (attr && attr.name) {
- if (['id', 'type'].includes(attr.name)) {
- // invalid mapping
+ if (typeInformation && typeInformation.lazy) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
+ }
+ if (typeInformation && typeInformation.active) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
+ }
+
+ //Proccess every proto Attribute to populate entities data steuture
+ entities[entityName] = {};
+ entities[entityName][typeInformation.type] = [];
+
+ for (const currentAttr of preprocessedAttr) {
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
+ let attrEntityName = entityName;
+ let attrEntityType = typeInformation.type;
+ let valueExpression = null;
+ //manage active attr without object__id (name by default)
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
+
+ //determine AttrEntityName for multientity
+ if (
+ currentAttr.entity_name !== null &&
+ currentAttr.entity_name !== undefined &&
+ currentAttr.entity_name !== '' &&
+ typeof currentAttr.entity_name === 'string'
+ ) {
+ try {
+ logger.debug(
+ context,
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
+ currentAttr.name,
+ currentAttr.entity_name,
+ jexlctxt
+ );
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
+ if (!attrEntityName) {
+ attrEntityName = currentAttr.entity_name;
+ }
+ } catch (e) {
logger.debug(
context,
- 'sendUpdateValueNgsiLD \n invalid mapping for attr %j \n newAttr %j',
- attr,
- newAttr
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
+ currentAttr.entity_name,
+ jexlctxt
);
- delete payload[0][attr.object_id];
- attr = undefined; // stop processing attr
- newAttr = undefined;
- } else {
- ctxt[attr.name] = payload[0][j].value;
+ attrEntityName = currentAttr.entity_name;
}
}
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n procesing j %j attr %j ctxt %j \n newAttr %j ',
- j,
- attr,
- ctxt,
- newAttr
- );
- if (attr && attr.type) {
- if (attr.type !== 'GeoProperty') {
- newAttr.type = attr.type;
- }
+ //determine AttrEntityType for multientity
+ if (
+ currentAttr.entity_type !== null &&
+ currentAttr.entity_type !== undefined &&
+ currentAttr.entity_type !== '' &&
+ typeof currentAttr.entity_type === 'string'
+ ) {
+ attrEntityType = currentAttr.entity_type;
}
- // Apply expression
- if (attr && attr.expression) {
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n apply expression %j \n over ctxt %j \n and device %j',
- attr.expression,
- ctxt,
- typeInformation
- );
- let res = null;
+ //PRE POPULATE CONTEXT
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
+
+ //determine Value
+ if (currentAttr.value !== undefined) {
+ //static attributes already have a value
+ hitted = true;
+ valueExpression = currentAttr.value;
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
+ //we have got a meaure for that Attr
+ //actives ¿lazis?
+ hitted = true;
+ valueExpression = plainMeasures[currentAttr.object_id];
+ }
+ //remove measures that have been shadowed by an alias (some may be left and managed later)
+ //Maybe we must filter object_id if there is name == object_id
+ measures = measures.filter((item) => item.name !== currentAttr.object_id);
+
+ if (
+ currentAttr.expression !== undefined &&
+ currentAttr.expression !== '' &&
+ typeof currentAttr.expression === 'string'
+ ) {
try {
- if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
- res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
- } else {
- logger.warn(
- context,
- 'sendUpdateValueNgsiLD \n no context available for apply expression %j \n',
- attr.expression
- );
- res = newAttr.value; // keep newAttr value
+ hitted = true;
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
+ //we fallback to null if anything unexpecte happend
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
+ valueExpression = null;
}
} catch (e) {
- logger.error(context, 'sendUpdateValueNgsiLD \n apply expression exception %j \n', e);
- res = ctxt[attr.name]; // TBD: add reference to test
+ valueExpression = null;
}
-
- // jexl expression plugin
- newAttr.value = res;
-
logger.debug(
context,
- 'sendUpdateValueNgsiLD \n apply expression result %j \n newAttr %j',
- res,
- newAttr
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
+ currentAttr.name,
+ currentAttr.expression,
+ jexlctxt,
+ valueExpression
);
}
- // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
- if (attr && (attr.entity_type || attr.entity_name)) {
- // Create a newEntity for this attribute
- let newEntityName = null;
- if (attr.entity_name) {
- try {
- if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
- newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
- } else {
- logger.warn(
- context,
- 'sendUpdateValueNgsiLD \n MULTI no context available for apply expression %j \n',
- attr.entity_name
+ currentAttr.hitted = hitted;
+ currentAttr.value = valueExpression;
+
+ //store de New Attributte in entity data structure
+ if (hitted === true) {
+ if (entities[attrEntityName] === undefined) {
+ entities[attrEntityName] = {};
+ }
+ if (entities[attrEntityName][attrEntityType] === undefined) {
+ entities[attrEntityName][attrEntityType] = [];
+ }
+ //store de New Attributte
+ entities[attrEntityName][attrEntityType].push(currentAttr);
+ }
+
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
+ jexlctxt[currentAttr.name] = valueExpression;
+
+ // Expand metadata value expression
+ if (currentAttr.metadata) {
+ for (const metaKey in currentAttr.metadata) {
+ if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
+ const newAttrMeta = {};
+ if (currentAttr.metadata[metaKey].type) {
+ newAttrMeta.type = currentAttr.metadata[metaKey].type;
+ }
+ let metaValueExpression;
+ try {
+ metaValueExpression = jexlParser.applyExpression(
+ currentAttr.metadata[metaKey].expression,
+ jexlctxt,
+ typeInformation
);
- newEntityName = attr.entity_name;
+ //we fallback to null if anything unexpecte happend
+ if (
+ metaValueExpression === null ||
+ metaValueExpression === undefined ||
+ Number.isNaN(metaValueExpression)
+ ) {
+ metaValueExpression = null;
+ }
+ } catch (e) {
+ metaValueExpression = null;
}
- newEntityName = newEntityName ? newEntityName : attr.entity_name;
- } catch (e) {
- logger.error(context, 'sendUpdateValueNgsiLD \n MULTI apply expression exception %j \n', e);
- newEntityName = attr.entity_name;
+ newAttrMeta.value = metaValueExpression;
+ currentAttr.metadata[metaKey] = newAttrMeta;
}
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n MULTI apply expression %j \n result %j \n payload %j',
- attr.entity_name,
- newEntityName,
- payload
- );
}
+ }
+ }
- let newEntity = {
- id: newEntityName ? newEntityName : payload[0].id,
- type: attr.entity_type ? attr.entity_type : payload[0].type
- };
- // Check if there is already a newEntity created
- const alreadyEntity = payload.find((entity) => {
- return entity.id === newEntity.id && entity.type === newEntity.type;
- });
- if (alreadyEntity) {
- // Use alreadyEntity
- alreadyEntity[attr.name] = newAttr;
- } else {
- // Add newEntity to payload
- newEntity[attr.name] = newAttr;
- if (
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
- ? typeInformation.timestamp
- : config.getConfig().timestamp !== undefined
- ? config.getConfig().timestamp
- : timestampValue !== undefined
- ) {
- newEntity = NGSIv2.addTimestamp(newEntity, typeInformation.timezone, timestampValue);
- logger.debug(context, 'sendUpdateValueNgsiLD \n timestamped newEntity=%j', newEntity);
- }
- payload.push(newEntity);
- }
- if (attr && attr.name) {
- if (attr.name !== j) {
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n MULTI remove measure attr %j keep alias j %j from %j \n',
- j,
- attr,
- payload
- );
- delete payload[0][j];
- }
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
+ let explicit = false;
+ if (typeof typeInformation.explicitAttrs === 'string') {
+ try {
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
+ if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
}
- // if (attr && (attr.entity_type || attr.entity_name))
- } else {
- // Not a multientity attr
- if (attr && attr.name) {
- payload[0][attr.name] = newAttr;
- if (attr.name !== j) {
- delete payload[0][j]; // keep alias name, remove measure name
+ logger.debug(
+ context,
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
+ typeInformation.explicitAttrs,
+ jexlctxt,
+ explicit
+ );
+ } catch (e) {
+ // nothing to do: exception is already logged at info level
+ }
+ } else if (typeof typeInformation.explicitAttrs === 'boolean') {
+ explicit = typeInformation.explicitAttrs;
+ }
+
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
+ if (explicit === false && Object.keys(measures).length > 0) {
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
+ }
+
+ //PRE-PROCESSING FINISHED
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
+ //Get ready to build and send NGSI payload (entities-->payload)
+
+ for (const ename in entities) {
+ for (const etype in entities[ename]) {
+ const e = {};
+ e.id = String(ename);
+ e.type = String(etype);
+ const 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 (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
- if (extendedTime) {
- // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
- // but there is one in agents
- newAttr.value = extendedTime;
+
+ 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 = currentIsoDate;
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
+ } else {
+ timestamp.value = currentMoment
+ .tz(typeInformation.timezone)
+ .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
}
}
- if (j === constants.TIMESTAMP_ATTRIBUTE) {
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
- timestampValue = newAttr.value;
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n newAttr is TimeInstant and new payload=%j',
- payload
- );
+ //extract attributes
+ let isEmpty = true;
+ for (const attr of entities[ename][etype]) {
+ if (
+ attr.name !== 'id' &&
+ attr.name !== 'type' &&
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
+ (typeof explicit === 'boolean' || //true and false already handled
+ (explicit instanceof Array && //check the array version
+ (explicit.includes(attr.name) ||
+ explicit.some(
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
+ ))))
+ ) {
+ 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 };
}
}
- if (
- newAttr &&
- newAttr.metadata &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
- ) {
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
- );
- if (extendedTime) {
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
+ if (!isEmpty) {
+ if (mustInsertTimeInstant) {
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
}
+ payload.push(e);
}
}
- } // if (j !== 'id' || j !== 'type')
-
- // final attr loop
- logger.debug(
- context,
- 'sendUpdateValueNgsiLD \n after procesing attr %j \n current entity %j \n current payload=%j',
- j,
- currentEntity,
- payload
- );
- }
- // for attr loop
-
- // Add timestamp to paylaod
- if (
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
- ? typeInformation.timestamp
- : config.getConfig().timestamp !== undefined
- ? config.getConfig().timestamp
- : timestampValue !== undefined
- ) {
- if (timestampValue) {
- // timeInstant is provided as measure
- if (Object.keys(payload[0]).length > 1) {
- // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
- payload[0] = NGSIv2.addTimestamp(payload[0], typeInformation.timezone, timestampValue);
- }
- } else {
- // jshint maxdepth:5
- for (let n = 0; n < payload.length; n++) {
- if (!utils.isTimestampedNgsi2(payload[n])) {
- // legacy check needed?
- payload[n] = NGSIv2.addTimestamp(payload[n], typeInformation.timezone);
- // jshint maxdepth:5
- } else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
- // legacy check needed?
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
- callback(new errors.BadTimestamp(payload));
- return;
- }
- }
- }
- }
-
- logger.debug(context, 'sendUpdateValueNgsiLD \n ending payload=%j', payload);
-
- for (let m = 0; m < payload.length; m++) {
- for (const key in payload[m]) {
- // purge object_id from payload
- if (payload[m][key] && payload[m][key].object_id) {
- delete payload[m][key].object_id;
- }
}
- payload[m] = NGSIUtils.castJsonNativeAttributes(payload[m]); // native types
- }
- logger.debug(context, 'sendUpdateValueNgsiLD \n payload with native types and without object_id %j', payload);
+ } // end for (let measures of originMeasures)
+ const url = '/ngsi-ld/v1/entityOperations/upsert/?options=update';
+ const options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
options.json = payload;
+
try {
if (payload instanceof Array) {
options.json = _.map(options.json, formatAsNGSILD);
} else {
options.json.id = entityName;
- options.json.type = typeInformation.type;
+ options.json.type = originTypeInformation.type;
options.json = [formatAsNGSILD(options.json)];
}
} catch (error) {
- return callback(new errors.BadGeocoordinates(JSON.stringify(payload)));
+ return callback(new errors.BadGeocoordinates(JSON.stringify(payload), originTypeInformation));
}
- if (typeInformation.active) {
- addLinkedEntities(typeInformation, options.json);
+ if (originTypeInformation.active) {
+ addLinkedEntities(originTypeInformation, options.json);
}
// Prevent to update an entity with an empty payload
@@ -985,7 +874,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
logger.debug(context, 'Using the following NGSI LD request:\n\n%s\n\n', JSON.stringify(options, null, 4));
request(
options,
- generateNGSILDOperationHandler('update', entityName, typeInformation, token, options, callback)
+ generateNGSILDOperationHandler('update', entityName, originTypeInformation, token, options, callback)
);
} else {
logger.debug(
diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js
index a4b435e0c..2ec3e709f 100644
--- a/lib/services/ngsi/entities-NGSI-v2.js
+++ b/lib/services/ngsi/entities-NGSI-v2.js
@@ -30,13 +30,10 @@
const request = require('../../request-shim');
const alarms = require('../common/alarmManagement');
const errors = require('../../errors');
-const utils = require('../northBound/restUtils');
const pluginUtils = require('../../plugins/pluginUtils');
-const config = require('../../commonConfig');
const constants = require('../../constants');
const jexlParser = require('../../plugins/jexlParser');
const expressionPlugin = require('../../plugins/expressionPlugin');
-const compressTimestampPlugin = require('../../plugins/compressTimestamp');
const moment = require('moment-timezone');
const NGSIUtils = require('./ngsiUtils');
const logger = require('logops');
@@ -94,83 +91,6 @@ function formatGeoAttrs(attr) {
return obj;
}
-/**
- * Adds timestamp to ngsiv2 payload entities accoding to timezone, and an optional timestampvalue.
- *
- * @param {Object} payload NGSIv2 payload with one or more entities
- * @param String timezone TimeZone value (optional)
- * @param String timestampValue Timestamp value (optional). If not provided current timestamp is used
- * @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
- * @return {Object} NGSIv2 payload entities with timestamp
- */
-function addTimestampNgsi2(payload, timezone, timestampValue) {
- function addTimestampEntity(entity, timezone, timestampValue) {
- const timestamp = {
- type: constants.TIMESTAMP_TYPE_NGSI2
- };
-
- if (timestampValue) {
- timestamp.value = timestampValue;
- } else if (!timezone) {
- timestamp.value = new Date().toISOString();
- } else {
- timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
- }
-
- function addMetadata(attribute) {
- let timestampFound = false;
-
- if (!attribute.metadata) {
- attribute.metadata = {};
- }
-
- for (let i = 0; i < attribute.metadata.length; i++) {
- if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) {
- if (
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value
- ) {
- timestampFound = true;
- break;
- }
- }
- }
-
- if (!timestampFound) {
- attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
- }
-
- return attribute;
- }
- let keyCount = 0;
- for (const key in entity) {
- /* eslint-disable-next-line no-prototype-builtins */
- if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
- addMetadata(entity[key]);
- keyCount += 1;
- }
- }
- // Add timestamp just to entity with attrs: multientity plugin could
- // create empty entities just with id and type.
- if (keyCount > 0) {
- entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
- }
-
- return entity;
- }
-
- if (payload instanceof Array) {
- for (let i = 0; i < payload.length; i++) {
- if (!utils.isTimestampedNgsi2(payload[i])) {
- payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
- }
- }
-
- return payload;
- }
- return addTimestampEntity(payload, timezone, timestampValue);
-}
-
/**
* Generate an operation handler for NGSIv2-based operations (query and update). The handler takes care of identifiying
* the errors and calling the appropriate callback with a success or a failure depending on how the operation ended.
@@ -252,9 +172,9 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
}
if (errorField !== undefined) {
- callback(new errors.DeviceNotFound(entityName));
+ callback(new errors.DeviceNotFound(entityName, typeInformation));
} else {
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
+ callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
}
} else {
logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
@@ -262,7 +182,15 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
body = JSON.parse(body);
}
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body, response.statusCode));
+ callback(
+ new errors.EntityGenericError(
+ entityName,
+ typeInformation.type,
+ typeInformation,
+ body,
+ response.statusCode
+ )
+ );
}
};
}
@@ -304,7 +232,7 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
options.method = 'GET';
if (!typeInformation || !typeInformation.type) {
- callback(new errors.TypeNotFound(null, entityName));
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
return;
}
@@ -328,684 +256,499 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
* array should comply to the NGSIv2's attribute format.
*
* @param {String} entityName Name of the entity to register.
- * @param {Array} attributes Attribute array containing the values to update.
+ * @param {Array} measures measure array containing the values to update.
* @param {Object} typeInformation Configuration information for the device.
* @param {String} token User token to identify against the PEP Proxies (optional).
*/
-function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 called with: entityName=%s attributes=%j typeInformation=%j',
- entityName,
- attributes,
- typeInformation
- );
- const payload = {
- entities: [
- {
- id: entityName
- }
- ]
- };
-
- let url = '/v2/op/update';
-
- if (typeInformation && typeInformation.type) {
- payload.entities[0].type = typeInformation.type;
+function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, token, callback) {
+ //aux functions used to builf JEXL context.
+ //it returns a flat object from an Attr array
+ function reduceAttrToPlainObject(attrs, initObj = {}) {
+ if (attrs !== undefined && Array.isArray(attrs)) {
+ return attrs.reduce((result, item) => {
+ result[item.name] = item.value;
+ return result;
+ }, initObj);
+ } else {
+ return initObj;
+ }
}
-
- if (config.getConfig().appendMode === false) {
- payload.actionType = 'update';
- } else {
- payload.actionType = 'append';
+ //it returns a metadata object using the same structure described
+ // at https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support
+ function reduceMetadataAttrToPlainObject(attrs, initObj = {}) {
+ if (attrs !== undefined && Array.isArray(attrs)) {
+ return attrs.reduce((result, item) => {
+ if (result['metadata'] === undefined) {
+ result['metadata'] = {};
+ }
+ if (item.metadata !== undefined) {
+ result['metadata'][item.name] = {};
+ for (var meta in item.metadata) {
+ result['metadata'][item.name][meta] = item.metadata[meta].value;
+ }
+ }
+ return result;
+ }, initObj);
+ } else {
+ return initObj;
+ }
}
- let options = NGSIUtils.createRequestObject(url, typeInformation, token);
-
- if (typeInformation && typeInformation.staticAttributes) {
- attributes = attributes.concat(typeInformation.staticAttributes);
- }
+ //Make a clone and overwrite
+ let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
- if (!typeInformation || !typeInformation.type) {
- callback(new errors.TypeNotFound(null, entityName));
+ //Check mandatory information: type
+ if (!originTypeInformation || !originTypeInformation.type) {
+ callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
return;
}
- let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
- logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList);
- let measureAttrsForCtxt = [];
+ let payload = {}; //will store the final payload
+ let entities = {};
+ payload.actionType = 'append';
+ payload.entities = [];
- // Check explicitAttrs: adds all final needed attributes to payload
- if (
- typeInformation.explicitAttrs === undefined ||
- (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
- // explicitAttrs is not defined => default case: all attrs should be included
- ) {
- // This loop adds all measure values (attributes) into payload entities (entity[0])
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && attributes[i].type) {
- payload.entities[0][attributes[i].name] = {
- value: attributes[i].value,
- type: attributes[i].type
- };
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
- if (metadata) {
- payload.entities[0][attributes[i].name].metadata = metadata;
- }
- } else {
- callback(new errors.BadRequest(null, entityName));
- return;
+ const currentIsoDate = new Date().toISOString();
+ const currentMoment = moment(currentIsoDate);
+ //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
+ const mustInsertTimeInstant =
+ originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
+
+ // Check if measures is a single measure or a array of measures (a multimeasure)
+ if (originMeasures[0] && !originMeasures[0][0]) {
+ originMeasures = [originMeasures];
+ }
+ for (let measures of originMeasures) {
+ entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
+
+ let plainMeasures = null; //will contain measures POJO
+ //Make a clone and overwrite
+ let typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
+
+ //Rename all measures with matches with id and type to measure_id and measure_type
+ for (let measure of measures) {
+ if (measure.name === 'id' || measure.name === 'type') {
+ measure.name = constants.MEASURE + measure.name;
}
}
- logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload);
- // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
- if (typeInformation.active) {
- typeInformation.active.forEach((attr) => {
- if (attr.expression) {
- if (attr.object_id) {
- payload.entities[0][attr.object_id] = {
- value: payload.entities[0][attr.object_id]
- ? payload.entities[0][attr.object_id].value
- : undefined,
- type: attr.type,
- object_id: attr.object_id
- };
- } else {
- payload.entities[0][attr.name] = {
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
- type: attr.type
- };
- }
- }
- });
- }
- } else {
- let selectedAttrs = [];
- if (typeof typeInformation.explicitAttrs === 'string') {
- // explicitAttrs is a jexlExpression
- // This ctxt should include all possible attrs
- const attributesCtxt = [];
- if (typeInformation.static) {
- typeInformation.static.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- // Measures
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && attributes[i].type) {
- const measureAttr = {
- name: attributes[i].name,
- value: attributes[i].value,
- type: attributes[i].type
- };
- attributesCtxt.push(measureAttr);
- // check measureAttr by object_id -> if in active
- let j = 0;
- let found = false;
- while (j < typeInformation.active.length && !found) {
- if (attributes[i].name === typeInformation.active[j].object_id) {
- let measureAttrByObjectId = {
- name: typeInformation.active[j].name,
- value: attributes[i].value,
- type: attributes[i].type
- };
- attributesCtxt.push(measureAttrByObjectId);
- found = true;
- }
- j++;
- }
- }
- }
- // This context is just to calculate explicitAttrs when is an expression
-
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
- // typeInformation.active all attrs with expressions
- if (typeInformation.active) {
- typeInformation.active.forEach(function (att) {
- if (att.expression !== undefined) {
- let expandedAttr = {
- name: att.name,
- value: att.expression,
- type: att.type
- };
- attributesCtxt.push(expandedAttr);
- if (att.object_id !== undefined) {
- let expandedAttrByObjectId = {
- name: att.object_id,
- value: att.expression,
- type: att.type
- };
- attributesCtxt.push(expandedAttrByObjectId);
- }
- ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
- }
- });
- }
- // calculate expression for explicitAttrs
- try {
- logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt);
- let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
- if (res === true) {
- // like explicitAttrs == true
- // selectAttrs should be measures which are defined attributes
- typeInformation.active.forEach((attr) => {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- });
- } else if (res === false) {
- // like explicitAttrs == false
- // selectAttrs should be measures and defined attributes
- typeInformation.active.forEach((attr) => {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- });
- for (let i = 0; i < attributes.length; i++) {
- selectedAttrs.push(attributes[i].name);
- }
- } else {
- selectedAttrs = res; // TBD: Check ensure is an array of strings
- }
- if (selectedAttrs.length === 0) {
- // implies do nothing
- logger.info(
- context,
- 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j',
- typeInformation.explicitAttrs,
- ctxt
- );
- return callback(null);
- }
- } catch (e) {
- // nothing to do: exception is already logged at info level
- }
- typeInformation.active.forEach((attr) => {
- if (selectedAttrs.includes(attr.name)) {
- selectedAttrs.push(attr.object_id);
- }
- // Check if selectedAttrs includes an attribute with format {object_id: xxxx}
- if (selectedAttrs.includes({ object_id: attr.object_id })) {
- selectedAttrs.push(attr.object_id);
- }
- });
- } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
- // explicitAtts is true => Add just measures which are defined in active attributes
- // and active attributes with expressions
- // and TimeInstant
- selectedAttrs = ['TimeInstant'];
- typeInformation.active.forEach((attr) => {
- // Measures
- if (attr.expression !== undefined) {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- } else {
- // check if active attr is receiving a measure
- let i = 0;
- let found = false;
- while (i < attributes.length && !found) {
- if (attributes[i].name && attributes[i].type) {
- if (attributes[i].name === attr.object_id || attributes[i].name === attr.name) {
- selectedAttrs.push(attr.name);
- selectedAttrs.push(attr.object_id);
- found = true;
- }
- }
- i++;
- }
- }
- });
- }
- // This loop adds selected measured values (attributes) into payload entities (entity[0])
- for (let i = 0; i < attributes.length; i++) {
- if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
- const attr = typeInformation.active.find((obj) => {
- return obj.name === attributes[i].name;
- });
- payload.entities[0][attributes[i].name] = {
- value: attributes[i].value,
- type: attributes[i].type
- };
- // ensure payload has attr with proper object_id
- if (attr && attr.object_id) {
- payload.entities[0][attributes[i].name].object_id = attr.object_id;
- }
- const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
- if (metadata) {
- payload.entities[0][attributes[i].name].metadata = metadata;
- }
- } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
- const att = {
- name: attributes[i].name,
- type: attributes[i].type,
- value: attributes[i].value
- };
- measureAttrsForCtxt.push(att);
- }
+ //Make a copy of measures in an plain object: plainMeasures
+ plainMeasures = reduceAttrToPlainObject(measures);
+ //Build the initital JEXL Context
+ //All the measures (avoid references make another copy instead)
+ jexlctxt = reduceAttrToPlainObject(measures);
+ //All the static
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
+ //id type Service and Subservice
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
+ //metadata attributes
+ jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.active, jexlctxt);
+ //metadata static attributes
+ jexlctxt = reduceMetadataAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
+
+ //recover oldctxt
+ if (typeInformation.oldCtxt) {
+ jexlctxt.oldCtxt = typeInformation.oldCtxt;
}
logger.debug(
context,
- 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j',
- payload,
- selectedAttrs
+ 'sendUpdateValueNgsi2 loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
+ entityName,
+ plainMeasures,
+ typeInformation,
+ jexlctxt,
+ mustInsertTimeInstant
);
- let selectedAttrsByObjectId = selectedAttrs
- .filter((o) => o !== undefined && o.object_id)
- .map(function (el) {
- return el.object_id;
- });
-
- // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
- if (typeInformation.active) {
- typeInformation.active.forEach((attr) => {
- if (selectedAttrs.includes(attr.name)) {
- if (attr.object_id) {
- payload.entities[0][attr.object_id] = {
- value: payload.entities[0][attr.object_id]
- ? payload.entities[0][attr.object_id].value
- : payload.entities[0][attr.name]
- ? payload.entities[0][attr.name].value
- : undefined,
- type: attr.type,
- object_id: attr.object_id
- };
- } else {
- payload.entities[0][attr.name] = {
- value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
- type: attr.type
- };
- }
- } else if (attr.object_id !== undefined && selectedAttrsByObjectId.includes(attr.object_id)) {
- payload.entities[0][attr.object_id] = {
- value: payload.entities[0][attr.object_id]
- ? payload.entities[0][attr.object_id].value
- : payload.entities[0][attr.name]
- ? payload.entities[0][attr.name].value
- : undefined,
- type: attr.type,
- object_id: attr.object_id
- };
- }
- });
- }
- } // END check explicitAttrs
- logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload);
- const currentEntity = payload.entities[0];
+ //Now we can calculate the EntityName of primary entity
+ let entityNameCalc = null;
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
+ try {
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
+ entityNameCalc = expressionPlugin.applyExpression(
+ typeInformation.entityNameExp,
+ jexlctxt,
+ typeInformation
+ );
+ } catch (e) {
+ logger.debug(
+ context,
+ 'Error evaluating expression for entityName: %j with context: %j',
+ typeInformation.entityNameExp,
+ jexlctxt
+ );
+ }
+ }
- // Prepare attributes for expresionPlugin
- const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
+ entityName = entityNameCalc ? entityNameCalc : entityName;
+ //enrich JEXL context
+ jexlctxt['entity_name'] = entityName;
- // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
- let attsArrayFiltered = [];
- if (attsArray) {
- attsArrayFiltered = attsArray.filter((obj) => {
- return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
- });
- }
- let attributesCtxt = [...attsArrayFiltered]; // just copy
- if (typeInformation.static) {
- typeInformation.static.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- if (measureAttrsForCtxt) {
- measureAttrsForCtxt.forEach(function (att) {
- attributesCtxt.push(att);
- });
- }
- attributesCtxt = attributesCtxt.concat(idTypeSSSList);
- let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
- logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt);
-
- // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
- // attributes without expressions should be processed before
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity);
- if (typeInformation.active && typeInformation.active.length > 0) {
- for (const k in currentEntity) {
- typeInformation.active.forEach(function (att) {
- if (
- (att.object_id && att.object_id === k && att.expression) ||
- (att.name && att.name === k && att.expression)
- ) {
- const m = currentEntity[k];
- delete currentEntity[k];
- currentEntity[k] = m; // put into the end of currentEntity
- }
- });
+ let preprocessedAttr = [];
+ //Add Raw Static, Lazy, Command and Actives attr attributes
+ if (typeInformation && typeInformation.staticAttributes) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
+ }
+ if (typeInformation && typeInformation.lazy) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
+ }
+ if (typeInformation && typeInformation.active) {
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
}
- }
- logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
- let timestampValue = undefined;
- // Loop for each final attribute to apply alias, multientity and expressions
- for (const j in currentEntity) {
- // discard id and type
- if (j !== 'id' || j !== 'type') {
- // Apply Mapping Alias: object_id in attributes are in typeInformation.active
- let attr;
- let newAttr = payload.entities[0][j];
- if (typeInformation.active) {
- attr = typeInformation.active.find((obj) => {
- return obj.object_id === j;
- });
- }
- if (!attr) {
- if (typeInformation.lazy) {
- attr = typeInformation.lazy.find((obj) => {
- return obj.object_id === j;
- });
- }
- }
- if (!attr) {
- if (typeInformation.active) {
- attr = typeInformation.active.find((obj) => {
- return obj.name === j;
- });
- }
- }
- if (attr && attr.name) {
- if (['id', 'type'].includes(attr.name)) {
- // invalid mapping
+ //Proccess every proto Attribute to populate entities data steuture
+ entities[entityName] = {};
+ entities[entityName][typeInformation.type] = [];
+
+ for (let currentAttr of preprocessedAttr) {
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
+ let attrEntityName = entityName;
+ let attrEntityType = typeInformation.type;
+ let valueExpression = null;
+ //manage active attr without object__id (name by default)
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
+
+ //determine AttrEntityName for multientity
+ if (
+ currentAttr.entity_name !== null &&
+ currentAttr.entity_name !== undefined &&
+ currentAttr.entity_name !== '' &&
+ typeof currentAttr.entity_name == 'string'
+ ) {
+ try {
+ logger.debug(
+ context,
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
+ currentAttr.name,
+ currentAttr.entity_name,
+ jexlctxt
+ );
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
+ if (!attrEntityName) {
+ attrEntityName = currentAttr.entity_name;
+ }
+ } catch (e) {
logger.debug(
context,
- 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
- attr,
- newAttr
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
+ currentAttr.entity_name,
+ jexlctxt
);
- delete payload.entities[0][attr.object_id];
- attr = undefined; // stop processing attr
- newAttr = undefined;
- } else {
- ctxt[attr.name] = payload.entities[0][j].value;
+ attrEntityName = currentAttr.entity_name;
}
}
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
- j,
- attr,
- ctxt,
- newAttr
- );
- if (attr && attr.type) {
- newAttr.type = attr.type;
+
+ //determine AttrEntityType for multientity
+ if (
+ currentAttr.entity_type !== null &&
+ currentAttr.entity_type !== undefined &&
+ currentAttr.entity_type !== '' &&
+ typeof currentAttr.entity_type === 'string'
+ ) {
+ attrEntityType = currentAttr.entity_type;
}
- // Apply expression
- if (attr && attr.expression) {
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
- attr.expression,
- ctxt,
- typeInformation
- );
- let res = null;
+ //PRE POPULATE CONTEXT
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
+
+ //determine Value
+ if (currentAttr.value !== undefined) {
+ //static attributes already have a value
+ hitted = true;
+ valueExpression = currentAttr.value;
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
+ //we have got a meaure for that Attr
+ //actives ¿lazis?
+ hitted = true;
+ valueExpression = plainMeasures[currentAttr.object_id];
+ }
+ //remove measures that have been shadowed by an alias (some may be left and managed later)
+ //Maybe we must filter object_id if there is name == object_id
+ measures = measures.filter((item) => item.name !== currentAttr.object_id);
+
+ if (
+ currentAttr.expression !== undefined &&
+ currentAttr.expression !== '' &&
+ typeof currentAttr.expression == 'string'
+ ) {
try {
- if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
- res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
- if (
- // By default undefined is equivalent to null: should not progress
- (attr.skipValue === undefined && res === null) ||
- (attr.skipValue !== undefined && res === attr.skipValue)
- ) {
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j',
- attr.skipValue,
- res,
- attr.expression
- );
- delete payload.entities[0][j]; // remove measure attr
- attr = undefined; // stop process attr
- }
- } else {
- logger.info(
- context,
- 'sendUpdateValueNgsi2 no context available for apply expression=%j',
- attr.expression
- );
- res = newAttr.value; // keep newAttr value
+ hitted = true;
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
+ //we fallback to null if anything unexpecte happend
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
+ valueExpression = null;
}
} catch (e) {
- logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
- if (attr && attr.name) {
- res = ctxt[attr.name];
- }
+ valueExpression = null;
}
- // jexl expression plugin
- newAttr.value = res;
+ logger.debug(
+ context,
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
+ currentAttr.name,
+ currentAttr.expression,
+ jexlctxt,
+ valueExpression
+ );
+ }
- logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr);
- // update current context with value
- if (attr && attr.name && ctxt[attr.name] !== undefined) {
- ctxt[attr.name] = newAttr.value;
+ currentAttr.hitted = hitted;
+ currentAttr.value = valueExpression;
+
+ //store de New Attributte in entity data structure
+ if (hitted === true) {
+ if (entities[attrEntityName] === undefined) {
+ entities[attrEntityName] = {};
+ }
+ if (entities[attrEntityName][attrEntityType] === undefined) {
+ entities[attrEntityName][attrEntityType] = [];
}
+ //store de New Attributte
+ entities[attrEntityName][attrEntityType].push(currentAttr);
}
- // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
- if (attr && (attr.entity_type || attr.entity_name)) {
- // Create a newEntity for this attribute
- let newEntityName = null;
- if (attr.entity_name) {
- try {
- if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
- newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
- } else {
- logger.info(
- context,
- 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
- attr.entity_name
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
+ jexlctxt[currentAttr.name] = valueExpression;
+
+ // Expand metadata value expression
+ if (currentAttr.metadata) {
+ for (var metaKey in currentAttr.metadata) {
+ if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
+ let newAttrMeta = {};
+ if (currentAttr.metadata[metaKey].type) {
+ newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
+ }
+ let metaValueExpression;
+ try {
+ metaValueExpression = jexlParser.applyExpression(
+ currentAttr.metadata[metaKey].expression,
+ jexlctxt,
+ typeInformation
);
- newEntityName = attr.entity_name;
+ //we fallback to null if anything unexpecte happend
+ if (
+ metaValueExpression === null ||
+ metaValueExpression === undefined ||
+ Number.isNaN(metaValueExpression)
+ ) {
+ metaValueExpression = null;
+ }
+ } catch (e) {
+ metaValueExpression = null;
}
- newEntityName = newEntityName ? newEntityName : attr.entity_name;
- } catch (e) {
- logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
- newEntityName = attr.entity_name;
- }
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
- attr.entity_name,
- newEntityName,
- payload
- );
- }
+ newAttrMeta['value'] = metaValueExpression;
+ currentAttr.metadata[metaKey] = newAttrMeta;
- let newEntity = {
- id: newEntityName ? newEntityName : payload.entities[0].id,
- type: attr.entity_type ? attr.entity_type : payload.entities[0].type
- };
- // Check if there is already a newEntity created
- const alreadyEntity = payload.entities.find((entity) => {
- return entity.id === newEntity.id && entity.type === newEntity.type;
- });
- if (alreadyEntity) {
- // Use alreadyEntity
- alreadyEntity[attr.name] = newAttr;
- } else {
- // Add newEntity to payload.entities
- newEntity[attr.name] = newAttr;
- if (
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
- ? typeInformation.timestamp
- : config.getConfig().timestamp !== undefined
- ? config.getConfig().timestamp
- : timestampValue !== undefined
- ) {
- newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
- logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
+ //RE-Populate de JEXLcontext
+ // It is possible metadata is still not in ctxt
+ if (!jexlctxt.metadata) {
+ jexlctxt.metadata = {};
+ }
+ if (!jexlctxt.metadata[currentAttr.name]) {
+ jexlctxt.metadata[currentAttr.name] = {};
+ }
+ jexlctxt.metadata[currentAttr.name][currentAttr.metadata[metaKey]] = metaValueExpression;
}
- payload.entities.push(newEntity);
}
- if (attr && attr.name) {
- if (attr.name !== j) {
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j',
- j,
- attr,
- payload
- );
- delete payload.entities[0][j];
- }
+ }
+ }
+
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
+ let explicit = false;
+ if (typeof typeInformation.explicitAttrs === 'string') {
+ try {
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
+ if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
}
- // if (attr && (attr.entity_type || attr.entity_name))
- } else {
- // Not a multientity attr
- if (attr && attr.name) {
- payload.entities[0][attr.name] = newAttr;
- if (attr.name !== j) {
- delete payload.entities[0][j]; // keep alias name, remove measure name
+ logger.debug(
+ context,
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
+ typeInformation.explicitAttrs,
+ jexlctxt,
+ explicit
+ );
+ } catch (e) {
+ // nothing to do: exception is already logged at info level
+ }
+ } else if (typeof typeInformation.explicitAttrs == 'boolean') {
+ explicit = typeInformation.explicitAttrs;
+ }
+
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
+ if (explicit === false && Object.keys(measures).length > 0) {
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
+ }
+
+ //PRE-PROCESSING FINISHED
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
+ //Get ready to build and send NGSI payload (entities-->payload)
+
+ for (let ename in entities) {
+ for (let etype in entities[ename]) {
+ 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 (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
- if (extendedTime) {
- // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
- // but there is one in agents
- newAttr.value = extendedTime;
+
+ 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 = currentIsoDate;
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
+ } else {
+ timestamp.value = currentMoment
+ .tz(typeInformation.timezone)
+ .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
+ }
}
}
- if (j === constants.TIMESTAMP_ATTRIBUTE) {
- if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
- timestampValue = newAttr.value;
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
- payload
- );
+ //extract attributes
+ let isEmpty = true;
+ for (let attr of entities[ename][etype]) {
+ if (
+ attr.name !== 'id' &&
+ attr.name !== 'type' &&
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
+ (typeof explicit === 'boolean' || //true and false already handled
+ (explicit instanceof Array && //check the array version
+ (explicit.includes(attr.name) ||
+ explicit.some(
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
+ ))))
+ ) {
+ 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 };
}
}
- if (
- newAttr &&
- newAttr.metadata &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
- ) {
- const extendedTime = compressTimestampPlugin.fromBasicToExtended(
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
- );
- if (extendedTime) {
- newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
+ if (!isEmpty) {
+ if (mustInsertTimeInstant) {
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
}
- }
- }
- } // if (j !== 'id' || j !== 'type')
-
- // final attr loop
- logger.debug(
- context,
- 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j',
- j,
- currentEntity,
- payload
- );
- }
- // for attr loop
-
- // Add timestamp to paylaod
- if (
- 'timestamp' in typeInformation && typeInformation.timestamp !== undefined
- ? typeInformation.timestamp
- : config.getConfig().timestamp !== undefined
- ? config.getConfig().timestamp
- : timestampValue !== undefined
- ) {
- if (timestampValue) {
- // timeInstant is provided as measure
- if (payload.entities.length > 0) {
- for (let n = 0; n < payload.entities.length; n++) {
- // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
- payload.entities[n] = addTimestampNgsi2(
- payload.entities[n],
- typeInformation.timezone,
- timestampValue
- );
- }
- }
- } else {
- // jshint maxdepth:5
- for (let n = 0; n < payload.entities.length; n++) {
- if (!utils.isTimestampedNgsi2(payload.entities[n])) {
- // legacy check needed?
- payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone);
- // jshint maxdepth:5
- } else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) {
- // legacy check needed?
- logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0]));
- callback(new errors.BadTimestamp(payload.entities));
- return;
+ payload.entities.push(e);
}
}
}
+ //Add jexlctxt to typeInformation without oldCtxt
+ const oldCJexlctxt = { ...jexlctxt };
+ delete oldCJexlctxt.oldCtxt;
+ originTypeInformation['oldCtxt'] = oldCJexlctxt;
+ } // end for (let measures of originMeasures)
+ let url = '/v2/op/update';
+ if (originTypeInformation.useCBflowControl) {
+ url += '?options=flowControl';
}
- logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
-
- for (let m = 0; m < payload.entities.length; m++) {
- for (const key in payload.entities[m]) {
- // purge object_id from payload
- if (payload.entities[m][key] && payload.entities[m][key].object_id) {
- delete payload.entities[m][key].object_id;
- }
- }
- payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
- }
- logger.debug(context, 'sendUpdateValueNgsi2 payload with native types and without object_id=%j', payload);
-
+ let options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
options.json = payload;
- // Prevent to update an entity with an empty payload
+ // Prevent to update an entity with an empty payload: more than id and type
if (
Object.keys(options.json).length > 0 &&
(options.json.entities.length > 1 ||
- (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type
+ (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2))
) {
// Final check: (to keep tests unchanged) before do CB requests
// one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type
// multi entities -> request /v2/op/update
// Note that the options object is prepared for the second case (multi entity), so we "patch" it
// only in the first case
- if (options.json.entities.length === 1) {
+
+ //Multi: multientity (more than one name o more than one type at primary entity)
+ // of multimeasure (originMeasures is an array of more than one element)
+ let multi =
+ Object.keys(entities).length > 1 ||
+ Object.keys(entities[entityName]).length > 1 ||
+ originMeasures.length > 1;
+
+ if (!multi) {
// recreate options object to use single entity update
- url = '/v2/entities/' + entityName + '/attrs';
- if (typeInformation && typeInformation.type) {
- url += '?type=' + typeInformation.type;
+ url = '/v2/entities?options=upsert';
+ if (originTypeInformation.useCBflowControl) {
+ url += ',flowControl';
}
- options = NGSIUtils.createRequestObject(url, typeInformation, token);
- options.json = payload.entities[0];
- delete options.json.id;
- delete options.json.type;
- if (config.getConfig().appendMode === false) {
- options.method = 'PATCH';
- } else {
- options.method = 'POST';
+ options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
+ delete payload.actionType;
+
+ let entityAttrs = payload.entities[0];
+ const transformedObject = {};
+ for (let attrname in entityAttrs) {
+ let attr = entityAttrs[attrname];
+ transformedObject[attrname] = {
+ type: attr.type,
+ value: attr.value,
+ metadata: attr.metadata
+ };
}
- } // else: keep current options object created for a batch update
- logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
- logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4));
+ transformedObject.id = entityAttrs.id;
+ transformedObject.type = entityAttrs.type;
+ options.json = transformedObject;
+ options.method = 'POST';
+ } else if (payload.entities.every((entity) => 'TimeInstant' in entity)) {
+ // Try sort entities by TimeInstant
+ payload.entities.sort(
+ (a, b) => new Date(a.TimeInstant.value).getTime() - new Date(b.TimeInstant.value).getTime()
+ );
+ options.json = payload;
+ } else {
+ // keep current options object created for a batch update
+ logger.debug(
+ context,
+ "some entities lack the 'TimeInstant' key. Sorting is not feasible: %j ",
+ payload.entities
+ );
+ }
+
+ //Send the NGSI request
+ logger.debug(context, 'Updating device value in the Context Broker at: %j', options.url);
+ logger.debug(context, 'Using the following NGSI v2 request: %j', options);
+
request(
options,
- generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
+ generateNGSI2OperationHandler('update', entityName, originTypeInformation, token, options, callback)
);
} else {
logger.debug(
context,
- 'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
+ 'Not updating device value in the Context Broker at: %j, due to empty payload: %j',
options.url,
- JSON.stringify(options, null, 4)
+ options
);
callback(null);
}
}
exports.sendQueryValue = sendQueryValueNgsi2;
-exports.sendUpdateValue = function (entityName, attributes, typeInformation, token, callback) {
- NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, attributes, typeInformation, () => {
- return sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback);
+exports.sendUpdateValue = function (entityName, measures, typeInformation, token, callback) {
+ NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, measures, typeInformation, () => {
+ return sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback);
});
};
-exports.addTimestamp = addTimestampNgsi2;
+
exports.formatGeoAttrs = formatGeoAttrs;
diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js
index aea6ef802..f47496916 100644
--- a/lib/services/ngsi/ngsiService.js
+++ b/lib/services/ngsi/ngsiService.js
@@ -26,6 +26,8 @@
const async = require('async');
const apply = async.apply;
+const statsRegistry = require('../stats/statsRegistry');
+const deviceService = require('../devices/deviceService');
const intoTrans = require('../common/domain').intoTrans;
const fillService = require('./../common/domain').fillService;
const errors = require('../../errors');
@@ -67,7 +69,40 @@ function init() {
* @param {String} token User token to identify against the PEP Proxies (optional).
*/
function sendUpdateValue(entityName, attributes, typeInformation, token, callback) {
- entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, callback);
+ const newCallback = statsRegistry.withStats('updateEntityRequestsOk', 'updateEntityRequestsError', callback);
+ const additionalCallback = (data, next) => {
+ if (typeInformation.oldCtxt) {
+ logger.debug(context, 'StoreOldCtxt %j', typeInformation.oldCtxt);
+ deviceService.storeDeviceField('oldCtxt', typeInformation.oldCtxt, typeInformation, function () {
+ next(null, data);
+ });
+ } else {
+ next(null, data);
+ }
+ };
+ const wrappedNewCallback = (err, result) => {
+ if (err) {
+ newCallback(err);
+ } else {
+ additionalCallback(result, (additionalErr, modifiedResult) => {
+ if (additionalErr) {
+ newCallback(additionalErr);
+ }
+ newCallback(null, modifiedResult || result);
+ });
+ }
+ };
+ // check config about store last measure
+ if (typeInformation.storeLastMeasure) {
+ logger.debug(context, 'StoreLastMeasure for %j', typeInformation);
+ let originalMeasure = typeInformation.originalMeasure ? typeInformation.originalMeasure : null;
+ deviceService.storeDeviceField('lastMeasure', originalMeasure, typeInformation, function () {
+ delete typeInformation.originalMeasure;
+ return entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, wrappedNewCallback);
+ });
+ } else {
+ entityHandler.sendUpdateValue(entityName, attributes, typeInformation, token, wrappedNewCallback);
+ }
}
/**
@@ -133,7 +168,21 @@ function executeWithDeviceInformation(operationFunction) {
deviceInformation
);
const currentType = type ? type : deviceInformation.type;
- config.getGroupRegistry().getTypeSilently(currentType, function (error, deviceGroup) {
+
+ function getConfiguration(currentType, deviceInformation, callback) {
+ config
+ .getGroupRegistry()
+ .getSilently(deviceInformation.resource, deviceInformation.apikey, function (error, deviceGroup) {
+ if (error) {
+ config.getGroupRegistry().getTypeSilently(currentType, function (error, deviceGroup) {
+ callback(error, deviceGroup);
+ });
+ } else {
+ callback(null, deviceGroup);
+ }
+ });
+ }
+ getConfiguration(currentType, deviceInformation, function (error, deviceGroup) {
let typeInformation;
const configDeviceInfo = config.getConfig().types[currentType];
if (error) {
@@ -144,9 +193,9 @@ function executeWithDeviceInformation(operationFunction) {
// For preregistered devices, augment the existing deviceInformation with selected attributes.
if (!callback) {
callback = deviceInformation;
- typeInformation = deviceGroup || configDeviceInfo;
+ typeInformation = deviceGroup || { ...config.getConfigForTypeInformation(), ...configDeviceInfo };
} else {
- typeInformation = deviceInformation;
+ typeInformation = { ...config.getConfigForTypeInformation(), ...deviceInformation };
attributeList.forEach((key) => {
typeInformation[key] =
typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key];
@@ -236,7 +285,7 @@ function setCommandResult(
if (commandInfo.length === 1) {
exports.update(entityName, typeInformation.type, apikey, attributes, typeInformation, callback);
} else {
- callback(new errors.CommandNotFound(commandName));
+ callback(new errors.CommandNotFound(commandName, typeInformation));
}
});
}
diff --git a/lib/services/ngsi/ngsiUtils.js b/lib/services/ngsi/ngsiUtils.js
index e6e6ee661..a3fbeb60f 100644
--- a/lib/services/ngsi/ngsiUtils.js
+++ b/lib/services/ngsi/ngsiUtils.js
@@ -89,35 +89,6 @@ function isFloat(value) {
return !isNaN(value) && value.toString().indexOf('.') !== -1;
}
-/**
- * It casts attribute values which are reported using JSON parsing
- *
- * @param {String} payload The payload
- * @return {String} New payload where attributes's values are casted to the corresponding JSON values
- */
-function castJsonNativeAttributes(payload) {
- if (!config.getConfig().autocast) {
- return payload;
- }
-
- for (const key in payload) {
- if (
- /* eslint-disable-next-line no-prototype-builtins */
- payload.hasOwnProperty(key) &&
- payload[key].value &&
- typeof payload[key].value === 'string'
- ) {
- try {
- const parsedValue = JSON.parse(payload[key].value);
- payload[key].value = parsedValue;
- } catch (e) {
- // Keep value as is
- }
- }
- }
- return payload;
-}
-
/**
* Create the request object used to communicate with the Context Broker, adding security and service information.
*
@@ -230,7 +201,6 @@ exports.createRequestObject = createRequestObject;
exports.applyMiddlewares = applyMiddlewares;
exports.getMetaData = getMetaData;
exports.getLngLats = getLngLats;
-exports.castJsonNativeAttributes = castJsonNativeAttributes;
exports.isFloat = isFloat;
exports.updateMiddleware = updateMiddleware;
exports.queryMiddleware = queryMiddleware;
diff --git a/lib/services/ngsi/subscription-NGSI-LD.js b/lib/services/ngsi/subscription-NGSI-LD.js
index 5ba7f71d1..977c4aee1 100644
--- a/lib/services/ngsi/subscription-NGSI-LD.js
+++ b/lib/services/ngsi/subscription-NGSI-LD.js
@@ -62,6 +62,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
new errors.EntityGenericError(
device.name,
device.type,
+ device,
{
details: body
},
@@ -88,7 +89,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
triggers
});
- config.getRegistry().update(device, callback);
+ config.getRegistry().update(device, device, callback);
} else {
callback(null, response.headers.location);
}
@@ -104,7 +105,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
* @param {Object} triggers Array with the names of the attributes that would trigger the subscription
* @param {Object} content Array with the names of the attributes to retrieve in the notification.
*/
-function subscribeNgsiLD(device, triggers, content, callback) {
+function subscribeNgsiLD(device, triggers, content, attrsFormat, callback) {
const options = {
method: 'POST',
headers: {
@@ -131,7 +132,7 @@ function subscribeNgsiLD(device, triggers, content, callback) {
accept: 'application/json'
},
attributes: content || [],
- format: 'normalized'
+ format: attrsFormat
}
}
};
@@ -183,6 +184,7 @@ function createUnsubscribeHandlerNgsiLD(device, id, callback) {
new errors.EntityGenericError(
device.name,
device.type,
+ device,
{
details: body
},
@@ -201,7 +203,7 @@ function createUnsubscribeHandlerNgsiLD(device, id, callback) {
callback(new errors.BadRequest(body.orionError.details));
} else {
device.subscriptions.splice(device.subscriptions.indexOf(id), 1);
- config.getRegistry().update(device, callback);
+ config.getRegistry().update(device, device, callback);
}
};
}
diff --git a/lib/services/ngsi/subscription-NGSI-mixed.js b/lib/services/ngsi/subscription-NGSI-mixed.js
index 4e0b4ed4f..fa0b41a2f 100644
--- a/lib/services/ngsi/subscription-NGSI-mixed.js
+++ b/lib/services/ngsi/subscription-NGSI-mixed.js
@@ -36,11 +36,11 @@ const subscriptionHandlerV2 = require('./subscription-NGSI-v2');
* @param {Object} triggers Array with the names of the attributes that would trigger the subscription
* @param {Object} content Array with the names of the attributes to retrieve in the notification.
*/
-function subscribeNgsiMixed(device, triggers, content, callback) {
+function subscribeNgsiMixed(device, triggers, content, attrsFormat, callback) {
if (config.checkNgsiLD(device)) {
- subscriptionHandlerLD.subscribe(device, triggers, content, callback);
+ subscriptionHandlerLD.subscribe(device, triggers, content, attrsFormat, callback);
} else {
- subscriptionHandlerV2.subscribe(device, triggers, content, callback);
+ subscriptionHandlerV2.subscribe(device, triggers, content, attrsFormat, callback);
}
}
diff --git a/lib/services/ngsi/subscription-NGSI-v2.js b/lib/services/ngsi/subscription-NGSI-v2.js
index aa31bede2..53969a5cd 100644
--- a/lib/services/ngsi/subscription-NGSI-v2.js
+++ b/lib/services/ngsi/subscription-NGSI-v2.js
@@ -64,6 +64,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
new errors.EntityGenericError(
device.name,
device.type,
+ device,
{
details: body
},
@@ -90,9 +91,11 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
triggers
});
- config.getRegistry().update(device, callback);
+ config.getRegistry().update(device, device, callback);
} else {
- callback(null, response.headers.location);
+ callback(null, {
+ subscriptionId: response.headers.location.substr(response.headers.location.lastIndexOf('/') + 1)
+ });
}
};
}
@@ -106,7 +109,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
* @param {Object} triggers Array with the names of the attributes that would trigger the subscription
* @param {Object} content Array with the names of the attributes to retrieve in the notification.
*/
-function subscribeNgsi2(device, triggers, content, callback) {
+function subscribeNgsi2(device, triggers, content, attrsFormat, callback) {
const options = {
method: 'POST',
headers: {
@@ -114,6 +117,7 @@ function subscribeNgsi2(device, triggers, content, callback) {
'fiware-servicepath': device.subservice
},
json: {
+ description: 'Managed by IOTA: ' + device.name + ' ' + device.type + ' ' + triggers.join(','),
subject: {
entities: [
{
@@ -131,7 +135,8 @@ function subscribeNgsi2(device, triggers, content, callback) {
url: config.getConfig().providerUrl + '/notify'
},
attrs: content || [],
- attrsFormat: 'normalized'
+ attrsFormat: attrsFormat,
+ onlyChangedAttrs: true
}
}
};
@@ -166,7 +171,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
if (error) {
logger.debug(
context,
- 'Transport error found subscribing device with id [%s] to entity [%s]',
+ 'Transport error found unsubscribing device with id [%s] to entity [%s]',
device.id,
device.name
);
@@ -175,7 +180,9 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
} else if (response.statusCode !== 204) {
logger.debug(
context,
- 'Unknown error subscribing device with id [%s] to entity [%s]: $s',
+ 'Unknown error unsubscribing device with id [%s] to entity [%s]: %s',
+ device.id,
+ device.name,
response.statusCode
);
@@ -183,6 +190,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
new errors.EntityGenericError(
device.name,
device.type,
+ device,
{
details: body
},
@@ -200,8 +208,16 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
callback(new errors.BadRequest(body.orionError.details));
} else {
- device.subscriptions.splice(device.subscriptions.indexOf(id), 1);
- config.getRegistry().update(device, callback);
+ logger.debug(context, 'removing subscription %s from device %j', id, device);
+ if (device.subscriptions) {
+ // check before try to eliminates
+ const index = device.subscriptions.indexOf(id);
+ if (index !== -1) {
+ // only eliminates if exits
+ device.subscriptions.splice(index, 1);
+ }
+ }
+ config.getRegistry().update(device, device, callback);
}
};
}
diff --git a/lib/services/ngsi/subscriptionService.js b/lib/services/ngsi/subscriptionService.js
index 47c781456..5eebfbbfe 100644
--- a/lib/services/ngsi/subscriptionService.js
+++ b/lib/services/ngsi/subscriptionService.js
@@ -57,8 +57,8 @@ function init() {
* @param {Object} triggers Array with the names of the attributes that would trigger the subscription
* @param {Object} content Array with the names of the attributes to retrieve in the notification.
*/
-function subscribe(device, triggers, content, callback) {
- subscriptionHandler.subscribe(device, triggers, content, callback);
+function subscribe(device, triggers, content, attrsFormat, callback) {
+ subscriptionHandler.subscribe(device, triggers, content, attrsFormat, callback);
}
/**
diff --git a/lib/services/northBound/contextServer-NGSI-LD.js b/lib/services/northBound/contextServer-NGSI-LD.js
index a1016b9b8..f391b6710 100644
--- a/lib/services/northBound/contextServer-NGSI-LD.js
+++ b/lib/services/northBound/contextServer-NGSI-LD.js
@@ -46,43 +46,38 @@ const config = require('../../commonConfig');
const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
-const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
-
+const queryPaths = ['/ngsi-ld/v1/entities/:entity', '/ngsi-ld/v1/entities'];
/**
* Replacement of NGSI-LD Null placeholders with real null values
*
*/
-function replaceNGSILDNull(payload){
- Object.keys(payload).forEach((key) =>{
+function replaceNGSILDNull(payload) {
+ Object.keys(payload).forEach((key) => {
const value = payload[key];
- if ( value === constants.NGSI_LD_NULL){
- payload[key] = null;
- } else if (typeof value === 'object' &&
- !Array.isArray(value) &&
- value !== null){
+ if (value === constants.NGSI_LD_NULL) {
+ payload[key] = null;
+ } else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
payload[key] = replaceNGSILDNull(payload[key]);
}
- })
- return payload;
+ });
+ return payload;
}
/**
* Check to see if the payload or its subattributes contain null values
*
*/
-function containsNulls(payload, result){
- Object.keys(payload).forEach((key) =>{
+function containsNulls(payload, result) {
+ Object.keys(payload).forEach((key) => {
const value = payload[key];
- if ( value === null){
- result.nulls = true;
- } else if (typeof value === 'object' &&
- !Array.isArray(value) &&
- value !== null){
+ if (value === null) {
+ result.nulls = true;
+ } else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
containsNulls(payload[key], result);
}
- })
- return result;
+ });
+ return result;
}
/**
@@ -90,23 +85,23 @@ function containsNulls(payload, result){
* to real nulls and checks for the presence of null and datasetId
*
*/
-function preprocessNGSILD(req, res, next){
- res.locals.hasDatasetId = false;
- const payload = req.body
- if (payload && typeof payload === 'object'){
- Object.keys(payload).forEach((key) =>{
- if (_.isArray(payload[key])){
+function preprocessNGSILD(req, res, next) {
+ res.locals.hasDatasetId = false;
+ const payload = req.body;
+ if (payload && typeof payload === 'object') {
+ Object.keys(payload).forEach((key) => {
+ if (_.isArray(payload[key])) {
payload[key].forEach((obj) => {
- if (obj.datasetId){
+ if (obj.datasetId) {
res.locals.hasDatasetId = true;
}
});
- } else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none'){
- res.locals.hasDatasetId = true;
- }
+ } else if (payload[key] && payload[key].datasetId && payload[key].datasetId !== '@none') {
+ res.locals.hasDatasetId = true;
+ }
});
req.body = replaceNGSILDNull(payload);
- const result = { nulls: false }
+ const result = { nulls: false };
containsNulls(payload, result);
res.locals.hasNulls = result.nulls;
}
@@ -124,16 +119,23 @@ function preprocessNGSILD(req, res, next){
function validateNGSILD(supportNull, supportDatasetId) {
return function validate(req, res, next) {
if (!supportNull && res.locals.hasNulls) {
- next(new errors.BadRequest('NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'));
+ next(
+ new errors.BadRequest(
+ 'NGSI-LD Null found within the payload. This IoT Agent does not support nulls for this endpoint.'
+ )
+ );
} else if (!supportDatasetId && res.locals.hasDatasetId) {
- next(new errors.BadRequest('datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'));
+ next(
+ new errors.BadRequest(
+ 'datasetId found within the payload. This IoT Agent does not support multi-attribute requests.'
+ )
+ );
} else {
next();
}
};
}
-
/**
* Extract metadata attributes from input.
*
@@ -413,34 +415,31 @@ function defaultQueryHandlerNgsiLD(id, type, service, subservice, attributes, ca
* @param {Object} req Update request to generate Actions from
*/
function generateMergePatchActionNgsiLD(req, callback) {
-
const entityId = req.params.entity;
-
- function addAttributes(deviceData, body, attributes){
+ function addAttributes(deviceData, body, attributes) {
const keys = Object.keys(body);
for (const j in deviceData) {
if (keys.includes(deviceData[j].name)) {
- const obj = body[deviceData[j].name]
- if ( obj === null) {
+ const obj = body[deviceData[j].name];
+ if (obj === null) {
attributes.push({
type: deviceData[j].type,
value: null,
name: deviceData[j].name
});
} else {
- attributes.push({
+ attributes.push({
type: deviceData[j].type,
value: obj.value,
name: deviceData[j].name
});
}
- }
+ }
}
return attributes;
}
-
deviceService.getDeviceByName(
entityId,
@@ -451,8 +450,8 @@ function generateMergePatchActionNgsiLD(req, callback) {
callback(error);
} else {
const attributes = [];
- addAttributes(deviceObj.commands, req.body, attributes)
- addAttributes(deviceObj.lazy, req.body, attributes)
+ addAttributes(deviceObj.commands, req.body, attributes);
+ addAttributes(deviceObj.lazy, req.body, attributes);
const executeMergePatchHandler = apply(
contextServerUtils.mergePatchHandler,
entityId,
@@ -461,10 +460,7 @@ function generateMergePatchActionNgsiLD(req, callback) {
contextServerUtils.getLDPath(req),
attributes
);
- async.waterfall(
- [executeMergePatchHandler],
- callback()
- );
+ async.waterfall([executeMergePatchHandler], callback());
}
}
);
@@ -477,7 +473,6 @@ function generateMergePatchActionNgsiLD(req, callback) {
* @param {Object} res Response that will be sent.
*/
function handleMergePatchNgsiLD(req, res, next) {
-
function handleMergePatchRequest(error, result) {
if (error) {
logger.debug(context, 'There was an error handling the merge-patch: %s.', error);
@@ -492,15 +487,15 @@ function handleMergePatchNgsiLD(req, res, next) {
if ((req.is('json') || req.is('application/ld+json')) === false) {
return handleMergePatchRequest(new errors.UnsupportedContentType(req.header('content-type')));
}
-
+
if (req.body) {
logger.debug(context, JSON.stringify(req.body, null, 4));
}
- if (contextServerUtils.mergePatchHandler){
+ if (contextServerUtils.mergePatchHandler) {
generateMergePatchActionNgsiLD(req, handleMergePatchRequest);
} else {
- return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path))
+ return handleMergePatchRequest(new errors.MethodNotSupported(req.method, req.path));
}
}
@@ -511,6 +506,9 @@ function handleMergePatchNgsiLD(req, res, next) {
* @param {Object} res Response that will be sent.
*/
function handleQueryNgsiLD(req, res, next) {
+ const returnAsArray = req.query.id;
+ req.params.entity = req.params.entity || req.query.id || '';
+
function getName(element) {
return element.name;
}
@@ -610,7 +608,7 @@ function handleQueryNgsiLD(req, res, next) {
getFunction(function handleFindDevice(error, innerDevice) {
let deviceList = [];
if (!innerDevice) {
- return callback(new errors.DeviceNotFound(contextEntity.id));
+ return callback(new errors.DeviceNotFound(contextEntity.id, contextEntity));
}
if (innerDevice.count) {
@@ -646,7 +644,7 @@ function handleQueryNgsiLD(req, res, next) {
next(error);
} else {
logger.debug(context, 'Query from [%s] handled successfully.', req.get('host'));
- res.status(200).json(result);
+ res.status(200).json(returnAsArray ? [result] : result);
}
}
@@ -689,7 +687,7 @@ function ErrorHandlingNgsiLD(action) {
error: error.name,
description: error.message.replace(/[<>\"\'=;\(\)]/g, '')
});
- }
+ };
}
/**
@@ -798,7 +796,6 @@ function loadUnsupportedEndpointsNGSILD(router) {
const unsupportedEndpoint = function (req, res) {
return res.status(501).send(new errors.MethodNotSupported(req.method, req.path));
};
- router.get('/ngsi-ld/v1/entities', unsupportedEndpoint);
router.post('/ngsi-ld/v1/entities', unsupportedEndpoint);
router.delete('/ngsi-ld/v1/entities/:entity', unsupportedEndpoint);
router.delete('/ngsi-ld/v1/entities/:entity/attrs/:attr', unsupportedEndpoint);
@@ -812,7 +809,7 @@ function loadUnsupportedEndpointsNGSILD(router) {
function loadContextRoutesNGSILD(router) {
// In a more evolved implementation, more endpoints could be added to queryPathsNgsi2
// according to https://www.etsi.org/standards-search#page=1&search=GS%20CIM%20009
-
+
const support = config.getConfig().server.ldSupport;
let i;
@@ -832,7 +829,7 @@ function loadContextRoutesNGSILD(router) {
router.patch('/ngsi-ld/v1/entities/:entity', [
preprocessNGSILD,
validateNGSILD(support.null, support.datasetId),
- handleMergePatchNgsiLD,
+ handleMergePatchNgsiLD,
ErrorHandlingNgsiLD('Merge-Patch')
]);
diff --git a/lib/services/northBound/contextServer-NGSI-v2.js b/lib/services/northBound/contextServer-NGSI-v2.js
index 360def2ea..d9c6a5e51 100644
--- a/lib/services/northBound/contextServer-NGSI-v2.js
+++ b/lib/services/northBound/contextServer-NGSI-v2.js
@@ -292,9 +292,20 @@ function handleNotificationNgsi2(req, res, next) {
}
}
}
+ logger.debug(context, 'extracted atts %j from dataElement %j', atts, dataElement);
+ // FIXME: command execution entity for advancedNotification is still not implemented
+ var id = dataElement.id;
+ var type = dataElement.type;
+
+ if (dataElement.targetEntityId && dataElement.targetEntityId.value) {
+ id = dataElement.targetEntityId.value;
+ }
+ if (dataElement.targetEntityType && dataElement.targetEntityType.value) {
+ type = dataElement.targetEntityType.value;
+ }
deviceService.getDeviceByNameAndType(
- dataElement.id,
- dataElement.type,
+ id,
+ type,
req.headers['fiware-service'],
req.headers['fiware-servicepath'],
function (error, device) {
@@ -336,7 +347,7 @@ function handleNotificationNgsi2(req, res, next) {
logger.error(context, 'Error found when processing notification: %j', error);
next(error);
} else {
- res.status(200).json({});
+ res.status(204).json();
}
}
@@ -503,7 +514,7 @@ function handleQueryNgsi2(req, res, next) {
getFunction(function handleFindDevice(error, innerDevice) {
let deviceList = [];
if (!innerDevice) {
- return callback(new errors.DeviceNotFound(contextEntity.id));
+ return callback(new errors.DeviceNotFound(contextEntity.id), contextEntity);
}
if (innerDevice.count) {
diff --git a/lib/services/northBound/contextServer.js b/lib/services/northBound/contextServer.js
index f328e6730..f901de3df 100644
--- a/lib/services/northBound/contextServer.js
+++ b/lib/services/northBound/contextServer.js
@@ -30,7 +30,7 @@ const context = {
op: 'IoTAgentNGSI.ContextServer'
};
const contextServerUtils = require('./contextServerUtils');
-
+const executeUpdateSideEffects = contextServerUtils.executeUpdateSideEffects;
let contextServerHandler;
/**
@@ -157,4 +157,5 @@ exports.setCommandHandler = intoTrans(context, setCommandHandler);
exports.setNotificationHandler = intoTrans(context, setNotificationHandler);
exports.addNotificationMiddleware = intoTrans(context, addNotificationMiddleware);
exports.setQueryHandler = intoTrans(context, setQueryHandler);
+exports.executeUpdateSideEffects = intoTrans(context, executeUpdateSideEffects);
exports.init = init;
diff --git a/lib/services/northBound/deviceGroupAdministrationServer.js b/lib/services/northBound/deviceGroupAdministrationServer.js
index 40829394f..99fc2ae40 100644
--- a/lib/services/northBound/deviceGroupAdministrationServer.js
+++ b/lib/services/northBound/deviceGroupAdministrationServer.js
@@ -29,13 +29,19 @@ const restUtils = require('./restUtils');
const groupService = require('./../groups/groupService');
const async = require('async');
const apply = async.apply;
-const templateGroup = require('../../templates/deviceGroup.json');
+const templateGroupService = require('../../templates/deviceGroup.json');
let configurationHandler;
let removeConfigurationHandler;
let configurationMiddleware = [];
const _ = require('underscore');
const mandatoryHeaders = ['fiware-service', 'fiware-servicepath'];
const mandatoryParameters = ['resource', 'apikey'];
+const constants = require('../../constants');
+
+// Create new template for configuration groups replacing services by CONFIGGROUP_TERM
+const templateGroup = JSON.parse(JSON.stringify(templateGroupService));
+templateGroup.properties[constants.CONFIGGROUP_TERM] = templateGroup.properties.services;
+delete templateGroup.properties.services;
const apiToInternal = {
entity_type: 'type',
@@ -118,6 +124,8 @@ function applyConfigurationMiddlewares(newConfiguration, callback) {
* @param {Function} next Invokes the next middleware in the chain.
*/
function handleCreateDeviceGroup(req, res, next) {
+ req.body = checkAndModifyGroupRecv(req._parsedUrl.pathname, req.body); // #FIXME1649: This line should be removed when /iot/services support is removed
+
for (let i = 0; i < req.body.services.length; i++) {
req.body.services[i] = applyMap(apiToInternal, req.body.services[i]);
req.body.services[i].service = req.headers['fiware-service'];
@@ -161,8 +169,7 @@ function handleListDeviceGroups(req, res, next) {
} else {
translatedGroup = applyMap(internalToApi, group);
}
-
- res.status(200).send(translatedGroup);
+ res.status(200).send(checkAndModifyGroupResp(req._parsedUrl.pathname, translatedGroup)); // #FIXME1649: Response should not use checkAndModifyGroupResp function when /iot/services support is removed
}
};
@@ -263,25 +270,37 @@ function handleDeleteDeviceGroups(req, res, next) {
*
* @param {Object} router Express request router object.
*/
+// #FIXME1649: Routes using /iot/services path should be removed when support for it is removed
function loadContextRoutes(router) {
router.post(
'/iot/services',
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
+ restUtils.checkBody(templateGroupService),
+ handleCreateDeviceGroup
+ );
+
+ router.post(
+ '/iot/groups',
+ restUtils.checkRequestAttributes('headers', mandatoryHeaders),
restUtils.checkBody(templateGroup),
handleCreateDeviceGroup
);
- router.get('/iot/services', restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleListDeviceGroups);
+ router.get(
+ ['/iot/services', '/iot/groups'],
+ restUtils.checkRequestAttributes('headers', mandatoryHeaders),
+ handleListDeviceGroups
+ );
router.put(
- '/iot/services',
+ ['/iot/services', '/iot/groups'],
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
restUtils.checkRequestAttributes('query', mandatoryParameters),
handleModifyDeviceGroups
);
router.delete(
- '/iot/services',
+ ['/iot/services', '/iot/groups'],
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
restUtils.checkRequestAttributes('query', mandatoryParameters),
handleDeleteDeviceGroups
@@ -306,6 +325,23 @@ function clear(callback) {
callback();
}
+// #FIXME1649: This function should be removed when /iot/services support is removed
+function checkAndModifyGroupResp(route, payload) {
+ if (route === '/iot/groups') {
+ payload['groups'] = payload['services'];
+ delete payload['services'];
+ }
+ return payload;
+}
+// #FIXME1649: This function should be removed when /iot/services support is removed
+function checkAndModifyGroupRecv(route, payload) {
+ if (route === '/iot/groups') {
+ payload['services'] = payload['groups'];
+ delete payload['groups'];
+ }
+ return payload;
+}
+
exports.loadContextRoutes = loadContextRoutes;
exports.setConfigurationHandler = setConfigurationHandler;
exports.setRemoveConfigurationHandler = setRemoveConfigurationHandler;
diff --git a/lib/services/northBound/deviceProvisioningServer.js b/lib/services/northBound/deviceProvisioningServer.js
index 3e995218a..47c0cbbcb 100644
--- a/lib/services/northBound/deviceProvisioningServer.js
+++ b/lib/services/northBound/deviceProvisioningServer.js
@@ -62,9 +62,13 @@ const provisioningAPITranslation = {
static_attributes: 'staticAttributes',
autoprovision: 'autoprovision',
explicitAttrs: 'explicitAttrs',
- expressionLanguage: 'expressionLanguage',
ngsiVersion: 'ngsiVersion',
- entityNameExp: 'entityNameExp'
+ entityNameExp: 'entityNameExp',
+ payloadType: 'payloadType',
+ useCBflowControl: 'useCBflowControl',
+ storeLastMeasure: 'storeLastMeasure',
+ lastMeasure: 'lastMeasure',
+ cmdMode: 'cmdMode'
};
/**
@@ -142,8 +146,12 @@ function handleProvision(req, res, next) {
internalId: null,
autoprovision: body.autoprovision,
explicitAttrs: body.explicitAttrs,
- expressionLanguage: body.expressionLanguage,
- ngsiVersion: body.ngsiVersion
+ ngsiVersion: body.ngsiVersion,
+ payloadType: body.payloadType,
+ useCBflowControl: body.useCBflowControl,
+ storeLastMeasure: body.storeLastMeasure,
+ lastMeasure: body.lastMeasure,
+ cmdMode: body.cmdMode
});
}
@@ -182,7 +190,6 @@ function attributeToProvisioningAPIFormat(attribute) {
type: attribute.type,
expression: attribute.expression,
skipValue: attribute.skipValue,
- reverse: attribute.reverse,
entity_name: attribute.entity_name,
entity_type: attribute.entity_type,
mqtt: attribute.mqtt,
@@ -219,9 +226,13 @@ function toProvisioningAPIFormat(device) {
internal_attributes: device.internalAttributes,
protocol: device.protocol,
autoprovision: device.autoprovision,
- expressionLanguage: device.expressionLanguage,
explicitAttrs: device.explicitAttrs,
- ngsiVersion: device.ngsiVersion
+ ngsiVersion: device.ngsiVersion,
+ payloadType: device.payloadType,
+ useCBflowControl: device.useCBflowControl,
+ storeLastMeasure: device.storeLastMeasure,
+ lastMeasure: device.lastMeasure,
+ cmdMode: device.cmdMode
};
}
@@ -254,6 +265,7 @@ function handleListDevices(req, res, next) {
function handleGetDevice(req, res, next) {
deviceService.getDevice(
req.params.deviceId,
+ req.query.apikey,
req.headers['fiware-service'],
req.headers['fiware-servicepath'],
function (error, device) {
@@ -262,48 +274,65 @@ function handleGetDevice(req, res, next) {
} else if (device) {
res.status(200).json(toProvisioningAPIFormat(device));
} else {
- next(new errors.DeviceNotFound(req.params.deviceId));
+ next(new errors.DeviceNotFound(req.params.deviceId), {
+ apikey: req.query.apikey,
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ });
}
}
);
}
-/**
- * This middleware handles the removal of a particular device specified with the deviceId.
- */
-function handleRemoveDevice(req, res, next) {
- function getDevice(deviceId, service, subservice, callback) {
- deviceService.getDevice(deviceId, service, subservice, function (error, device) {
- if (error) {
- callback(error);
- } else if (device) {
- callback(null, device);
- } else {
- callback(new errors.DeviceNotFound(deviceId));
- }
- });
- }
-
- function applyRemoveDeviceHandler(device, callback) {
- if (removeDeviceHandler) {
- removeDeviceHandler(device, callback);
- } else {
+function getDevice(deviceId, apikey, service, subservice, callback) {
+ deviceService.getDevice(deviceId, apikey, service, subservice, function (error, device) {
+ if (error) {
+ callback(error);
+ } else if (device) {
callback(null, device);
+ } else {
+ callback(
+ new errors.DeviceNotFound(deviceId, {
+ apikey: apikey,
+ service: service,
+ subservice: subservice
+ })
+ );
}
- }
+ });
+}
- function unregisterDevice(deviceId, service, subservice, device, callback) {
- return deviceService.unregister(deviceId, service, subservice, callback);
+function applyRemoveDeviceHandler(device, callback) {
+ if (removeDeviceHandler) {
+ removeDeviceHandler(device, callback);
+ } else {
+ callback(null, device);
}
+}
+function unregisterDevice(deviceId, apikey, service, subservice, device, callback) {
+ return deviceService.unregister(deviceId, apikey, service, subservice, callback);
+}
+
+/**
+ * This middleware handles the removal of a particular device specified with the deviceId.
+ */
+function handleRemoveDevice(req, res, next) {
async.waterfall(
[
apply(statsRegistry.add, 'deviceRemovalRequests', 1),
- apply(getDevice, req.params.deviceId, req.headers['fiware-service'], req.headers['fiware-servicepath']),
+ apply(
+ getDevice,
+ req.params.deviceId,
+ req.query.apikey,
+ req.headers['fiware-service'],
+ req.headers['fiware-servicepath']
+ ),
applyRemoveDeviceHandler,
apply(
unregisterDevice,
req.params.deviceId,
+ req.query.apikey,
req.headers['fiware-service'],
req.headers['fiware-servicepath']
)
@@ -312,7 +341,13 @@ function handleRemoveDevice(req, res, next) {
if (error && error.code !== 404) {
next(error);
} else if (error && error.code === 404) {
- next(new errors.DeviceNotFound(req.params.deviceId));
+ next(
+ new errors.DeviceNotFound(req.params.deviceId, {
+ apikey: req.query.apikey,
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ })
+ );
} else {
res.status(204).send();
}
@@ -320,15 +355,65 @@ function handleRemoveDevice(req, res, next) {
);
}
+/**
+ * This middleware handles the removal of several devices specified in a array into a body
+ */
+function handleRemoveDevices(req, res, next) {
+ logger.debug(context, 'Handling delete of devices: %j', req.body);
+ let theErrorOut = false;
+ for (let devicetoRemove of req.body.devices) {
+ let theError = theErrorOut;
+ async.waterfall(
+ [
+ apply(statsRegistry.add, 'deviceRemovalRequests', 1),
+ apply(
+ getDevice,
+ devicetoRemove.deviceId,
+ devicetoRemove.apikey,
+ req.headers['fiware-service'],
+ req.headers['fiware-servicepath']
+ ),
+ applyRemoveDeviceHandler,
+ apply(
+ unregisterDevice,
+ devicetoRemove.deviceId,
+ devicetoRemove.apikey,
+ req.headers['fiware-service'],
+ req.headers['fiware-servicepath']
+ )
+ ],
+ function (error) {
+ if (error && error.code !== 404) {
+ theError = !theError ? error : theError;
+ } else if (error && error.code === 404) {
+ theError = !theError
+ ? new errors.DeviceNotFound(devicetoRemove.deviceId, {
+ apikey: devicetoRemove.apikey,
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ })
+ : theError;
+ }
+ }
+ ); // waterfall
+ theErrorOut = theError;
+ } // for
+ if (theErrorOut) {
+ next(theErrorOut);
+ } else {
+ res.status(204).send();
+ }
+}
+
/**
* This middleware handles updates in the provisioning devices. The only attribute
*/
function handleUpdateDevice(req, res, next) {
- function applyUpdatingHandler(device, callback) {
+ function applyUpdatingHandler(newDevice, oldDevice, callback) {
if (updatingHandler) {
- updatingHandler(device, callback);
+ updatingHandler(newDevice, oldDevice, callback);
} else {
- callback(null, device);
+ callback(null, newDevice);
}
}
@@ -337,6 +422,7 @@ function handleUpdateDevice(req, res, next) {
} else {
deviceService.getDevice(
req.params.deviceId,
+ req.query.apikey,
req.headers['fiware-service'],
req.headers['fiware-servicepath'],
function (error, device) {
@@ -354,10 +440,11 @@ function handleUpdateDevice(req, res, next) {
isTypeOrNameUpdated = true;
}
async.waterfall(
- [apply(applyUpdatingHandler, newDevice)],
+ [apply(applyUpdatingHandler, newDevice, device)],
function handleUpdating(error, newDeviceUpdated) {
deviceService.updateRegister(
newDeviceUpdated,
+ device,
isTypeOrNameUpdated,
function handleDeviceUpdate(error) {
if (error) {
@@ -370,7 +457,13 @@ function handleUpdateDevice(req, res, next) {
}
);
} else {
- next(new errors.DeviceNotFound(req.params.deviceId));
+ next(
+ new errors.DeviceNotFound(req.params.deviceId, {
+ apikey: req.query.apikey,
+ service: req.headers['fiware-service'],
+ subservice: req.headers['fiware-servicepath']
+ })
+ );
}
}
);
@@ -405,6 +498,8 @@ function loadContextRoutes(router) {
handleUpdateDevice
);
+ router.post('/iot/op/delete', restUtils.checkRequestAttributes('headers', mandatoryHeaders), handleRemoveDevices);
+
router.delete(
'/iot/devices/:deviceId',
restUtils.checkRequestAttributes('headers', mandatoryHeaders),
diff --git a/lib/services/northBound/northboundServer.js b/lib/services/northBound/northboundServer.js
index 5ed44f8c4..a3d394ba9 100644
--- a/lib/services/northBound/northboundServer.js
+++ b/lib/services/northBound/northboundServer.js
@@ -29,10 +29,12 @@ let northboundServer;
const contextServer = require('./contextServer');
const domainUtils = require('../common/domain');
const middlewares = require('../common/genericMiddleware');
+const healthMiddleware = require('../common/health');
const intoTrans = domainUtils.intoTrans;
const deviceProvisioning = require('./deviceProvisioningServer');
const deviceUpdating = require('./deviceProvisioningServer');
const groupProvisioning = require('./deviceGroupAdministrationServer');
+const statsRegistry = require('../stats/statsRegistry');
const logger = require('logops');
const context = {
op: 'IoTAgentNGSI.NorthboundServer'
@@ -56,9 +58,9 @@ function start(config, callback) {
northboundServer.app.set('port', config.server.port);
northboundServer.app.set('host', config.server.host || '0.0.0.0');
northboundServer.app.use(domainUtils.requestDomain);
- northboundServer.app.use(bodyParser.json());
- northboundServer.app.use(bodyParser.json({ type: 'application/*+json' }));
-
+ northboundServer.app.use(bodyParser.json({ limit: config.expressLimit }));
+ northboundServer.app.use(bodyParser.json({ type: 'application/*+json', limit: config.expressLimit }));
+ northboundServer.app.set('trust proxy', true); // populate req.ip
if (config.logLevel && config.logLevel === 'DEBUG') {
northboundServer.app.use(middlewares.traceRequest);
}
@@ -79,10 +81,31 @@ function start(config, callback) {
middlewares.setIotaInformation(iotaInformation);
+ const cbUrl =
+ config.contextBroker?.host && config.contextBroker?.port
+ ? `http://${config.contextBroker.host}:${config.contextBroker.port}`
+ : null;
+ const mgrUrl = config.iotManager?.url ? `${config.iotManager.url}` : null;
+
+ if (config.healthCheck) {
+ healthMiddleware.startHealthChecks({
+ contextBrokerUrl: cbUrl,
+ iotagentManagerUrl: mgrUrl,
+ deviceRegistryType: config.deviceRegistry?.type,
+ configMqtt: config.mqtt,
+ intervalMs: config.healthCheckInterval,
+ timeoutMs: config.healthCheckTimeout,
+ downAfterFails: config.healthCheckDownAfterFails,
+ considerHttpResponseUp: config.healthCheckConsiderHttpResponseUp
+ });
+ }
+
northboundServer.router.get('/iot/about', middlewares.retrieveVersion);
northboundServer.router.get('/version', middlewares.retrieveVersion);
+ northboundServer.router.get('/ready', middlewares.getReady);
northboundServer.router.put('/admin/log', middlewares.changeLogLevel);
northboundServer.router.get('/admin/log', middlewares.getLogLevel);
+ northboundServer.router.get('/metrics', statsRegistry.openmetricsHandler);
northboundServer.app.use(baseRoot, northboundServer.router);
contextServer.loadContextRoutes(northboundServer.router);
@@ -123,6 +146,7 @@ exports.setRemoveDeviceHandler = intoTrans(context, deviceProvisioning.setRemove
exports.addDeviceProvisionMiddleware = deviceProvisioning.addDeviceProvisionMiddleware;
exports.addConfigurationProvisionMiddleware = groupProvisioning.addConfigurationProvisionMiddleware;
exports.addNotificationMiddleware = contextServer.addNotificationMiddleware;
+exports.executeUpdateSideEffects = contextServer.executeUpdateSideEffects;
exports.clear = clear;
exports.start = intoTrans(context, start);
exports.stop = intoTrans(context, stop);
diff --git a/lib/services/northBound/restUtils.js b/lib/services/northBound/restUtils.js
index 9be885c80..0c2c1e63c 100644
--- a/lib/services/northBound/restUtils.js
+++ b/lib/services/northBound/restUtils.js
@@ -30,7 +30,6 @@ const errors = require('../../errors');
const constants = require('../../constants');
const intoTrans = require('../common/domain').intoTrans;
const revalidator = require('revalidator');
-const moment = require('moment');
const context = {
op: 'IoTAgentNGSI.RestUtils'
};
@@ -65,7 +64,7 @@ function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
}
if (missing.length !== 0) {
- const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing));
+ const error = new errors.MissingAttributes('Missing attributes: ' + JSON.stringify(missing), body);
error.code = '400';
callback(error);
@@ -121,6 +120,11 @@ function checkBody(template) {
};
}
+function isISOString(val) {
+ const d = new Date(val);
+ return !Number.isNaN(d.valueOf()) && d.toISOString() === val;
+}
+
/**
* Checks if the timestamp properties of NGSIv2 entities are valid ISO8601 dates.
*
@@ -131,7 +135,7 @@ function IsValidTimestampedNgsi2(payload) {
function isValidTimestampedNgsi2Entity(entity) {
for (const i in entity) {
if (entity.hasOwnProperty(i)) {
- if (i === constants.TIMESTAMP_ATTRIBUTE && !moment(entity[i].value, moment.ISO_8601).isValid()) {
+ if (i === constants.TIMESTAMP_ATTRIBUTE && !isISOString(entity[i].value)) {
return false;
}
}
diff --git a/lib/services/stats/statsRegistry.js b/lib/services/stats/statsRegistry.js
index 6900c9940..7d96df893 100644
--- a/lib/services/stats/statsRegistry.js
+++ b/lib/services/stats/statsRegistry.js
@@ -23,16 +23,9 @@
/* eslint-disable no-prototype-builtins */
-const async = require('async');
const _ = require('underscore');
-const apply = async.apply;
const logger = require('logops');
-const config = require('../../commonConfig');
-const dbService = require('../../model/dbConn');
let globalStats = {};
-let currentStats = {};
-let timerActions = [];
-let timerHandler;
const statsContext = {
op: 'IoTAgentNGSI.TimedStats'
};
@@ -45,28 +38,24 @@ const statsContext = {
* @param {Number} value Value to be added to the total.
*/
function add(key, value, callback) {
- if (currentStats[key]) {
- currentStats[key] += value;
- } else {
- currentStats[key] = value;
- }
-
if (globalStats[key]) {
globalStats[key] += value;
} else {
globalStats[key] = value;
}
-
callback(null);
}
/**
- * Get the current value of a particular stat.
+ * Set a new stat value to the specified key of the stats registry. The stat is set
+ * both to the global and current stats.
*
- * @param {String} key Name of the stat to retrive.
+ * @param {String} key Name of the stat that is going to be written.
+ * @param {Number} value Value to be set to the total.
*/
-function getCurrent(key, callback) {
- callback(null, currentStats[key]);
+function set(key, value, callback) {
+ globalStats[key] = value;
+ callback(null);
}
/**
@@ -85,112 +74,159 @@ function getAllGlobal(callback) {
callback(null, globalStats);
}
-/**
- * Get all the current stats currently stored in the repository.
- */
-function getAllCurrent(callback) {
- callback(null, currentStats);
-}
-
/**
* Loads the values passed as parameters into the global statistics repository.
*
* @param {Object} values Key-value map with the values to be load.
*/
function globalLoad(values, callback) {
- globalStats = values;
- currentStats = {};
-
- for (const i in values) {
- if (values.hasOwnProperty(i)) {
- currentStats[i] = 0;
- }
- }
-
+ globalStats = _.extend({}, globalStats, values);
callback(null);
}
/**
- * Reset each of the current stats to value zero.
+ * Chooses the appropiate content type and version based on Accept header
+ *
+ * @param {String} accepts The accepts header
*/
-function resetCurrent(callback) {
- for (const i in currentStats) {
- if (currentStats.hasOwnProperty(i)) {
- currentStats[i] = 0;
+function matchContentType(accepts) {
+ const requestedType = [];
+ const vlabel = 'version=';
+ const clabel = 'charset=';
+ const qlabel = 'q=';
+ for (const expression of accepts.split(',')) {
+ const parts = expression.split(';').map((part) => part.trim());
+ const mediaType = parts[0];
+ let version = null;
+ let charset = null;
+ let preference = null;
+ for (let part of parts.slice(1)) {
+ if (part.startsWith(vlabel)) {
+ version = part.substring(vlabel.length).trim();
+ } else if (part.startsWith(clabel)) {
+ charset = part.substring(clabel.length).trim();
+ } else if (part.startsWith(qlabel)) {
+ preference = parseFloat(part.substring(qlabel.length).trim());
+ }
}
+ requestedType.push({
+ mediaType: mediaType,
+ version: version,
+ charset: charset,
+ preference: preference || 1.0
+ });
}
-
- callback();
-}
-
-/**
- * Executes all the stored timer actions when a timer click is received.
- */
-function tickHandler() {
- process.nextTick(apply(async.series, timerActions));
-}
-
-/**
- * Adds a new timer action to the timerActions Array, activating the timer if it was not previously activated.
- *
- * @param {Function} handler Action to be executed. Should take two statistics objects and a callback.
- */
-function addTimerAction(handler, callback) {
- if (!timerHandler && config.getConfig().stats.interval) {
- timerHandler = setInterval(tickHandler, config.getConfig().stats.interval);
+ // If both text/plain and openmetrics are accepted,
+ // prefer openmetrics
+ const mediaTypePref = {
+ 'application/openmetrics-text': 1.0,
+ 'text/plain': 0.5
+ };
+ // sort requests by priority descending
+ requestedType.sort(function (a, b) {
+ if (a.preference === b.preference) {
+ // same priority, sort by media type.
+ return (mediaTypePref[b.mediaType] || 0) - (mediaTypePref[a.mediaType] || 0);
+ }
+ return b.preference - a.preference;
+ });
+ for (const req of requestedType) {
+ switch (req.mediaType) {
+ case 'application/openmetrics-text':
+ req.version = req.version || '1.0.0';
+ req.charset = req.charset || 'utf-8';
+ if ((req.version === '1.0.0' || req.version === '0.0.1') && req.charset === 'utf-8') {
+ return req;
+ }
+ break;
+ case 'text/plain':
+ case 'text/*':
+ case '*/*':
+ req.version = req.version || '0.0.4';
+ req.charset = req.charset || 'utf-8';
+ if (req.version === '0.0.4' && req.charset === 'utf-8') {
+ req.mediaType = 'text/plain';
+ return req;
+ }
+ break;
+ }
}
-
- timerActions.push(apply(handler, currentStats, globalStats));
- callback();
+ return null;
}
/**
- * Clear the actions array and stop the timers.
+ * Predefined http handler that returns current openmetrics data
*/
-function clearTimers(callback) {
- if (timerHandler) {
- clearInterval(timerHandler);
- timerHandler = undefined;
+/* eslint-disable-next-line no-unused-vars */
+function openmetricsHandler(req, res) {
+ // Content-Type:
+ // - For openmetrics collectors, it MUST BE 'application/openmetrics-text; version=1.0.0; charset=utf-8'. See:
+ // https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#overall-structure
+ // - For prometheus compatible collectors, it SHOULD BE 'text/plain; version=0.0.4; charset=utf-8'. See:
+ // https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
+ // - Caveat: Some versions of prometheus have been observed to send multivalued Accept headers such as
+ // Accept: application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1
+ let reqType = {
+ mediaType: 'application/openmetrics-text',
+ version: '1.0.0',
+ charset: 'utf-8'
+ };
+ if (req.headers.accept) {
+ // WORKAROUND: express version 4 does not parse properly the openmetrics Accept header,
+ // it won't match the regular expressions supported by `express.accepts`.
+ // So we must parse these key-value pairs ourselves.
+ reqType = matchContentType(req.headers.accept);
+ if (reqType === null) {
+ logger.error(statsContext, 'Unsupported media type: %s', req.headers.accept);
+ res.status(406).send('Not Acceptable');
+ return;
+ }
}
-
- timerActions = [];
- callback();
+ const contentType = `${reqType.mediaType};version=${reqType.version};charset=${reqType.charset}`;
+ // The actual payload is the same for all supported content types
+ const metrics = [];
+ for (const key in globalStats) {
+ if (globalStats.hasOwnProperty(key)) {
+ metrics.push('# HELP ' + key + ' global metric for ' + key);
+ metrics.push('# TYPE ' + key + ' counter');
+ metrics.push(key + ' ' + globalStats[key]);
+ }
+ }
+ // Expositions MUST END WITH '#EOF'
+ // See https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md
+ metrics.push('# EOF');
+ res.set('Content-Type', contentType);
+ res.status(200).send(metrics.join('\n'));
}
/**
- * Predefined stats action that logs the stats to the standard log.
+ * Wraps a callback with stats, incrementing the given counters
+ * depending on the parameters passed to the callback:
*
- * @param {Object} currentValues Current stat values.
- * @param {Object} globalValues Global stat values.
- */
-function logStats(currentValues, globalValues, callback) {
- logger.info(statsContext, 'Global stat values:\n%s\n', JSON.stringify(globalValues, null, 4));
- logger.info(statsContext, 'Current stat values:\n%s\n', JSON.stringify(currentValues, null, 4));
-
- resetCurrent(callback);
-}
-
-/**
- * Predefined action that persists the current value of the stats in the MongoDb instance.
+ * - If the callback receives an error, the errCounter is incremented.
+ * - If the callback receives no error, the okCounter is incremented.
*
- * @param {Object} currentValues Current stat values.
- * @param {Object} globalValues Global stat values.
+ * @param {String} okCounter Name of the counter to increment on success.
+ * @param {String} errCounter Name of the counter to increment on error.
+ * @param {Function} callback Callback to wrap. It must be a function that can
+ * expect any number of parameters, but the first one must
+ * be an indication of the error occured, if any.
*/
-function mongodbPersistence(currentValues, globalValues, callback) {
- const statStamp = _.clone(globalValues);
-
- statStamp.timestamp = new Date().toISOString();
- dbService.db.collection('kpis').insertOne(statStamp, callback);
+function withStats(okCounter, errCounter, callback) {
+ function accounting(...args) {
+ const counter = args.length > 0 && args[0] ? errCounter : okCounter;
+ add(counter, 1, function () {
+ callback(...args);
+ });
+ }
+ return accounting;
}
+exports.set = set;
exports.add = add;
-exports.getCurrent = getCurrent;
exports.getGlobal = getGlobal;
exports.getAllGlobal = getAllGlobal;
-exports.getAllCurrent = getAllCurrent;
exports.globalLoad = globalLoad;
-exports.resetCurrent = resetCurrent;
-exports.clearTimers = clearTimers;
-exports.addTimerAction = addTimerAction;
-exports.logStats = logStats;
-exports.mongodbPersistence = mongodbPersistence;
+exports.withStats = withStats;
+exports.openmetricsHandler = openmetricsHandler;
+exports.matchContentType = matchContentType;
diff --git a/lib/templates/createDevice.json b/lib/templates/createDevice.json
index b652db6ec..3fe7abea9 100644
--- a/lib/templates/createDevice.json
+++ b/lib/templates/createDevice.json
@@ -32,12 +32,12 @@
"description": "Protocol the device is using to communicate with the platform",
"type": "string"
},
- "transport": {
- "description": "Transport protocol used by the platform to communicate with the device",
+ "endpoint": {
+ "description": "Endpoint for the commands targeting this device",
"type": "string"
},
- "expressionLanguage": {
- "description": "Expression language used to apply expressions for this device",
+ "transport": {
+ "description": "Transport protocol used by the platform to communicate with the device",
"type": "string"
},
"explicitAttrs": {
@@ -47,6 +47,10 @@
"description": "NGSI Interface for this device",
"type": "string"
},
+ "payloadType": {
+ "description": "Payload type allowed for measures for this device",
+ "type": "string"
+ },
"lazy": {
"description": "list of lazy attributes of the devices",
"type": "array",
@@ -115,30 +119,6 @@
"type": "string",
"pattern": "^([^<>;'=\"]+)+$"
},
- "reverse": {
- "description": "Define the attribute as bidirectional",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "object_id": {
- "description": "ID of the attribute in the device",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "type": {
- "description": "Type of the attribute in the target entity",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "expression": {
- "description": "Optional expression for measurement transformation",
- "type": "string"
- }
- }
- }
- },
"metadata": {
"description": "Attribute Metadata",
"type": "object"
@@ -187,6 +167,10 @@
"description": "Content type",
"type": "string"
},
+ "headers": {
+ "description": "Optional headers to include with command",
+ "type": "object"
+ },
"mqtt": {
"description": "Mqtt properties",
"type": "object",
diff --git a/lib/templates/createDeviceLax.json b/lib/templates/createDeviceLax.json
index c681d3fd9..a5232f46e 100644
--- a/lib/templates/createDeviceLax.json
+++ b/lib/templates/createDeviceLax.json
@@ -32,14 +32,14 @@
"description": "Protocol the device is using to communicate with the platform",
"type": "string"
},
+ "endpoint": {
+ "description": "Endpoint for the commands targeting this device",
+ "type": "string"
+ },
"transport": {
"description": "Transport protocol used by the platform to communicate with the device",
"type": "string"
},
- "expressionLanguage": {
- "description": "Expression language used to apply expressions for this device",
- "type": "string"
- },
"explicitAttrs": {
"description": "Flag about only provisioned attributes will be processed to Context Broker"
},
@@ -47,6 +47,10 @@
"description": "NGSI Interface for this device",
"type": "string"
},
+ "payloadType": {
+ "description": "Payload type allowed for measures for this device",
+ "type": "string"
+ },
"lazy": {
"description": "list of lazy attributes of the devices",
"type": "array",
@@ -112,29 +116,6 @@
"description": "Optional entity type for multientity updatess",
"type": "string",
"pattern": "^([^<>;'=\"]+)+$"
- },
- "reverse": {
- "description": "Define the attribute as bidirectional",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "object_id": {
- "description": "ID of the attribute in the device",
- "type": "string"
- },
- "type": {
- "description": "Type of the attribute in the target entity",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "expression": {
- "description": "Optional expression for measurement transformation",
- "type": "string"
- }
- }
- }
}
}
}
diff --git a/lib/templates/deviceGroup.json b/lib/templates/deviceGroup.json
index f5788d119..cb9fb513c 100644
--- a/lib/templates/deviceGroup.json
+++ b/lib/templates/deviceGroup.json
@@ -20,6 +20,14 @@
"type": "string",
"required": true
},
+ "endpoint": {
+ "description": "Endpoint for the commands targeting this group",
+ "type": "string"
+ },
+ "transport": {
+ "description": "Transport protocol used by the platform to communicate with the device",
+ "type": "string"
+ },
"token": {
"description": "token",
"type": "string"
@@ -37,14 +45,14 @@
"description": "list of lazy attributes of the devices",
"type": "array"
},
- "expressionLanguage": {
- "description": "Expression language used to for the group of devices",
- "type": "string"
- },
"ngsiVersion": {
"description": "NGSI Interface for this group of devices",
"type": "string"
},
+ "payloadType": {
+ "description": "Payload type allowed for measures for this group",
+ "type": "string"
+ },
"attributes": {
"description": "list of active attributes of the devices",
"type": "array"
@@ -52,33 +60,9 @@
"commands": {
"description": "list of commands of the devices",
"type": "array"
- },
- "reverse": {
- "description": "Define the attribute as bidirectional",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "object_id": {
- "description": "ID of the attribute in the device",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "type": {
- "description": "Type of the attribute in the target entity",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "expression": {
- "description": "Optional expression for measurement transformation",
- "type": "string"
- }
- }
- }
}
}
}
}
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/lib/templates/queryContext.json b/lib/templates/queryContext.json
deleted file mode 100644
index e91e62569..000000000
--- a/lib/templates/queryContext.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "properties": {
- "entities": {
- "description": "The objects the request is applied to.",
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "required": true
- },
- "isPattern": {
- "type": "string"
- },
- "id": {
- "type": "string",
- "required": true
- }
- }
- },
- "required": true
- }
- }
-}
\ No newline at end of file
diff --git a/lib/templates/updateDevice.json b/lib/templates/updateDevice.json
index 8fc369b52..ad1f015c9 100644
--- a/lib/templates/updateDevice.json
+++ b/lib/templates/updateDevice.json
@@ -33,6 +33,10 @@
"description": "Endpoint for the commands targeting this device",
"type": "string"
},
+ "transport": {
+ "description": "Transport protocol used by the platform to communicate with the device",
+ "type": "string"
+ },
"lazy": {
"description": "list of lazy attributes of the devices",
"type": "array",
@@ -101,30 +105,6 @@
"type": "string",
"pattern": "^([^<>;'=\"]+)+$"
},
- "reverse": {
- "description": "Define the attribute as bidirectional",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "object_id": {
- "description": "ID of the attribute in the device",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "type": {
- "description": "Type of the attribute in the target entity",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "expression": {
- "description": "Optional expression for measurement transformation",
- "type": "string"
- }
- }
- }
- },
"metadata": {
"description": "Attribute Metadata",
"type": "object"
@@ -165,10 +145,22 @@
"description": "Optional expression for command transformation",
"type": "string"
},
+ "headers": {
+ "description": "Optional headers to include with command",
+ "type": "object"
+ },
"payloadType": {
"description": "Payload type",
"type": "string"
},
+ "useCBflowControl": {
+ "description": "use CB flowControl option",
+ "type": "boolean"
+ },
+ "cmdMode": {
+ "description": "CB commands mode",
+ "type": "string"
+ },
"contentType": {
"description": "Content type",
"type": "string"
@@ -200,12 +192,36 @@
"description": "Free form array of data to be appended to the target entity",
"type": "array"
},
+ "timestamp": {
+ "description": "Timestamp",
+ "type": "boolean"
+ },
"explicitAttrs": {
"description": "Flag to decide update of active attributes only"
},
"ngsiVersion": {
"description": "NGSI Interface for this device",
"type": "string"
+ },
+ "payloadType": {
+ "description": "Payload type allowed for measures for this device",
+ "type": "string"
+ },
+ "useCBflowControl": {
+ "description": "use CB flowControl option",
+ "type": "boolean"
+ },
+ "storeLastMeasure": {
+ "description": "Store last measure",
+ "type": "boolean"
+ },
+ "lastMeasure": {
+ "description": "last measure",
+ "type": "object"
+ },
+ "cmdMode": {
+ "description": "CB commands mode",
+ "type": "string"
}
}
}
diff --git a/lib/templates/updateDeviceLax.json b/lib/templates/updateDeviceLax.json
index 4341e2e31..0b2002dbc 100644
--- a/lib/templates/updateDeviceLax.json
+++ b/lib/templates/updateDeviceLax.json
@@ -89,29 +89,6 @@
"description": "Optional entity type for multientity updatess",
"type": "string",
"pattern": "^([^<>;'=\"]+)+$"
- },
- "reverse": {
- "description": "Define the attribute as bidirectional",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "object_id": {
- "description": "ID of the attribute in the device",
- "type": "string"
- },
- "type": {
- "description": "Type of the attribute in the target entity",
- "type": "string",
- "pattern": "^([^<>();'=\"]+)+$"
- },
- "expression": {
- "description": "Optional expression for measurement transformation",
- "type": "string"
- }
- }
- }
}
}
}
@@ -154,6 +131,37 @@
"static_attributes": {
"description": "Free form array of data to be appended to the target entity",
"type": "array"
+ },
+ "timestamp": {
+ "description": "Timestamp",
+ "type": "boolean"
+ },
+ "explicitAttrs": {
+ "description": "Flag to decide update of active attributes only"
+ },
+ "ngsiVersion": {
+ "description": "NGSI Interface for this device",
+ "type": "string"
+ },
+ "payloadType": {
+ "description": "Payload type allowed for measures for this device",
+ "type": "string"
+ },
+ "useCBflowControl": {
+ "description": "use CB flowControl option",
+ "type": "boolean"
+ },
+ "storeLastMeasure": {
+ "description": "Store last measure",
+ "type": "boolean"
+ },
+ "lastMeasure": {
+ "description": "last measure",
+ "type": "object"
+ },
+ "cmdMode": {
+ "description": "CB commands mode",
+ "type": "string"
}
}
}
diff --git a/mkdocs.yml b/mkdocs.yml
index 94795c5a5..10118f528 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,20 +8,15 @@ edit_uri: edit/master/doc/
markdown_extensions: [toc,fenced_code]
use_directory_urls: false
theme: readthedocs
-extra_css: ["https://www.fiware.org/style/fiware_readthedocs.css", "https://www.fiware.org/style/fiware_readthedocs_iot.css"]
+extra_css: ["https://fiware.github.io/catalogue/style/fiware_readthedocs.css", "https://fiware.github.io/catalogue/style/fiware_readthedocs_iot.css"]
pages:
- Home: 'index.md'
- 'Getting Started' : 'getting-started.md'
- 'User & Programmers Manual':
- - 'Architecture' : 'architecture.md'
- 'IoT Agent API' : 'api.md'
- - 'Advanced Topics' : 'advanced-topics.md'
- - 'Library Functions': 'usermanual.md'
- - 'Measurement Transformation Expression Language': 'expressionLanguage.md'
- - 'How to develop a new IoT Agent': 'howto.md'
- - 'North Port - NGSI Interactions': 'northboundinteractions.md'
- - 'Development Documentation': development.md
- - 'Installation & Administration Manual':
- - 'Installation Guide': 'installationguide.md'
- - 'Operations (logs & alarms)': 'operations.md'
-
+ - 'Installation and administration manual': 'admin.md'
+ - 'Development documentation':
+ - 'Development manual': 'devel/development.md'
+ - 'Contributing guide': 'devel/contribution-guidelines.md'
+ - 'Architecture' : 'devel/architecture.md'
+ - 'North Port - NGSI Interactions': 'devel/northboundinteractions.md'
\ No newline at end of file
diff --git a/package.json b/package.json
index b4e4b2a7a..12dddbf25 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "iotagent-node-lib",
"license": "AGPL-3.0-only",
"description": "IoT Agent library to interface with NGSI Context Broker",
- "version": "3.2.0-next",
+ "version": "4.13.0-next",
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
"keywords": [
"fiware",
@@ -23,7 +23,7 @@
},
"main": "lib/fiware-iotagent-lib",
"engines": {
- "node": ">=16"
+ "node": ">=20"
},
"scripts": {
"clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage",
@@ -33,6 +33,7 @@
"prettier": "prettier --config .prettierrc.json --write '**/**/**/**/*.js' '**/**/**/*.js' '**/**/*.js' '**/*.js' '*.js'",
"prettier:text": "prettier 'README.md' 'doc/*.md' 'doc/**/*.md' --no-config --tab-width 4 --print-width 120 --write --prose-wrap always",
"test": "nyc --reporter=text mocha --recursive 'test/**/*.js' --reporter spec --timeout 8000 --ui bdd --exit --color true",
+ "test:functional": "nyc --reporter=text mocha --recursive 'test/functional/*.js' --reporter spec --timeout 5000 --ui bdd --exit --color true",
"test:expression": "nyc --reporter=text mocha --recursive 'test/unit/expressions/*.js' --reporter spec --timeout 5000 --ui bdd --exit --color true",
"test:multientity": "nyc --reporter=text mocha --recursive 'test/unit/ngsiv2/plugins/multientity-plugin_test.js' --reporter spec --timeout 5000 --ui bdd --exit --color true",
"test:debug": "mocha --recursive 'test/**/*.js' --reporter spec --inspect-brk --timeout 30000 --ui bdd --exit",
@@ -43,21 +44,24 @@
},
"dependencies": {
"async": "2.6.4",
- "body-parser": "~1.20.0",
- "express": "~4.18.1",
+ "body-parser": "~1.20.3",
+ "express": "~4.22.1",
"got": "~11.8.5",
"jexl": "2.3.0",
"jison": "0.4.18",
"logops": "2.1.2",
"moment": "~2.29.2",
"moment-timezone": "~0.5.34",
- "mongoose": "5.13.14",
+ "mongoose": "8.9.5",
"query-string": "7.1.1",
"revalidator": "~0.3.1",
"underscore": "~1.13.4",
"uuid": "~8.3.2"
},
"devDependencies": {
+ "async-mqtt": "~2.6.3",
+ "chai": "~4.3.10",
+ "chai-match-pattern": "~1.3.0",
"coveralls": "~3.1.1",
"eslint": "~8.18.0",
"eslint-config-tamia": "~8.0.0",
@@ -65,7 +69,7 @@
"husky": "~4.2.5",
"lint-staged": "~12.3.8",
"mocha": "10.0.0",
- "mongodb": "4.7.0",
+ "mongodb": "4.17.2",
"nock": "13.2.7",
"nyc": "~15.1.0",
"prettier": "~2.7.1",
diff --git a/scripts/legacy_expression_tool/README.md b/scripts/legacy_expression_tool/README.md
index e601f0491..d99ae26cb 100644
--- a/scripts/legacy_expression_tool/README.md
+++ b/scripts/legacy_expression_tool/README.md
@@ -82,28 +82,51 @@ python legacy_expression_tool.py \
The list of possible arguments that the scripts accepts are:
-| Argument | Description | Default value | Mandatory |
-| ---------------------- | --------------------------------------------------------------------------------------------- | ---------------------------- | --------- |
-| `--mongouri` | The MongoDB URI to connect to | `mongodb://localhost:27017/` | No |
-| `--database` | The database name to replace the expressions | NA | Yes |
-| `--collection` | The collection name to replace the expressions | NA | Yes |
-| `--translation` | The translation dictionary file to replace the expressions | `translation.json` | No |
-| `--debug` | Enable debug mode | `False` | No |
-| `--commit` | Commit the changes to the database | `False` | No |
-| `--expressionlanguage` | What to do with the expression language field. Possibles values: `delete`, `ignore` or `jexl` | `ignore` | No |
-| `--statistics` | Print match statistics. Aggregation modes are the possible values: `service` and `subservice` | `service` | No |
-| `--service` | The fiware service filter to replace the expressions | All subservices | No |
-| `--service-path` | The fiware service path filter to replace the expressions | All subservices | No |
-| `--deviceid` | The device id filter to replace the expressions | All devices | No |
-| `--entitytype` | The entity type filter to replace the expressions | All entity types | No |
-| `--regexservice` | The fiware service regex filter to replace the expressions | All subservices | No |
-| `--regexservicepath` | The fiware service path regex filter to replace the expressions | All subservices | No |
-| `--regexdeviceid` | The device id regex filter to replace the expressions | All devices | No |
-| `--regexentitytype` | The entity type regex filter to replace the expressions | All entity types | No |
+| Argument | Description | Default value | Mandatory |
+| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------- |
+| `--mongouri` | The MongoDB URI to connect to | `mongodb://localhost:27017/` | No |
+| `--database` | The database name to replace the expressions | NA | Yes |
+| `--collection` | The collection name to replace the expressions | NA | Yes |
+| `--translation` | The translation dictionary file to replace the expressions | `translation.json` | No |
+| `--debug` | Enable debug mode | `False` | No |
+| `--commit` | Commit the changes to the database | `False` | No |
+| `--expressionlanguage` | What to do with the expression language field. Possibles values: `delete`, `ignore`, `jexl` or `jexlall`. More detail on this bellow. | `ignore` | No |
+| `--statistics` | Print match statistics. Aggregation modes are the possible values: `service` and `subservice` | `service` | No |
+| `--service` | The fiware service filter to replace the expressions | All subservices | No |
+| `--service-path` | The fiware service path filter to replace the expressions | All subservices | No |
+| `--deviceid` | The device id filter to replace the expressions | All devices | No |
+| `--entitytype` | The entity type filter to replace the expressions | All entity types | No |
+| `--regexservice` | The fiware service regex filter to replace the expressions | All subservices | No |
+| `--regexservicepath` | The fiware service path regex filter to replace the expressions | All subservices | No |
+| `--regexdeviceid` | The device id regex filter to replace the expressions | All devices | No |
+| `--regexentitytype` | The entity type regex filter to replace the expressions | All entity types | No |
Note that filters (`--service`, `--service-path`, `--deviceid` and `--entitytype`, and the regex versions) are
interpreted in additive way (i.e. like a logical AND).
+With regards to `--expressionlanguage`:
+
+- `delete`: changes expressions from legacy to JEXL equivalence in the
+ [fields where expressions may be used](#fields-in-which-expressions-may-be-used) (based on the translation
+ dictionary specified by `--translations`). In addition, deletes the `expressionLanguage` field (no matter its value)
+ in the case it exists in the group/device.
+- `ignore`: changes expressions from legacy to JEXL equivalence in the
+ [fields where expressions may be used](#fields-in-which-expressions-may-be-used) (based on the translation
+ dictionary specified by `--translations`). In addition, it leaves untouched the `expressionLanguage` field. This may
+ cause inconsistencies, if the value of the `expressionLanguage` is `legacy`, as detailed
+ [in this section](#replacing-expression-without-setting-jexl-at-group-or-device-level).
+- `jexl`: changes expressions from legacy to JEXL equivalence in the
+ [fields where expressions may be used](#fields-in-which-expressions-may-be-used) (based on the translation
+ dictionary specified by `--translations`). In addition, it set `expressionLanguage` field to `jexl` (no matter if
+ the field originally exists in the group/device or not) **if some JEXL expression were translated**.
+- `jexlall`: changes expression from legacy to JEXL equivalence in the
+ [fields where expressions may be used](#fields-in-which-expressions-may-be-used) (based on the translation
+ dictionary specified by `--translations`). In addition, it set `expressionLanguage` field to `jexl` (no matter if
+ the field originally exists in the group/device or not). The difference between `jexl`and `jexlall` is in the second
+ case, the script is not only looking for documents that contains legacy expressions, it also includes all
+ groups/devices that have `expressionLanguage` field defined. The `expressionLanguage` field on those documents (and
+ also on the documents that contains legacy expresions) is set to `jexl`.
+
## Usage
### Getting legacy expressions matches
@@ -204,3 +227,53 @@ python legacy_expression_tool.py \
--translation \
--commit
```
+
+### Output statistics
+
+When the script is executed, it prints some statistics about the matches found. This statistics can be printed filtered
+by service or subservice. By default, no statistics are printed. You can change it using the `--statistics` plus the
+aggregation mode. The possible values are `service` and `subservice`.
+
+```bash
+Found 2 legacy expressions in 2 documents
+ _id
+service service1 service2 All
+expression
+${@value*100/total} 1 1 2
+All 1 1 2
+```
+
+### Fields in which expressions may be used
+
+The script implements expression detection and translation in the following fields in group/device documents at DB:
+
+- active.expression
+- active.entity_name
+- attributes.expression
+- attributes.entity_name
+- attributes.expression
+- commands.expression
+- endpoint
+- entityNameExp
+- explicitAttrs
+
+### Known issues
+
+#### Execution with `expressionlanguage` set to `jexlall`
+
+When executing the script with `expressionlanguage` set to `jexlall`, the script will look for all the documents
+containing legacy expressions (in some of the [expression capable fields](#fields-in-which-expressions-may-be-used)) or
+the existence of the `expressionLanguage` field. This would change the number of documents found, and the statistics
+will include extra documents.
+
+Running the script with the option set to `jexl` would not include such extra documents in statistics.
+
+#### Replacing expression without setting jexl at group or device level
+
+When executing the script setting `--expressionlanguage` to `ignore` (or when `--expressionlanguage` is not used), the
+script will not change the`expressionLanguage` field in the document. This means that the legacy expressions will be
+replaced, but the `expressionLanguage` field will still be set to the default value or legacy. This would make
+expression evaluation to fail, propagating the value of the attribute as the expression literal to the context broker.
+
+To avoid this, it is recommended use always the `--expressionlangauge` parameter set to `jexl`, so doing it a value
+different from `ignore` will be used.
diff --git a/scripts/legacy_expression_tool/legacy_expression_tool.py b/scripts/legacy_expression_tool/legacy_expression_tool.py
index b7a426302..4b30370eb 100644
--- a/scripts/legacy_expression_tool/legacy_expression_tool.py
+++ b/scripts/legacy_expression_tool/legacy_expression_tool.py
@@ -58,7 +58,7 @@ def parse_json(data):
parser.add_argument('--debug', help='Debug mode', required=False, action='store_true')
parser.add_argument('--commit', help='Commit changes to database', required=False, action='store_true')
parser.add_argument('--mongouri', help='Database connection URI', required=False, default='mongodb://localhost:27017/')
-parser.add_argument('--expressionlanguage', help='How to handle expressionLanguage values. Can be: delete, ignore or jexl', required=False, default='ignore')
+parser.add_argument('--expressionlanguage', help='How to handle expressionLanguage values. Can be: delete, ignore, jexl or jexlall', required=False, default='ignore')
parser.add_argument('--statistics', help='Show statistics at the end of the execution. Possible values: service subservice', required=False, default='service')
parser.add_argument('--regexservice', help='FIWARE service filter', required=False, default='.*')
parser.add_argument('--regexservicepath', help='FIWARE servicepath filter', required=False, default='.*')
@@ -112,8 +112,10 @@ def parse_json(data):
if args['expressionlanguage'] == 'delete':
filter['$and'][0]['$or'].append({'expressionLanguage':{'$exists': True}})
expressionlanguage = 'delete'
-elif args['expressionlanguage'] == 'jexl':
+elif args['expressionlanguage'] == 'jexlall':
filter['$and'][0]['$or'].append({'expressionLanguage':{'$exists': True}})
+ expressionlanguage = 'jexlall'
+elif args['expressionlanguage'] == 'jexl':
expressionlanguage = 'jexl'
else:
expressionlanguage = 'ignore'
@@ -359,10 +361,15 @@ def parse_json(data):
if debug:
print ('ocurrence: ' + occurrence_id + ' expressionLanguage: ' + str(occurrence['expressionLanguage']))
del occurrence['expressionLanguage']
- elif expressionlanguage == 'jexl':
+ elif expressionlanguage == 'jexl' or expressionlanguage == 'jexlall':
if debug:
print ('ocurrence: ' + occurrence_id + ' expressionLanguage: ' + str(occurrence['expressionLanguage']))
occurrence['expressionLanguage'] = 'jexl'
+ else:
+ if expressionlanguage == 'jexl' or expressionlanguage == 'jexlall':
+ if debug:
+ print ('ocurrence: ' + occurrence_id + ' expressionLanguage: ' + 'undefined')
+ occurrence['expressionLanguage'] = 'jexl'
# Update element in the database
diff --git a/scripts/legacy_expression_tool/requirements.txt b/scripts/legacy_expression_tool/requirements.txt
index 5333800c3..200e25307 100644
--- a/scripts/legacy_expression_tool/requirements.txt
+++ b/scripts/legacy_expression_tool/requirements.txt
@@ -1,3 +1,3 @@
matplotlib==3.7.1
pandas==2.0.2
-pymongo==4.3.3
+pymongo==4.6.3
diff --git a/test/functional/README.md b/test/functional/README.md
new file mode 100644
index 000000000..2b29f3d83
--- /dev/null
+++ b/test/functional/README.md
@@ -0,0 +1,406 @@
+## Functional test suite
+
+This directory contains the functional test suite for the IoTA Node Lib. This test suite is based on mocha and chai. For
+mocks, we use the nock library. Additionally, it uses some specific functions to ease to implement the test. Helper
+functions are located in the `testUtils.js` file.
+
+There are 2 tests files in this directory:
+
+- `fuctional-tests.js`: This file contains the test defined in the "classic way". This means, coded in the JS file as
+ any other mocha test. It uses the functions defined in the `testUtils.js` file to simplify the tests.
+- `functional-tests-runner.js`: This file contains the test runner that executes the tests defined as JSON in a
+ separate file (`testCases.js`) in a "automatic way". This file is loaded by the test suite and the test cases are
+ automatically executed.
+
+The recommended way is to use `testCases.js` (run by `functional-tests-runner.js`). The `fuctional-tests.js` file is
+provides as basically as an example in the case the "old way" needs to be used (I.E: when the test follow a different
+pattern than the one supported by the test runner).
+
+### Automatic test cases
+
+Each test case is defined as a JSON object in the `testCases.js` file. This file is loaded by the test suite and the
+test cases are automatically generated. Each test case is defined as an object with the following elements:
+
+- `describeName`: The name of the `DESCRIBE` test case. This will be used to generate the test case name in the mocha.
+ Note this name is prefixed by a pure number (e.g `0010 - Simple group without attributes`) which specifies the group
+ to which the test belong (usually meaning a feature) or by a number preced by the `#` symbol to refer to an issue
+ number (e.g. `#1234 - Bug foobar`). test suite.
+- `provision`: The JSON object that will be sent to the IoTA JSON provisioning API. This will be used to create the
+ group. It contains the following elements:
+ - `url`: The URL of the provisioning API (group)
+ - `method`: The HTTP method to use (POST)
+ - `json`: The JSON object that defines the group
+ - `headers`: The headers to send to the provisioning API. This should contain the `fiware-service` and
+ `fiware-servicepath` headers.
+ - `skip`: optional. Allow to skip test cases (at `describe` level). It allows diferent values: `false` (default,
+ meaning that the test is not skipped in any circustance), `true` (meaning the test is always skipped), `"lib"`
+ (meaning the test has to be skipped when running it in IoTA Node lib repo) and `"json"` (meaning the test has to
+ be skipped when running it in IOTA JSON repo). The latter alternatives are useful to skip test cases that are
+ not supported by the lib (I.E: all tests related to the transport) or by the IOTA. Combinations (e.g
+ `"lib,json"`) and negation (e.g. `"!lib"`) are also supported.
+- `should`: The array of test cases to execute. Each test case is defined as an object with the following elements:
+ - `transport`: The transport to use to send the measure. This can be `HTTP` or `MQTT`. It uses `HTTP` by default
+ or if the `transport` element is not defined. See the "Advanced features" section for more information.
+ - `shouldName`: The name of the `IT` test case. This will be used to generate the test case name in the mocha test
+ suite.
+ - `type`: The type of the test case. This can be `single`, `multimeasure` or `multientity`. See the "Advanced
+ features" section for more information.
+ - `measure`: The JSON object that will be sent to the IoTA JSON measure API. This will be used to send the
+ measure. It contains the following elements:
+ - `url`: The URL of the measure API (group)
+ - `method`: The HTTP method to use (POST)
+ - `qs`: The query string to send to the measure API. This should contain the `i` and `k` parameters.
+ - `json`: The JSON object that defines the measure
+ - `expectation`: The JSON object that defines the expectation. This will be used to check that the measure has
+ been correctly sent to the Context Broker.
+ - `loglevel`: optional. If set to `debug`, the agent will log all the debug messages to the console. This is
+ useful to check the messages sent to the Context Broker. See the "Debugging automated tests" section for more
+ information.
+ - `skip`: optional. If set to `true`, the test case (`it`) will be skipped. Same as the `skip` element in the
+ `provision` element.
+ - `isRegex`: optional. If set to `true`, then the expectation will be treated as a regular expression. This is
+ useful to check that the measure has been correctly sent to the Context Broker when the measure contains a a
+ variable parameter like a timestamp. See the "Advanced features" section for more information.
+
+#### Example
+
+```javascript
+{
+ describeName: 'Basic group provision with attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/iot/json',
+ apikey: '123456',
+ entity_type: 'TheLightType2',
+ cbHost: 'http://192.168.1.1:1026',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ },
+ should:[
+ {
+ shouldName: 'should send its value to the Context Broker',
+ config:{
+ type: 'single'
+ },
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: 'MQTT_2',
+ k: '123456'
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ },
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ }
+ },
+ {
+ transport: 'MQTT',
+ shouldName: 'should send its value to the Context Broker when using MQTT',
+ config:{
+ type: 'single'
+ },
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: 'MQTT_2',
+ k: '123456'
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ },
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ }
+ }
+ ]
+}
+```
+
+### Advanced features
+
+#### Multientity
+
+This test suite support the multientity feature. To test this feature, you need to set add to the test case the
+parameter `type: 'multientity'`. This will automatically take care of the multientity feature. This means that the suite
+will configure the mock to listen to `/v2/op/update` instead of `/v2/entities?options=upsert`.
+
+In particular, it will configure the mock to listen the correct URL. You should define the expectation for the test case
+as a batch operation (see the following example).
+
+```javascript
+{
+ "entities": [
+ {
+ "id": "TheLightType2:MQTT_2",
+ "type": "TheLightType2",
+ "status": {
+ "value": false,
+ "type": "Boolean"
+ }
+ },
+ {
+ "id": "TheLightType2:MQTT_3",
+ "type": "TheLightType2",
+ "temperature": {
+ "value": 10,
+ "type": "Number"
+ }
+ }
+ ],
+ "actionType": "append"
+}
+```
+
+#### Multimeasures
+
+It is also supported to test cases in which is sent more than one measure. To do so, you need to set add to the test
+case the parameter `should.type` to the value `'multimeasure'`.
+
+You must define the measure as multimeasure. This is done by defining the `measure` JSON element as an array of objects.
+I.E:
+
+```javascript
+measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: 'MQTT_2',
+ k: '123456'
+ },
+ json: [
+ {
+ s: false,
+ t: 21
+ },
+ {
+ s: true,
+ t: 22
+ },
+ {
+ s: false,
+ t: 23
+ }
+ ]
+}
+```
+
+And you should define the test case `expectation` as an object, following a Context Broker batch operation. I.E:
+
+```js
+expectation: {
+ actionType: 'append',
+ entities: [
+ {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ type: 'Number',
+ value: 21
+ },
+ status: {
+ type: 'Boolean',
+ value: false
+ }
+ },
+ {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ type: 'Number',
+ value: 22
+ },
+ status: {
+ type: 'Boolean',
+ value: true
+ }
+ },
+ {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ type: 'Number',
+ value: 23
+ },
+ status: {
+ type: 'Boolean',
+ value: false
+ }
+ }
+ ]
+}
+```
+
+Then, a batch request would be sent to the Context Broker containing the different measures. More information about how
+the IoT Agent send multimeasures to the Context Broker [here](/doc/api.md#multimeasure-support).
+
+#### Transport
+
+The test suite supports using the internal node lib function `iotAgentLib.update`, `HTTP` or `MQTT` for measure sending.
+In order to select the specific way to send the measure you should add a `transport` element to each should case having
+the value set to `MQTT`. By doing so, the suite will automatically configure the mock to connect to the MQTT broker and
+send the measure to the correct topic based on the `i` and `k` parameters. It will ignore the `url` and `method`
+parameters present in the measure JSON element. I.E:
+
+```javascript
+should: [
+ {
+ transport: 'MQTT',
+ shouldName: 'should send its value to the Context Broker when using MQTT',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: 'MQTT_2',
+ k: '123456'
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ id: 'TheLightType2:MQTT_2',
+ type: 'TheLightType2',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ },
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ }
+ }
+];
+```
+
+#### No payload reception
+
+The test suite also supports the case in which the Context Broker does not receive any payload. This is done by defining
+the expectation as an empty object. I.E:
+
+```javascript
+ ...
+ expectation: []
+ ...
+```
+
+Note that this means the CB _must not_ receive any payload. In other words, if the CB would receive any payload, the
+test will fail.
+
+### Debugging automated tests
+
+It is possible to debug the automated tests by using the loglevel parameter set to `debug` for each should case. This
+parameter configures the IoTA log level. By setting it to `debug`, the agent will log all the debug messages to the
+console. This is useful to check the messages sent to the Context Broker.
+
+It is also useful to debug the test by adding a breakpoint in the `testUtils.js` (read the comments in the file to know
+where to add the breakpoint). This will allow you to debug just that particular test case stopping the execution in the
+breakpoint.
+
+Example of a test case with the loglevel set to `debug`:
+
+```javascript
+should:[
+ {
+ shouldName: 'should send its value to the Context Broker when using MQTT',
+ loglevel: 'debug',
+ transport: 'MQTT',
+ config:{
+ type: 'single'
+ },
+ measure: {...},
+ expectation: {...}
+ }
+]
+```
+
+### Using expressions in the expectation
+
+It is possible to use expressions in the expectation. This is useful to check that the measure has been correctly sent
+to the Context Broker when the measure contains a variable parameter like a timestamp. To do so, you need to set the
+`isRegex` parameter to `true` in the test case. This will tell the test suite that the expectation can contain some
+expressions. The expression support is based on [Chai match pattern plugin](https://github.com/mjhm/chai-match-pattern).
+This plugin relies on [loadash match pattern](https://github.com/mjhm/lodash-match-pattern) to support the expressions.
+For further information about the expression support, check the documentation of the previous links.
+
+Even if setting the `isRegex` parameter to `true` make the test pass if the expectation does not contain any expression,
+it is recommended to do not set this parameter to `true` if the expectation does not contain any expression because the
+test result will be less clear if fails (it will show the error message of the expression library instead of the
+expected value).
+
+Here, you can find an example of a test case using expressions:
+
+```javascript
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 21,
+ type: 'Number',
+ metadata: {
+ TimeInstant: {
+ type: 'DateTime',
+ value: _.isDateString
+ }
+ }
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: _.isDateString
+ }
+ }
+```
+
+The previous example will check that the `TimeInstant` value and `attr_a.metadata.TimeInstant.value` are valid date.
diff --git a/test/functional/config-test.js b/test/functional/config-test.js
new file mode 100644
index 000000000..4bb2d2483
--- /dev/null
+++ b/test/functional/config-test.js
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of iotagent-json
+ *
+ * iotagent-json is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * iotagent-json is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with iotagent-json.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Miguel Angel Pedraza
+ */
+
+/* eslint-disable no-unused-vars */
+
+const config = {};
+
+config.mqtt = {
+ host: 'localhost',
+ port: 1883
+};
+
+config.http = {
+ port: 7896,
+ host: 'localhost'
+};
+
+config.amqp = {
+ port: 5672,
+ exchange: 'amq.topic',
+ queue: 'iota_queue',
+ options: { durable: true }
+};
+
+config.iota = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026',
+ ngsiVersion: 'v2'
+ },
+ server: {
+ port: 4041,
+ host: 'localhost'
+ },
+ deviceRegistry: {
+ type: 'memory'
+ },
+ service: 'smartgondor',
+ subservice: '/gardens',
+ providerUrl: 'http://localhost:4041',
+ types: {},
+ useCBflowControl: true
+};
+
+config.defaultKey = '1234';
+config.defaultTransport = 'MQTT';
+
+module.exports = config;
diff --git a/test/functional/functional-tests-runner.js b/test/functional/functional-tests-runner.js
new file mode 100755
index 000000000..9a732159d
--- /dev/null
+++ b/test/functional/functional-tests-runner.js
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of iotagent-json
+ *
+ * iotagent-json is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * iotagent-json is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with iotagent-json.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Miguel Angel Pedraza
+ */
+
+/* eslint-disable no-unused-vars*/
+/* eslint-disable no-unused-expressions*/
+
+const config = require('./config-test.js');
+const nock = require('nock');
+const chai = require('chai');
+const expect = chai.expect;
+const iotAgentLib = require('../../lib/fiware-iotagent-lib');
+const testUtils = require('./testUtils');
+const logger = require('logops');
+chai.config.truncateThreshold = 0;
+
+const baseTestCases = require('./testCases.js').testCases;
+
+const env = {
+ service: 'smartgondor',
+ servicePath: '/gardens'
+};
+
+// You can add here your own test cases to be executed in addition to the base ones
+// It is useful to test new features or to test specific scenarios. If you are going
+// to add a new test case, please, add it to the testCases.js file instead of adding
+// it here.
+let testCases = [];
+
+// If you want to execute only the test cases defined above, you can comment
+// the following line. Otherwise, the tests defined in testCases.js will be
+// executed as well.
+testCases = testCases.concat(baseTestCases);
+
+describe('FUNCTIONAL TESTS AUTO', function () {
+ testCases.forEach((testCase) => {
+ describe(testCase.describeName, function () {
+ beforeEach(function (done) {
+ if (testCase.skip && testUtils.checkSkip(testCase.skip, 'lib')) {
+ this.skip();
+ }
+ if (testCase.loglevel) {
+ logger.setLevel(testCase.loglevel);
+ }
+ let confType = testUtils.groupToIoTAConfigType(
+ testCase.provision.json.services[0],
+ testCase.provision.headers['fiware-service'],
+ testCase.provision.headers['fiware-servicepath']
+ );
+ // Inject device id into config as real typeInformation
+ if (testCase.should[0] && testCase.should[0].measure.qs.i) {
+ confType.type['id'] = testCase.should[0].measure.qs.i;
+ }
+ config.iota.types[confType.name] = confType.type;
+
+ iotAgentLib.activate(config.iota, function (error) {
+ done(error);
+ });
+ });
+
+ afterEach(function (done) {
+ logger.setLevel('FATAL');
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(function () {
+ iotAgentLib.setDataUpdateHandler();
+ iotAgentLib.setCommandHandler();
+ done();
+ });
+ });
+ });
+
+ testCase.should.forEach((should) => {
+ it(should.shouldName, async function () {
+ if (should.skip && testUtils.checkSkip(should.skip, 'lib')) {
+ this.skip();
+ }
+ // Skip the test if the transport is specified (IoTA Lib does not support any transport)
+ if (
+ should.transport &&
+ (should.transport === 'MQTT' || should.transport === 'AMQP' || should.transport === 'HTTP')
+ ) {
+ this.skip();
+ }
+
+ this.retries(2); // Retry if the test fails
+
+ if (should.loglevel) {
+ // You can use this line to set a breakpoint in the test in order to debug it
+ // You just need to add a loglevel element to the test case with the desired log level
+ // and then set a breakpoint in the next line. By default, the log level is FATAL and
+ // the following line will never be executed
+ logger.setLevel(should.loglevel);
+ } else {
+ await testUtils.testCase(
+ should.measure,
+ should.expectation,
+ testCase.provision,
+ env,
+ config,
+ should.type ? should.type : 'single',
+ should.transport,
+ should.isRegex ? should.isRegex : false
+ );
+ }
+ });
+ });
+ });
+ });
+});
diff --git a/test/functional/functional-tests.js b/test/functional/functional-tests.js
new file mode 100755
index 000000000..9023c11a6
--- /dev/null
+++ b/test/functional/functional-tests.js
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of iotagent-json
+ *
+ * iotagent-json is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * iotagent-json is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with iotagent-json.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Miguel Angel Pedraza
+ */
+
+/* eslint-disable no-unused-vars*/
+/* eslint-disable no-unused-expressions*/
+
+const config = require('./config-test.js');
+const nock = require('nock');
+const chai = require('chai');
+const expect = chai.expect;
+const iotAgentLib = require('../../lib/fiware-iotagent-lib');
+const async = require('async');
+const utils = require('../tools/utils');
+const testUtils = require('./testUtils');
+const request = utils.request;
+const logger = require('logops');
+var chaiMatchPattern = require('chai-match-pattern');
+
+chai.use(chaiMatchPattern);
+var _ = chaiMatchPattern.getLodashModule();
+let contextBrokerMock;
+
+const globalEnv = {
+ service: 'smartgondor',
+ servicePath: '/gardens',
+ apikey: '123456',
+ entity_type: 'TestType',
+ entity_name: 'TestType:TestDevice',
+ deviceId: 'TestDevice'
+};
+
+describe('FUNCTIONAL TESTS', function () {
+ describe('Basic group provision with attributes', function () {
+ const provision = {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ };
+
+ const measure = {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ };
+
+ const expectation = {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ temperature: {
+ value: 10,
+ type: 'Number'
+ },
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ };
+
+ beforeEach(function (done) {
+ let type = testUtils.groupToIoTAConfigType(
+ provision.json.services[0],
+ provision.headers['fiware-service'],
+ provision.headers['fiware-servicepath']
+ );
+ config.iota.types[type.name] = type.type;
+ iotAgentLib.activate(config.iota, function (error) {
+ done(error);
+ });
+ });
+
+ afterEach(function (done) {
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(function () {
+ iotAgentLib.setDataUpdateHandler();
+ iotAgentLib.setCommandHandler();
+ done();
+ });
+ });
+ });
+
+ it('should send its value to the Context Broker', async function () {
+ await testUtils.testCase(measure, expectation, provision, globalEnv, config, 'single', '');
+ });
+ });
+
+ describe('Basic group provision with attributes and multientity', function () {
+ const provision = {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number',
+ entity_name: 'TheLightType2:MQTT_3',
+ entity_type: 'TheLightType2'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ };
+
+ const measure = {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ };
+
+ const expectation = {
+ entities: [
+ {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ },
+ {
+ id: 'TheLightType2:MQTT_3',
+ type: 'TheLightType2',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ }
+ }
+ ],
+ actionType: 'append'
+ };
+
+ beforeEach(function (done) {
+ let type = testUtils.groupToIoTAConfigType(
+ provision.json.services[0],
+ provision.headers['fiware-service'],
+ provision.headers['fiware-servicepath']
+ );
+ config.iota.types[type.name] = type.type;
+ iotAgentLib.activate(config.iota, function (error) {
+ done(error);
+ });
+ });
+
+ afterEach(function (done) {
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(function () {
+ iotAgentLib.setDataUpdateHandler();
+ iotAgentLib.setCommandHandler();
+ done();
+ });
+ });
+ });
+
+ it('should send its value to the Context Broker', async function () {
+ await testUtils.testCase(measure, expectation, provision, globalEnv, config, 'multientity', '');
+ });
+ });
+});
diff --git a/test/functional/testCases.js b/test/functional/testCases.js
new file mode 100644
index 000000000..198b3b6d0
--- /dev/null
+++ b/test/functional/testCases.js
@@ -0,0 +1,4878 @@
+/*
+ * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of iotagent-json
+ *
+ * iotagent-json is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * iotagent-json is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with iotagent-json.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Miguel Angel Pedraza
+ */
+
+/* eslint-disable no-unused-vars*/
+/* eslint-disable no-unused-expressions*/
+
+const config = require('./config-test.js');
+var chai = require('chai');
+var chaiMatchPattern = require('chai-match-pattern');
+chai.use(chaiMatchPattern);
+var _ = chaiMatchPattern.getLodashModule();
+chai.config.truncateThreshold = 0;
+
+const globalEnv = {
+ service: 'smartgondor',
+ servicePath: '/gardens',
+ apikey: '123456',
+ entity_type: 'TestType',
+ entity_name: 'TestType:TestDevice',
+ deviceId: 'TestDevice'
+};
+
+const testCases = [
+ // 0000 - BASIC TESTS
+ {
+ describeName: '0010 - Simple group without attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ // loglevel: 'debug',
+ shouldName:
+ 'A - WHEN sending measures through http IT should send measures to Context Broker preserving value types',
+ config: {
+ type: 'single'
+ },
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ b: 10,
+ c: 'text',
+ d: 10.5,
+ e: [1, 2],
+ f: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ a: {
+ value: false,
+ type: 'Text'
+ },
+ b: {
+ value: 10,
+ type: 'Text'
+ },
+ c: {
+ type: 'Text',
+ value: 'text'
+ },
+ d: {
+ type: 'Text',
+ value: 10.5
+ },
+ e: {
+ type: 'Text',
+ value: [1, 2]
+ },
+ f: {
+ type: 'Text',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ },
+ {
+ transport: 'MQTT',
+ shouldName:
+ 'B - WHEN sending measures through mqtt IT should send measures to Context Broker preserving value types',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ b: 10,
+ c: 'text',
+ d: 10.5,
+ e: [1, 2],
+ f: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ a: {
+ value: false,
+ type: 'Text'
+ },
+ b: {
+ value: 10,
+ type: 'Text'
+ },
+ c: {
+ type: 'Text',
+ value: 'text'
+ },
+ d: {
+ type: 'Text',
+ value: 10.5
+ },
+ e: {
+ type: 'Text',
+ value: [1, 2]
+ },
+ f: {
+ type: 'Text',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0020 - Simple group with active attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean'
+ },
+ {
+ object_id: 'b',
+ name: 'attr_b',
+ type: 'Integer'
+ },
+ {
+ object_id: 'c',
+ name: 'attr_c',
+ type: 'Text'
+ },
+ {
+ object_id: 'd',
+ name: 'attr_d',
+ type: 'Float'
+ },
+ {
+ object_id: 'e',
+ name: 'attr_e',
+ type: 'Array'
+ },
+ {
+ object_id: 'f',
+ name: 'attr_f',
+ type: 'Object'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types and name mappings',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ b: 10,
+ c: 'text',
+ d: 10.5,
+ e: [1, 2],
+ f: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean'
+ },
+ attr_b: {
+ value: 10,
+ type: 'Integer'
+ },
+ attr_c: {
+ type: 'Text',
+ value: 'text'
+ },
+ attr_d: {
+ type: 'Float',
+ value: 10.5
+ },
+ attr_e: {
+ type: 'Array',
+ value: [1, 2]
+ },
+ attr_f: {
+ type: 'Object',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending defined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types and name mappings',
+ transport: 'MQTT',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ b: 10,
+ c: 'text',
+ d: 10.5,
+ e: [1, 2],
+ f: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean'
+ },
+ attr_b: {
+ value: 10,
+ type: 'Integer'
+ },
+ attr_c: {
+ type: 'Text',
+ value: 'text'
+ },
+ attr_d: {
+ type: 'Float',
+ value: 10.5
+ },
+ attr_e: {
+ type: 'Array',
+ value: [1, 2]
+ },
+ attr_f: {
+ type: 'Object',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN sending undefined object_ids (measures) through http IT should send measures to Context Broker preserving value types',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ u: false,
+ v: 10,
+ w: 'text',
+ y: 10.5,
+ x: [1, 2],
+ z: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ u: {
+ value: false,
+ type: 'Text'
+ },
+ v: {
+ value: 10,
+ type: 'Text'
+ },
+ w: {
+ type: 'Text',
+ value: 'text'
+ },
+ y: {
+ type: 'Text',
+ value: 10.5
+ },
+ x: {
+ type: 'Text',
+ value: [1, 2]
+ },
+ z: {
+ type: 'Text',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN sending undefined object_ids (measures) through mqtt IT should send measures to Context Broker preserving value types',
+ transport: 'MQTT',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ u: false,
+ v: 10,
+ w: 'text',
+ y: 10.5,
+ x: [1, 2],
+ z: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ u: {
+ value: false,
+ type: 'Text'
+ },
+ v: {
+ value: 10,
+ type: 'Text'
+ },
+ w: {
+ type: 'Text',
+ value: 'text'
+ },
+ y: {
+ type: 'Text',
+ value: 10.5
+ },
+ x: {
+ type: 'Text',
+ value: [1, 2]
+ },
+ z: {
+ type: 'Text',
+ value: {
+ a: 1,
+ b: 2
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0021 - Simple group with active attributes with metadata',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types, name mappings and metadatas',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean',
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0021b - Simple group with active attributes with special names in object_id',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ },
+ {
+ object_id: '.1.0.0.1',
+ name: 'psBatteryVoltage',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids with special format names (measures) through http IT should send measures to Context Broker preserving value types, name mappings and metadatas',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ '.1.0.0.1': 23.5
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean',
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ },
+ psBatteryVoltage: {
+ type: 'Number',
+ value: 23.5
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0021c - Simple group with active attributes with special names in object_id',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'mainDoorEvent',
+ type: 'Boolean'
+ },
+ {
+ name: 'psSMDUHPower14',
+ type: 'Text',
+ object_id: '.1.3.6.1.4.1.6302.2.1.2.17.1.1.15.1702'
+ },
+ {
+ name: 'psSMDUHPower17',
+ type: 'Number',
+ object_id: '.1.3.6.1.4.1.6302.2.1.2.17.1.1.18.1702'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids with special format names in attributes through http IT should send measures to Context Broker preserving value types, name mappings and metadatas',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ '.1.3.6.1.4.1.6302.2.1.2.17.1.1.18.1702': 3,
+ mainDoorEvent: 1,
+ psSMDUHPower14: 2
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ mainDoorEvent: {
+ type: 'Boolean',
+ value: 1
+ },
+ psSMDUHPower17: {
+ type: 'Number',
+ value: 3
+ },
+ psSMDUHPower14: {
+ type: 'Text',
+ value: 2
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0022 - Simple group with active attributes and multimeasures',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types and name mappings',
+ type: 'multimeasure',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: [
+ {
+ a: 0
+ },
+ {
+ a: 1
+ },
+ {
+ a: 2
+ },
+ {
+ a: 3
+ },
+ {
+ a: 4
+ },
+ {
+ a: 5
+ },
+ {
+ a: 6
+ }
+ ]
+ },
+ expectation: {
+ actionType: 'append',
+ entities: [
+ {
+ attr_a: {
+ type: 'Number',
+ value: 0
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 1
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 2
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 3
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 4
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 5
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 6
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ }
+ ]
+ }
+ },
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids (measures) through http IT should send measures with TimeInstant to Context Broker preserving value types and name mappings and order',
+ type: 'multimeasure',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: [
+ {
+ a: 0,
+ TimeInstant: '2024-04-10T10:00:00Z'
+ },
+ {
+ a: 1,
+ TimeInstant: '2024-04-10T10:05:00Z'
+ },
+ {
+ a: 2,
+ TimeInstant: '2024-04-10T10:10:00Z'
+ },
+ {
+ a: 3,
+ TimeInstant: '2024-04-10T10:15:00Z'
+ },
+ {
+ a: 4,
+ TimeInstant: '2024-04-10T10:20:00Z'
+ },
+ {
+ a: 5,
+ TimeInstant: '2024-04-10T10:25:00Z'
+ },
+ {
+ a: 6,
+ TimeInstant: '2024-04-10T10:30:00Z'
+ }
+ ]
+ },
+ expectation: {
+ actionType: 'append',
+ entities: [
+ {
+ attr_a: {
+ type: 'Number',
+ value: 0
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:00:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 1
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:05:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 2
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:10:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 3
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:15:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 4
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:20:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 5
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:25:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 6
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:30:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ }
+ ]
+ }
+ },
+ {
+ shouldName:
+ 'A - WHEN sending defined object_ids (measures) through http IT should send measures with TimeInstant to Context Broker preserving value types and name mappings and sorted by TimeInstant',
+ type: 'multimeasure',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: [
+ {
+ a: 0,
+ TimeInstant: '2024-04-10T10:15:00Z'
+ },
+ {
+ a: 1,
+ TimeInstant: '2024-04-10T10:05:00Z'
+ },
+ {
+ a: 2,
+ TimeInstant: '2024-04-10T10:20:00Z'
+ },
+ {
+ a: 3,
+ TimeInstant: '2024-04-10T10:00:00Z'
+ },
+ {
+ a: 4,
+ TimeInstant: '2024-04-10T10:10:00Z'
+ },
+ {
+ a: 5,
+ TimeInstant: '2024-04-10T10:30:00Z'
+ },
+ {
+ a: 6,
+ TimeInstant: '2024-04-10T10:25:00Z'
+ }
+ ]
+ },
+ expectation: {
+ actionType: 'append',
+ entities: [
+ {
+ attr_a: {
+ type: 'Number',
+ value: 3
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:00:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 1
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:05:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 4
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:10:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 0
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:15:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 2
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:20:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 6
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:25:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ },
+ {
+ attr_a: {
+ type: 'Number',
+ value: 5
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2024-04-10T10:30:00Z'
+ },
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type
+ }
+ ]
+ }
+ }
+ ]
+ },
+ // 0100 - JEXL TESTS
+ {
+ describeName: '0100 - Simple group with active attribute + JEXL expression boolean (!)',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ expression: '!a'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a boolean value (false) through http IT should send to Context Broker the value true ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: true,
+ type: 'Boolean'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a numeric value (3) through http IT should send to Context Broker the value false ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN sending a text value (abcd) through http IT should send to Context Broker the value false ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 'abcd'
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: false,
+ type: 'Boolean'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN not sending the object ID (undefined) required by the expression through http IT should send to Context Broker the value true ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 1
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: true,
+ type: 'Boolean'
+ },
+ b: {
+ value: 1,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0110 - Simple group with active attribute + JEXL expression numeric (+3)',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a+3'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 4 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: true
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 4,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 3 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -4 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: -7
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: -4,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN sending a text value (abcd) through http IT should send to Context Broker the value abcd3',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 'abcd'
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 'abcd3',
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 1
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 1,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0120 - Simple group with active attribute + JEXL expression numeric (*3)',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a*3'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 3 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: true
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value 0',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 0,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value -21 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: -7
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: -21,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN sending a text value (abcd) through http IT should not send to anything 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: 'abcd'
+ }
+ },
+ expectation: []
+ },
+ {
+ shouldName:
+ 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 1
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 1,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0130 - Simple group with active attribute + JEXL expression text (a|substr(0,2))',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a|substr(0,3)'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value "tru" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: true
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 'tru',
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a boolean value (false) through http IT should send to Context Broker the value "fal"',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 'fal',
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN sending a numeric value (-7) through http IT should send to Context Broker the value "-7" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: -7
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: '-7',
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN sending a text value (abcd) through http IT should not send to anything 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: 'abcd'
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 'abc',
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'E - WHEN not sending the object ID (undefined) required by the expression through http IT should not send that attribute to Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 1
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 'und',
+ type: 'Number'
+ },
+ b: {
+ value: 1,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ 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',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'a',
+ type: 'Text',
+ expression: 'a | trim | replacestr("hello","hi")'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a expected value ("Say hello and smile") through http IT should send to Context Broker the value "Say hi and smile" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 'Say hello and smile'
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ a: {
+ value: 'Say hi and smile',
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0150 - Simple group with active attribute + JEXL expression text reusing previous values)',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a*10'
+ },
+ {
+ object_id: 'b',
+ name: 'attr_b',
+ type: 'Number',
+ expression: 'attr_a*10'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a value (a:3) through http IT should apply nested expressions and send to Context Broker the value "attr_b=300" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 30,
+ type: 'Number'
+ },
+ attr_b: {
+ value: 300,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0160 - Simple group with active attribute + JEXL expression referencing static attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a*coef'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'coef',
+ type: 'Number',
+ value: 1.5
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a value (a:6) through http IT should apply the expression using the static attribute value and send to Context Broker the value "attr_a:9" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 6
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 9,
+ type: 'Number'
+ },
+ coef: {
+ value: 1.5,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0170 - Simple group with active attribute + JEXL expression referencing context attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Text',
+ expression: 'a+":"+service+subservice+id+type'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a value (text) through http IT should apply the expression using the context attributes value and send to Context Broker the value "text:smartgondor/gardensTestDeviceTestType" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 'text'
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value:
+ 'text:' +
+ globalEnv.service +
+ globalEnv.servicePath +
+ globalEnv.deviceId +
+ globalEnv.entity_type,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName:
+ '0175 - Simple group with active attribute + JEXL expression referencing metadata context attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ static_attributes: [
+ {
+ name: 'st_attr1',
+ type: 'Number',
+ value: 1.5,
+ metadata: {
+ coef1: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ },
+ {
+ name: 'st_attr',
+ type: 'Number',
+ value: 1.5,
+ metadata: {
+ coef: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ },
+ {
+ name: 'st_attr2',
+ type: 'Number',
+ value: 1.5,
+ metadata: {
+ coef2: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ }
+ ],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a*st_attr*metadata.st_attr.coef'
+ }
+ ],
+ explicitAttrs: "['attr_a']"
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a value (number) through http IT should apply the expression using the context attributes value and send to Context Broker the value "39.60" ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 33
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 39.6,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0180 - Simple group with active attributes + JEXL multiples expressions at same time',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ expression: '!a'
+ },
+ {
+ object_id: 'b',
+ name: 'attr_b',
+ type: 'Integer',
+ expression: 'b+1'
+ },
+ {
+ object_id: 'c',
+ name: 'attr_c',
+ type: 'Text',
+ expression: 'c+":"+service+subservice+id+type'
+ },
+ {
+ object_id: 'd',
+ name: 'attr_d',
+ type: 'Float',
+ expression: 'd/2+attr_b'
+ },
+ {
+ object_id: 'e',
+ name: 'attr_e',
+ type: 'Array',
+ expression: 'e|concatarr([3,4])'
+ },
+ {
+ object_id: 'f',
+ name: 'attr_f',
+ type: 'Object',
+ expression: '{coordinates: [f.a,f.b], type: "Point"}'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending multiples object_ids (measures) through http IT should send measures to Context Broker applying all expressions at same time',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false,
+ b: 10,
+ c: 'text',
+ d: 10.5,
+ e: [1, 2],
+ f: { a: 1, b: 2 }
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: true,
+ type: 'Boolean'
+ },
+ attr_b: {
+ value: 11,
+ type: 'Integer'
+ },
+ attr_c: {
+ type: 'Text',
+ value:
+ 'text:' +
+ globalEnv.service +
+ globalEnv.servicePath +
+ globalEnv.deviceId +
+ globalEnv.entity_type
+ },
+ attr_d: {
+ type: 'Float',
+ value: 16.25
+ },
+ attr_e: {
+ type: 'Array',
+ value: [1, 2, 3, 4]
+ },
+ attr_f: {
+ type: 'Object',
+ value: { coordinates: [1, 2], type: 'Point' }
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName:
+ '0190 - Simple group with JEXL expression using static attribute - addressing structured values (JSON)',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ expression: 'a?threshold[90|tostring].max:true'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'threshold',
+ type: 'Object',
+ value: {
+ '90': { max: 10, min: 1 },
+ '92': { max: 12, min: 2 },
+ '93': { max: 13, min: 3 }
+ }
+ }
+ ],
+ explicitAttrs: "['attr_a']"
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a numeric value () through http IT should send to Context Broker the value true ',
+ type: 'single',
+ isRegex: true,
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: true,
+ type: 'Boolean'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0191 - Simple group with JEXL expression in metadata',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Boolean',
+ expression: 'a?threshold[90|tostring].max:true',
+ metadata: {
+ unit: {
+ type: 'Text',
+ expression: '"hola" + "adios"'
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a measure through http IT should send to Context Broker expanded metadata value ',
+ type: 'single',
+ isRegex: true,
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: false
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: true,
+ type: 'Boolean',
+ metadata: {
+ unit: {
+ type: 'Text',
+ value: 'holaadios'
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ // 0200 - COMMANDS TESTS
+ {
+ describeName: '0200 - Simple group with commands',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [
+ {
+ name: 'cmd1',
+ type: 'command'
+ }
+ ],
+ endpoint: 'http://myendpoint.com',
+ transport: 'http',
+ lazy: [],
+ attributes: [],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending not provisioned object_ids (measures) through http IT should store commands into Context Broker',
+ type: 'single',
+ skip: '!lib', // there is not CB registration mock
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 10,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0201 - Simple groups with commands',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [
+ {
+ name: 'cmd1',
+ type: 'command'
+ }
+ ],
+ endpoint: 'http://myendpoint.com',
+ transport: 'http',
+ lazy: [],
+ attributes: [],
+ static_attributes: []
+ },
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey + 'OTHER',
+ entity_type: globalEnv.entity_type,
+ commands: [
+ {
+ name: 'cmd1',
+ type: 'command'
+ }
+ ],
+ transport: 'mqtt',
+ lazy: [],
+ attributes: [],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending not provisioned object_ids (measures) through http IT should store commands into Context Broker',
+ type: 'single',
+ skip: '!lib', // there is not CB registration mock
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 10,
+ type: 'Text'
+ }
+ }
+ }
+ ]
+ },
+ // 0300 - STATIC ATTRIBUTES TESTS
+ {
+ describeName:
+ '0300 - Simple group with active attributes + Static attributes + JEXL expression using static attribute value',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ expression: 'a*static_a'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending not provisioned object_ids (measures) through http IT should store static attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 10,
+ type: 'Text'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending provisioned object_ids (measures) through http IT should store static attributes into Context Broker + calculate expression based on static',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 30,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0310 - Simple group with active attributes + JSON value Static attributes ',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: {
+ v1: 1,
+ v2: {
+ v3: 3,
+ v4: 4
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending measures through http IT should store complex static attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ a: {
+ value: 10,
+ type: 'Text'
+ },
+ static_a: {
+ value: {
+ v1: 1,
+ v2: {
+ v3: 3,
+ v4: 4
+ }
+ },
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0320 - Simple group with active attributes + metadata in Static attributes ',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 4,
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending measures through http IT should store metatada static attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ a: {
+ value: 10,
+ type: 'Text'
+ },
+ static_a: {
+ value: 4,
+ type: 'Number',
+ metadata: {
+ accuracy: {
+ value: 0.8,
+ type: 'Float'
+ }
+ }
+ }
+ }
+ }
+ ]
+ },
+ // 0400 - TIMESTAMP TESTS
+ {
+ describeName: '0400 - Simple group with active attribute + timestamp:false',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ timestamp: false,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a measure not named TimeInstant through http IT should not add the timestamp to the 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,
+ attr_a: {
+ value: 23,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other 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: {
+ TimeInstant: '2015-12-14T08:06:01.468Z',
+ a: 23
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 23,
+ type: 'Number'
+ },
+ TimeInstant: {
+ value: '2015-12-14T08:06:01.468Z',
+ type: 'DateTime'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0410 - Simple group with active attribute + timestamp:true',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ timestamp: true,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a measure not named TimeInstant through http IT should add the timestamp to the 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: 21
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 21,
+ type: 'Number',
+ metadata: {
+ TimeInstant: {
+ type: 'DateTime',
+ value: _.isDateString
+ }
+ }
+ },
+ TimeInstant: {
+ type: 'DateTime',
+ value: _.isDateString
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other 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: {
+ TimeInstant: '2015-12-14T08:06:01.468Z',
+ a: 23
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 23,
+ type: 'Number',
+ metadata: {
+ TimeInstant: {
+ type: 'DateTime',
+ value: '2015-12-14T08:06:01.468Z'
+ }
+ }
+ },
+ TimeInstant: {
+ value: '2015-12-14T08:06:01.468Z',
+ type: 'DateTime'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0420 - Simple group with active attribute + timestamp not defined',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a measure not named TimeInstant through http IT should not add the timestamp to the attributes or metadata 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: 21
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 21,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN sending a measure named TimeInstant through http IT should not add the timestamp to the other 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: {
+ TimeInstant: '2015-12-14T08:06:01.468Z',
+ a: 23
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 23,
+ type: 'Number'
+ },
+ TimeInstant: {
+ value: '2015-12-14T08:06:01.468Z',
+ type: 'DateTime'
+ }
+ }
+ }
+ ]
+ },
+ {
+ 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 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: '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 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',
+ 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',
+ 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'
+ }
+ }
+ }
+ ]
+ },
+ {
+ 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',
+ 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: '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'
+ }
+ },
+ {
+ 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 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'
+ }
+ }
+ ]
+ }
+ }
+ ]
+ },
+
+ // 0500 - EXPLICIT ATTRIBUTES TESTS
+ {
+ describeName: '0500 - Group with explicit attrs:false (boolean) + active atributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: false,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should store all attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 10,
+ type: 'Text'
+ },
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0510 - Group without explicit (not defined) + active atributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should store all attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ b: {
+ value: 10,
+ type: 'Text'
+ },
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0520 - Group with explicit attrs:true (boolean) + active atributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: true,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should store all attributes into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0530 - Group with explicit attrs:[array] + active attributes + static attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: "['attr_a','static_a']",
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should store only defined in explicitAttrs array into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0531 - Group with explicit attrs:[ ] (empty array) + active attributes + static attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: '[ ]',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11
+ }
+ },
+ expectation: [] // No payload expected
+ }
+ ]
+ },
+ {
+ describeName:
+ '0532 - Group with explicit attrs:[ ] (empty array) + active attributes + static attributes + timestamp:true',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: '[ ]',
+ timestamp: true,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11
+ }
+ },
+ expectation: [] // No payload expected
+ },
+ {
+ shouldName:
+ 'B - WHEN sending data and a measure called TimeInstant through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ TimeInstant: '2015-12-14T08:06:01.468Z'
+ }
+ },
+ expectation: [] // No payload expected
+ }
+ ]
+ },
+ {
+ describeName:
+ '0533 - Group with explicit attrs:[ ] (empty array) + active attributes + TimeInstant attribute + static attributes + timestamp:true',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: '[ ]',
+ timestamp: true,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ },
+ {
+ name: 'DateIssued',
+ object_id: 'TimeInstant',
+ type: 'DateTime'
+ },
+ {
+ name: 'TimeInstant',
+ object_id: 't',
+ type: 'DateTime'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending data and a measure called "t" (defined as TimeInstant attribte) through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ t: '2015-12-14T08:06:01.468Z'
+ }
+ },
+ expectation: []
+ },
+ {
+ shouldName:
+ 'B - WHEN sending data and a measure called TimeInstant through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ TimeInstant: '2015-12-14T08:06:01.468Z'
+ }
+ },
+ expectation: [] // No payload expected
+ }
+ ]
+ },
+ {
+ describeName:
+ '0534 - Group with explicit attrs:[ ] (empty array) + active attributes + TimeInstant attribute + static attributes + timestamp:false',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: '[ ]',
+ timestamp: false,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ },
+ {
+ name: 'DateIssued',
+ object_id: 'TimeInstant',
+ type: 'DateTime'
+ },
+ {
+ name: 'TimeInstant',
+ object_id: 't',
+ type: 'DateTime'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending data and a measure called "t" (defined as TimeInstant attribte) through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ t: '2015-12-14T08:06:01.468Z'
+ }
+ },
+ expectation: []
+ },
+ {
+ shouldName:
+ 'B - WHEN sending data and a measure called TimeInstant through http IT should NOT store anything into the Context Broker (No request to CB)',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ TimeInstant: '2015-12-14T08:06:01.468Z'
+ }
+ },
+ expectation: [] // No payload expected
+ }
+ ]
+ },
+ {
+ describeName:
+ '0540 - Group with explicit attrs: JEXL expression based on measure resulting boolean + active attributes + static attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: 'c?true:false',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http and being the explicitAttrs JEXL result true IT should store only explicit attrs into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ c: 11,
+ d: 12
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ },
+ attr_b: {
+ value: 10,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ },
+ static_b: {
+ value: 4,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'A - WHEN sending both provisioned and not object_ids (measures) through http and being the explicitAttrs JEXL result false IT should store all attrs into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10,
+ d: 12
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ },
+ attr_b: {
+ value: 10,
+ type: 'Number'
+ },
+ d: {
+ value: 12,
+ type: 'Text'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ },
+ static_b: {
+ value: 4,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ {
+ describeName:
+ '0550 - Group with explicit attrs: JEXL expression based on measure resulting an array + active attributes + static attributes',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs:
+ "(a&&b)?['attr_a','attr_b']:a?['attr_a','static_b']:b?[{object_id:'b'},'c']:['static_a','static_b','d','c']",
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'attr_a',
+ object_id: 'a',
+ type: 'Number'
+ },
+ {
+ name: 'attr_b',
+ object_id: 'b',
+ type: 'Number'
+ },
+ {
+ object_id: 'c',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'static_b',
+ type: 'Number',
+ value: 4
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName: 'A - WHEN sending (a&&b) IT should store only [attr_a,attr_b] attrs into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 1,
+ b: 2,
+ c: 3,
+ d: 4
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 1,
+ type: 'Number'
+ },
+ attr_b: {
+ value: 2,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName: 'B - WHEN sending only a IT should store only [attr_a,static_b] attrs into Context Broker',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 1,
+ c: 3,
+ d: 4
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 1,
+ type: 'Number'
+ },
+ static_b: {
+ value: 4,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName: 'C - WHEN sending only b IT should store only [{object_id:b}] attrs into Context Broker',
+ type: 'single',
+ skip: '!lib', // FIXME: https://github.com/telefonicaid/iotagent-json/issues/782
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: 2,
+ c: 3,
+ d: 4
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_b: {
+ value: 2,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'D - WHEN no sending any defined case IT should store [static_a,static_b] attrs into Context Broker',
+ type: 'single',
+ skip: '!lib', // FIXME: https://github.com/telefonicaid/iotagent-json/issues/782
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ c: 3,
+ d: 4
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ static_a: {
+ value: 3,
+ type: 'Number'
+ },
+ static_b: {
+ value: 4,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ // 0600 - MULTIENTITY TESTS
+ {
+ describeName: '0600 - Group multientity attrs',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number',
+ entity_name: 'TestType:TestDevice2',
+ entity_type: 'TestType'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending provisioned object_ids (measures) through http IT should store both entities into Context Broker',
+ type: 'multientity',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ entities: [
+ {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ status: {
+ value: false,
+ type: 'Boolean'
+ }
+ },
+ {
+ id: 'TestType:TestDevice2',
+ type: 'TestType',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ }
+ }
+ ],
+ actionType: 'append'
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0610 - Group multientity attrs + value JEXL expression',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number',
+ entity_name: 'TestType:TestDevice2',
+ entity_type: 'TestType',
+ expression: 'type+":"+(t*2*static_a)'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending provisioned object_ids (measures) through http IT should store both entities into Context Broker',
+ type: 'multientity',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ entities: [
+ {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ status: {
+ value: false,
+ type: 'Boolean'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ },
+ {
+ id: 'TestType:TestDevice2',
+ type: 'TestType',
+ temperature: {
+ value: 'TestType:60',
+ type: 'Number'
+ }
+ }
+ ],
+ actionType: 'append'
+ }
+ }
+ ]
+ },
+ {
+ describeName: '0620 - Group multientity attrs + multientity ID JEXL expression',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number',
+ entity_name: 'type+":"+(t*2*static_a)',
+ entity_type: 'TestType'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending provisioned object_ids (measures) through http IT should store both entities into Context Broker',
+ type: 'multientity',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ entities: [
+ {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ status: {
+ value: false,
+ type: 'Boolean'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ },
+ {
+ id: 'TestType:60',
+ type: 'TestType',
+ temperature: {
+ value: 10,
+ type: 'Number'
+ }
+ }
+ ],
+ actionType: 'append'
+ }
+ }
+ ]
+ },
+ // 0700 - ENTITY NAME EXPRESSION TESTS
+ {
+ describeName: '0700 - Group JEXL entityNameExpression',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ entityNameExp: 'type+":"+(t*2*static_a)',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 's',
+ name: 'status',
+ type: 'Boolean'
+ },
+ {
+ object_id: 't',
+ name: 'temperature',
+ type: 'Number'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending provisioned object_ids (measures) through http IT should store the entity with the expression generated ID into the Context Broker',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ s: false,
+ t: 10
+ }
+ },
+ expectation: {
+ id: 'TestType:60',
+ type: globalEnv.entity_type,
+ status: {
+ value: false,
+ type: 'Boolean'
+ },
+ temperature: {
+ value: 10,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ // 0700 - FULL GROUP TESTS
+ // FIXME: APIKEY recognition is not working for lib, and it needs to be tested if works as it is in agents.
+ // 0800 - SKIP VALUE
+ {
+ describeName: '0700 - skipValue test',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'a',
+ name: 'attr_a',
+ type: 'Number',
+ skipValue: null
+ },
+ {
+ object_id: 'b',
+ name: 'attr_b',
+ type: 'Number',
+ skipValue: 'skip',
+ expression: '(b+static_a)>0?(b+static_a):"skip"'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'static_a',
+ type: 'Number',
+ value: 3
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN not matching skip conditions IT should store the entity with all the values (attr_a, attr_b, static_a) into the Context Broker',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: 10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ },
+ attr_b: {
+ value: 13,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'B - WHEN matching skip conditions for attr_b IT should store the entity with some of the values (attr_a, static_a) into the Context Broker',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: 3,
+ b: -10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ attr_a: {
+ value: 3,
+ type: 'Number'
+ },
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ },
+ {
+ shouldName:
+ 'C - WHEN matching skip conditions for attr_a and attr_b (expression result) IT should store the entity with only some of the values (static_a) into the Context Broker',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ b: -10
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ static_a: {
+ value: 3,
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ },
+ // 0900 - JEXL FUNCTION TESTS
+ {
+ describeName: '0900 - JEXL function - valuePicker and valuePickerMulti',
+ provision: {
+ url: 'http://localhost:' + config.iota.server.port + '/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/iot/json',
+ apikey: globalEnv.apikey,
+ entity_type: globalEnv.entity_type,
+ explicitAttrs: true,
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ object_id: 'single',
+ name: 'single',
+ type: 'Number',
+ expression: '{alarm1:alarm1,alarm2:alarm2,alarm3:alarm3}|valuePicker(true)'
+ },
+ {
+ object_id: 'multi',
+ name: 'multi',
+ type: 'Number',
+ expression: "a|valuePickerMulti([true,1,'on','nok'])"
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': globalEnv.service,
+ 'fiware-servicepath': globalEnv.servicePath
+ }
+ },
+ should: [
+ {
+ shouldName:
+ 'A - WHEN sending a boolean value (true) through http IT should send to Context Broker the value 3 ',
+ type: 'single',
+ measure: {
+ url: 'http://localhost:' + config.http.port + '/iot/json',
+ method: 'POST',
+ qs: {
+ i: globalEnv.deviceId,
+ k: globalEnv.apikey
+ },
+ json: {
+ a: { n1: true, n2: 1, n3: 'on', n4: 'nok', n5: 'ok', n6: true, n7: false, n8: 0 },
+ alarm1: true,
+ alarm2: false,
+ alarm3: true
+ }
+ },
+ expectation: {
+ id: globalEnv.entity_name,
+ type: globalEnv.entity_type,
+ single: {
+ value: ['alarm1', 'alarm3'],
+ type: 'Number'
+ },
+ multi: {
+ value: ['n1', 'n2', 'n3', 'n4', 'n6'],
+ type: 'Number'
+ }
+ }
+ }
+ ]
+ }
+];
+
+exports.testCases = testCases;
diff --git a/test/functional/testUtils.js b/test/functional/testUtils.js
new file mode 100644
index 000000000..4eb784389
--- /dev/null
+++ b/test/functional/testUtils.js
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2023 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of iotagent-json
+ *
+ * iotagent-json is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * iotagent-json is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with iotagent-json.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Miguel Angel Pedraza
+ */
+
+/* eslint-disable no-unused-vars*/
+/* eslint-disable no-unused-expressions*/
+
+const nock = require('nock');
+const utils = require('../tools/utils');
+const request = utils.request;
+const async = require('async');
+const chai = require('chai');
+const MQTT = require('async-mqtt');
+const iotAgentLib = require('../../lib/fiware-iotagent-lib');
+
+var chaiMatchPattern = require('chai-match-pattern');
+chai.use(chaiMatchPattern);
+var _ = chaiMatchPattern.getLodashModule();
+var expect = chai.expect;
+chai.config.truncateThreshold = 0;
+
+// Error messages
+const ERR_CB_EXPECTATION_DIFFER = 'Assertion Error - Context Broker received payload differs from expectation';
+const ERR_MEAS_BODY = 'Assertion Error - Measure response is not empty';
+const ERR_MEAS_CODE = 'Assertion Error - Measure response status code differs from 200';
+const ERR_MQTT = 'Error with MQTT: ';
+const ERR_CB_NOT_EMPTY = 'Assertion Error - unexpected Context Broker request received (no request expected)';
+const DEF_TYPE = 'TestType';
+/**
+ * @brief Sends a measure to the IoT Agent and returns a promise with the response
+ *
+ * @param {Object} measure Measure to be sent to the IoT Agent
+ */
+function sendMeasureHttp(measure) {
+ return new Promise((resolve, reject) => {
+ request(measure, function (error, result, body) {
+ error ? reject(error) : resolve(result);
+ });
+ });
+}
+
+/**
+ * @brief Sends a measure to the IoT Agent and returns a promise with the response
+ *
+ * @param {Object} measure Measure to be sent to the IoT Agent
+ */
+function sendMeasureIotaLib(measure, provision) {
+ return new Promise((resolve, reject) => {
+ /**
+ * WARNING: This is kind of a hack, only required for the tests using Lib, since the method iotAgentLib.update
+ * requires a type and does not check the type of the group. For this purpose, this function uses the
+ * provision type, setting the measure type to the same type as the provision.
+ * This is not a problem for the tests using other transports than Lib, in that case, the type will be retrieved
+ * from the real provision.
+ */
+ let typeInformation = {
+ service: provision.headers['fiware-service'],
+ subservice: provision.headers['fiware-servicepath']
+ };
+ let type;
+ let staticAttrs;
+ if (Array.isArray(provision.json.services) && provision.json.services.length > 0) {
+ type = provision.json.services[0].entity_type;
+ staticAttrs = provision.json.services[0].static_attributes;
+ } else {
+ type = DEF_TYPE;
+ }
+ typeInformation.type = type;
+ if (staticAttrs) {
+ typeInformation.staticAttributes = staticAttrs;
+ }
+ typeInformation.id = measure.qs.i;
+ iotAgentLib.update(
+ type + ':' + measure.qs.i,
+ type,
+ typeInformation,
+ jsonToIotaMeasures(measure.json),
+ function (error, result, body) {
+ error ? reject(error) : resolve(result);
+ }
+ );
+ });
+}
+
+/**
+ * @brief Converts a IOTA JSON object to an array of measures as expected by the IOTA Lib
+ *
+ * @param {Object} json
+ * @returns {Array} measures
+ */
+function jsonToIotaMeasures(originJson) {
+ // FIXME: maybe this could be refactored to use less code
+ if (originJson && originJson[0]) {
+ // multimeasure case
+ let finalMeasures = [];
+
+ for (let json of originJson) {
+ let measures = [];
+ for (let key in json) {
+ /* eslint-disable-next-line no-prototype-builtins */
+ if (json.hasOwnProperty(key)) {
+ let measure = {
+ name: key,
+ value: json[key]
+ };
+ // A bit of Magic. If the key is TimeInstant, we set the type to DateTime.
+ // When sending the data through iot
+ if (key === 'TimeInstant') {
+ measure.type = 'DateTime';
+ } else {
+ // Although the type is not meaningfull and we could have picked any string for this,
+ // we have aligned with DEFAULT_ATTRIBUTE_TYPE constant in IOTA-JSON and IOTA-UL repositories
+ measure.type = 'Text';
+ }
+ measures.push(measure);
+ }
+ }
+ finalMeasures.push(measures);
+ }
+ return finalMeasures;
+ } else {
+ let json = originJson;
+
+ let measures = [];
+ for (let key in json) {
+ /* eslint-disable-next-line no-prototype-builtins */
+ if (json.hasOwnProperty(key)) {
+ let measure = {
+ name: key,
+ value: json[key]
+ };
+ // A bit of Magic. If the key is TimeInstant, we set the type to DateTime.
+ // When sending the data through iot
+ if (key === 'TimeInstant') {
+ measure.type = 'DateTime';
+ } else {
+ // Although the type is not meaningfull and we could have picked any string for this,
+ // we have aligned with DEFAULT_ATTRIBUTE_TYPE constant in IOTA-JSON and IOTA-UL repositories
+ measure.type = 'Text';
+ }
+ measures.push(measure);
+ }
+ }
+ return measures;
+ }
+}
+
+/**
+ * @brief Delays the execution of the code for the specified time in ms
+ *
+ * @param {Number} time in ms
+ * @returns
+ */
+const delay = (time) => new Promise((res) => setTimeout(res, time));
+
+function groupToIoTAConfigType(group, service, subservice) {
+ let type = {};
+ for (var key in group) {
+ /* eslint-disable-next-line no-prototype-builtins */
+ if (group.hasOwnProperty(key)) {
+ if (key === 'attributes') {
+ type.active = group.attributes;
+ } else if (key === 'entity_type') {
+ type.type = group.entity_type;
+ } else if (key === 'static_attributes') {
+ type.staticAttributes = group.static_attributes;
+ } else if (key === 'commands') {
+ type.commands = group.commands;
+ } else if (key !== 'resource') {
+ type[key] = group[key];
+ }
+ }
+ }
+ type.service = service;
+ type.subservice = subservice;
+ return { name: group.entity_type, type: type };
+}
+
+/**
+ * Test Case function
+ * @brief Sends a measure to the IoT Agent and validates the response
+ * and validates the Context Broker expectation
+ *
+ * @param {Object} measure Measure to be sent to the IoT Agent
+ * @param {Object} expectation Expectation for the Context Broker
+ * @param {Object} env Environment variables
+ * @param {Object} config IoTA Configuration object
+ * @param {String} type Type of test (multientity or multimeasure)
+ * @param {String} transport Transport to be used (Lib, HTTP or MQTT). If not specified, Lib is used.
+ * @param {Boolean} regex If true, the expectation is treated as a regex
+ */
+async function testCase(measure, expectation, provision, env, config, type, transport, regex) {
+ let receivedContext = [];
+ let cbMockRoute = '';
+ // Set the correct route depending if the test is multientity or not
+ if (type === 'multientity' || type === 'multimeasure') {
+ cbMockRoute = '/v2/op/update?options=flowControl';
+ } else {
+ cbMockRoute = '/v2/entities?options=upsert,flowControl';
+ }
+
+ // Set the correct mock times depending if the test is multimeasure or not
+ // based on the length of the expectation array
+ let mockTimes = 1;
+ if (expectation.length > 1) {
+ mockTimes = expectation.length;
+ }
+
+ let contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', env.service)
+ .matchHeader('fiware-servicepath', env.servicePath)
+ .post(cbMockRoute, function (body) {
+ mockTimes === 1 ? (receivedContext = body) : receivedContext.push(body); // Save the received body for later comparison
+ return true;
+ })
+ .times(mockTimes)
+ .reply(204);
+
+ // Send a measure to the IoT Agent and wait for the response
+ if (transport === 'MQTT') {
+ try {
+ let client = await MQTT.connectAsync('mqtt://' + config.mqtt.host);
+ await client.publish('/json/' + measure.qs.k + '/' + measure.qs.i + '/attrs', JSON.stringify(measure.json));
+ await client.end();
+ } catch (error) {
+ expect.fail(ERR_MQTT + error);
+ }
+ } else if (transport === 'HTTP') {
+ // HTTP
+ const response = await sendMeasureHttp(measure);
+ // Validate the response status code and the response body
+ expect(response.statusCode, ERR_MEAS_CODE).to.equal(200);
+ expect(response.body, ERR_MEAS_BODY).to.be.empty;
+ } else {
+ const response = await sendMeasureIotaLib(measure, provision);
+ }
+
+ // Validate Context Broker Expectation
+ if ((Array.isArray(expectation) && expectation.length > 0) || !Array.isArray(expectation)) {
+ // Filter empty expectations
+ regex && regex === true
+ ? expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.matchPattern(expectation)
+ : expect(receivedContext, ERR_CB_EXPECTATION_DIFFER).to.deep.equal(expectation);
+ contextBrokerMock.done(); // Ensure the request was made, no matter the body content
+ } else {
+ // If empty expectation, ensure no request was made
+ expect(contextBrokerMock.isDone(), ERR_CB_NOT_EMPTY).to.be.false;
+ expect(receivedContext, ERR_CB_NOT_EMPTY).to.be.empty;
+ }
+}
+
+/**
+ *
+ * @param {*} skip skip string from test case. I.E: "lib, !json"
+ * @param {*} matchPattern skip pattern to check. I.E: "lib"
+ * @returns true if the test should be skipped. False otherwise
+ */
+function checkSkip(skip, matchPattern) {
+ var isMatch = false;
+ // Separate tokens by comma or space, and remove empty tokens
+ var tokens = skip.split(/[ , ]+/).filter(function (value, index, arr) {
+ return value !== '' && !value.match(/[* ]+/);
+ });
+ // Check if the skip pattern is in the tokens array, or there is a token starting with ! without the pattern (negative match -!b)
+ tokens.forEach((element) => {
+ if (element === matchPattern || (element[0] === '!' && element.substr(1) !== matchPattern)) {
+ isMatch = true;
+ }
+ });
+ return isMatch;
+}
+
+exports.checkSkip = checkSkip;
+exports.sendMeasureHttp = sendMeasureHttp;
+exports.sendMeasureIotaLib = sendMeasureIotaLib;
+exports.delayMs = delay;
+exports.testCase = testCase;
+exports.groupToIoTAConfigType = groupToIoTAConfigType;
diff --git a/test/tools/utils.js b/test/tools/utils.js
index b051b1b70..ca539159b 100644
--- a/test/tools/utils.js
+++ b/test/tools/utils.js
@@ -35,6 +35,31 @@ function readExampleFile(name, raw) {
return raw ? text : JSON.parse(text);
}
+function deepEqual(objA, objB) {
+ if (objA === objB) {
+ return true;
+ }
+
+ if (typeof objA !== 'object' || typeof objB !== 'object' || objA === null || objB === null) {
+ return false;
+ }
+
+ const keysA = Object.keys(objA);
+ const keysB = Object.keys(objB);
+
+ if (keysA.length !== keysB.length) {
+ return false;
+ }
+
+ for (const key of keysA) {
+ if (!keysB.includes(key) || !deepEqual(objA[key], objB[key])) {
+ return false;
+ }
+ }
+
+ return true;
+}
exports.readExampleFile = readExampleFile;
+exports.deepEqual = deepEqual;
exports.request = request;
diff --git a/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json b/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json
deleted file mode 100644
index 28dd1678b..000000000
--- a/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "devices": [
- {
- "device_id": "Light1",
- "protocol": "GENERIC_PROTO",
- "entity_name": "TheFirstLight",
- "entity_type": "TheLightType",
- "timezone": "America/Santiago",
- "endpoint": "http://fakedEndpoint:1234",
- "transport": "MQTT",
- "attributes": [
- {
- "name":"location",
- "type":"geo:point",
- "expression": "latitude,longitude",
- "reverse": [
- {
- "object_id":"latitude",
- "type": "Number",
- "expression": "location | split(', ')[1] | parsefloat()"
- },
- {
- "object_id":"longitude",
- "type": "Number",
- "expression": "location | split(', ')[0] | parsefloat()"
- }
- ]
- }
- ],
- "lazy": [],
- "static_attributes": [],
- "commands": []
- }
- ]
-}
\ No newline at end of file
diff --git a/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json b/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json
deleted file mode 100644
index ad470f4ce..000000000
--- a/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "devices": [
- {
- "device_id": "Light1",
- "protocol": "GENERIC_PROTO",
- "entity_name": "TheFirstLight",
- "entity_type": "TheLightType",
- "timezone": "America/Santiago",
- "endpoint": "http://fakedEndpoint:1234",
- "transport": "MQTT",
- "attributes": [],
- "lazy": [],
- "static_attributes": [],
- "commands": []
- }
- ]
-}
\ No newline at end of file
diff --git a/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json b/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json
index 694b1c4cc..83e3926c9 100644
--- a/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json
+++ b/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json
@@ -25,19 +25,7 @@
{
"name":"location",
"type":"geo:point",
- "expression": "${latitude}, ${longitude}",
- "reverse": [
- {
- "object_id":"latitude",
- "type": "string",
- "expression": "${trim(substr(@location, indexOf(@location, \",\") + 1, length(@location)))}"
- },
- {
- "object_id":"longitude",
- "type": "string",
- "expression": "${trim(substr(@location, 0, indexOf(@location, \",\")))}"
- }
- ]
+ "expression": "${latitude}, ${longitude}"
}
],
"static_attributes": [],
diff --git a/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json b/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json
index 7bb1ee7cd..23f6026f7 100644
--- a/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json
+++ b/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json
@@ -3,7 +3,6 @@
{
"device_id": "MicroLight1",
"protocol": "GENERIC_PROTO",
- "expressionLanguage": "jexl",
"attributes": [
{
"name": "attr_name",
diff --git a/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json b/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json
new file mode 100644
index 000000000..4b9b51e29
--- /dev/null
+++ b/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json
@@ -0,0 +1,4 @@
+{
+ "apikey": "APIKEY2",
+ "timezone": "Europe/Madrid"
+}
diff --git a/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json b/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json
deleted file mode 100644
index a55b8a901..000000000
--- a/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "services": [
- {
- "resource": "/deviceTest",
- "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
- "entity_type": "TheLightType",
- "commands": [],
- "lazy": [],
- "attributes": [
- {
- "name":"location",
- "type":"geo:point",
- "expression": "${latitude}, ${longitude}",
- "reverse": [
- {
- "object_id":"latitude",
- "type": "Number",
- "expression": "location | split(', ')[1] | parsefloat()"
- },
- {
- "object_id":"longitude",
- "type": "Number",
- "expression": "location | split(', ')[0] | parsefloat()"
- }
- ]
- }
- ],
- "static_attributes": []
- }
- ]
-}
diff --git a/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json b/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json
new file mode 100644
index 000000000..64b7748e7
--- /dev/null
+++ b/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json
@@ -0,0 +1,44 @@
+{
+ "groups": [
+ {
+ "resource": "/deviceTest",
+ "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
+ "entity_type": "Light",
+ "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS",
+ "cbHost": "http://unexistentHost:1026",
+ "commands": [
+ {
+ "name": "wheel1",
+ "type": "Wheel"
+ }
+ ],
+ "lazy": [
+ {
+ "name": "luminescence",
+ "type": "Lumens"
+ }
+ ],
+ "attributes": [
+ {
+ "name": "status",
+ "type": "Boolean"
+ }
+ ]
+ },
+ {
+ "resource": "/deviceTest",
+ "apikey": "23HJK3Y9090DSFL173209HV8801232",
+ "entity_type": "Termometer",
+ "trust": "BL9803H3QRW8342HBAMS8A8",
+ "cbHost": "http://unexistentHost:1026",
+ "commands": [
+ {
+ "name": "temperature",
+ "type": "degrees"
+ }
+ ],
+ "lazy": [],
+ "attributes": []
+ }
+ ]
+}
diff --git a/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json b/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json
new file mode 100644
index 000000000..d87fdbc7e
--- /dev/null
+++ b/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json
@@ -0,0 +1,35 @@
+{
+ "groups": [
+ {
+ "resource": "/deviceDuplicateGroup",
+ "apikey": "JK09H3L12K09H3L123HJK0732L123HJK38K09H3",
+ "entity_type": "CommandMachinery",
+ "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS",
+ "commands": [
+ {
+ "name": "wheel1",
+ "type": "Wheel"
+ }
+ ],
+ "lazy": [
+ {
+ "name": "luminescence",
+ "type": "Lumens"
+ }
+ ],
+ "attributes": [
+ {
+ "name": "status",
+ "type": "Boolean"
+ }
+ ],
+ "static_attributes": [
+ {
+ "name": "bootstrapServer",
+ "type": "Address",
+ "value": "127.0.0.1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json b/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json
new file mode 100644
index 000000000..5764460b5
--- /dev/null
+++ b/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json
@@ -0,0 +1,36 @@
+{
+ "groups": [
+ {
+ "resource": "/deviceTest",
+ "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
+ "entity_type": "SensorMachine",
+ "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS",
+ "cbHost": "http://unexistentHost:1026",
+ "commands": [
+ {
+ "name": "wheel1",
+ "type": "Wheel"
+ }
+ ],
+ "lazy": [
+ {
+ "name": "luminescence",
+ "type": "Lumens"
+ }
+ ],
+ "attributes": [
+ {
+ "name": "status",
+ "type": "Boolean"
+ }
+ ],
+ "static_attributes": [
+ {
+ "name": "bootstrapServer",
+ "type": "Address",
+ "value": "127.0.0.1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json b/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json
new file mode 100644
index 000000000..530fa0632
--- /dev/null
+++ b/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json
@@ -0,0 +1,36 @@
+{
+ "groups": [
+ {
+ "resource": "/deviceTest",
+ "apikey": "754KL23Y9090DSFL123HSFL12380KL23Y2",
+ "entity_type": "AnotherMachine",
+ "trust": "8970A9078A803H3BL98PINEQRW8342HBAMS",
+ "cbHost": "http://unexistentHost:1026",
+ "commands": [
+ {
+ "name": "wheel1",
+ "type": "Wheel"
+ }
+ ],
+ "lazy": [
+ {
+ "name": "luminescence",
+ "type": "Lumens"
+ }
+ ],
+ "attributes": [
+ {
+ "name": "status",
+ "type": "Boolean"
+ }
+ ],
+ "static_attributes": [
+ {
+ "name": "bootstrapServer",
+ "type": "Address",
+ "value": "127.0.0.1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json b/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json
index cd2a11174..82bf528bc 100644
--- a/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json
+++ b/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json
@@ -6,6 +6,7 @@
"entity_type": "SensorMachine",
"trust": "8970A9078A803H3BL98PINEQRW8342HBAMS",
"cbHost": "http://unexistentHost:1026",
+ "useCBflowControl": true,
"commands": [
{
"name": "wheel1",
diff --git a/test/unit/expressions/jexlExpression-test.js b/test/unit/expressions/jexlExpression-test.js
index 4b187f76b..fa5fd03f5 100644
--- a/test/unit/expressions/jexlExpression-test.js
+++ b/test/unit/expressions/jexlExpression-test.js
@@ -40,7 +40,12 @@ describe('Jexl expression interpreter', function () {
object: {
name: 'John',
surname: 'Doe'
- }
+ },
+ invalidJson: '{"name": "John", surname": "Doe"}', // Invalid JSON for jsonparse
+ invalidDate: 'invalid-date', // Invalid date for toisodate and other date functions
+ emptyArray: [], // Empty array for addreduce
+ invalidHex: 'zz', // Invalid hex string for hextostring
+ nonNumeric: 'not-a-number' // Non-numeric input for parseint, parsefloat
};
describe('When a expression with a single value is parsed', function () {
@@ -291,7 +296,7 @@ describe('Jexl expression interpreter', function () {
it('it should detect when it is not a map', function (done) {
const [error, message, resultMap] = expressionParser.checkTransformationMap(noMap);
should.exist(error);
- message.should.equal('No trasformations were added to JEXL Parser');
+ message.should.equal('No transformations were added to JEXL Parser');
resultMap.should.eql({});
done();
});
@@ -299,7 +304,7 @@ describe('Jexl expression interpreter', function () {
it('it should be empty {}', function (done) {
const [error, message, resultMap] = expressionParser.checkTransformationMap({});
should.not.exist(error);
- message.should.equal('No trasformations were added to JEXL Parser');
+ message.should.equal('No transformations were added to JEXL Parser');
resultMap.should.eql({});
done();
});
@@ -307,7 +312,7 @@ describe('Jexl expression interpreter', function () {
it('it should be empty null', function (done) {
const [error, message, resultMap] = expressionParser.checkTransformationMap(null);
should.not.exist(error);
- message.should.equal('No trasformations were added to JEXL Parser');
+ message.should.equal('No transformations were added to JEXL Parser');
resultMap.should.eql({});
done();
});
@@ -325,7 +330,7 @@ describe('Jexl expression interpreter', function () {
it('it should be correct (map of funtions)', function (done) {
const [error, message, resultMap] = expressionParser.checkTransformationMap(niceMap);
should.not.exist(error);
- message.should.equal('Trasformations can be added to JEXL parser');
+ message.should.equal('Transformations can be added to JEXL parser');
resultMap.should.eql(niceMap);
done();
});
@@ -392,4 +397,233 @@ describe('Jexl expression interpreter', function () {
});
});
});
+
+ describe('When number localization functions are executed', function () {
+ it('should return the localized string representation of the number', function (done) {
+ const scope = {
+ theNumber: 1234567.89,
+ locale: 'es-ES',
+ options: { minimumFractionDigits: 2, maximumFractionDigits: 2 }
+ };
+
+ expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.equal('1.234.567,89');
+ done();
+ });
+ });
+
+ it('should return null for invalid number input', function (done) {
+ const scope = {
+ theNumber: 'not_a_number',
+ locale: 'en-US',
+ options: {}
+ };
+
+ expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) {
+ should.not.exist(error);
+ should.equal(result, null);
+ done();
+ });
+ });
+ });
+
+ // Check errors
+ describe('When invalid inputs are used', function () {
+ describe('When an invalid JSON string is parsed', function () {
+ it('should return null for jsonparse', function (done) {
+ expressionParser.parse('invalidJson|jsonparse', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When an invalid date string is parsed', function () {
+ it('should return null for toisodate', function (done) {
+ expressionParser.parse('invalidDate|toisodate', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+
+ it('should return null for timeoffset', function (done) {
+ expressionParser.parse('invalidDate|timeoffset', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When an empty array is processed with addreduce', function () {
+ it('should return null for addreduce', function (done) {
+ expressionParser.parse('emptyArray|addreduce', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When a non-numeric string is parsed by parseint and parsefloat', function () {
+ it('should return null for parseint', function (done) {
+ expressionParser.parse('nonNumeric|parseint', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+
+ it('should return null for parsefloat', function (done) {
+ expressionParser.parse('nonNumeric|parsefloat', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When an invalid hex string is processed by hextostring', function () {
+ it('should return null for hextostring', function (done) {
+ expressionParser.parse('invalidHex|hextostring', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When invalid regular expressions are used in replace functions', function () {
+ it('should return null for replaceregexp with invalid regex', function (done) {
+ expressionParser.parse(
+ 'theString|replaceregexp("[a-z", "replacement")',
+ scope,
+ function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ }
+ );
+ });
+
+ it('should return null for replaceallregexp with invalid regex', function (done) {
+ expressionParser.parse(
+ 'theString|replaceallregexp("[a-z", "replacement")',
+ scope,
+ function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ }
+ );
+ });
+ });
+
+ describe('When tostring is used with null or undefined', function () {
+ it('should return null for tostring(null)', function (done) {
+ expressionParser.parse('null|tostring', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+
+ it('should return null for tostring(undefined)', function (done) {
+ expressionParser.parse('undefined|tostring', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When urldecode is used with an invalid URI', function () {
+ it('should return null for urldecode with malformed URI', function (done) {
+ expressionParser.parse('"%%%invalidURI"|urldecode', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When tofixed is used with invalid inputs', function () {
+ it('should return null for tofixed with non-numeric value', function (done) {
+ expressionParser.parse('"notANumber"|tofixed(2)', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+
+ it('should return null for tofixed with invalid decimal value', function (done) {
+ expressionParser.parse('"123.456"|tofixed("invalid")', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+
+ describe('When gettime is used with invalid date', function () {
+ it('should return null for gettime with invalid date string', function (done) {
+ expressionParser.parse('"invalidDate"|gettime', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+
+ it('should return null for gettime with null', function (done) {
+ expressionParser.parse('null|gettime', scope, function (error, result) {
+ should.not.exist(error);
+ should(result).be.Null();
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When hash function is executed', function () {
+ it('should return an 8-character uppercase hexadecimal hash', function (done) {
+ expressionParser.parse('"test"|hash', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.match(/^[0-9A-F]{8}$/);
+ result.should.equal('00364492');
+ done();
+ });
+ });
+
+ it('should return consistent hash for the same input', function (done) {
+ expressionParser.parse('value|hash', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.match(/^[0-9A-F]{8}$/);
+ const firstHash = result;
+ expressionParser.parse('value|hash', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.equal(firstHash);
+ done();
+ });
+ });
+ });
+
+ it('should handle numeric values', function (done) {
+ expressionParser.parse('number|hash', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.match(/^[0-9A-F]{8}$/);
+ done();
+ });
+ });
+
+ it('should handle empty string', function (done) {
+ expressionParser.parse('""|hash', scope, function (error, result) {
+ should.not.exist(error);
+ result.should.equal('00000000');
+ done();
+ });
+ });
+ });
});
diff --git a/test/unit/general/config-multi-core-test.js b/test/unit/general/config-multi-core-test.js
index d03bc53a8..c1f8bfbbd 100644
--- a/test/unit/general/config-multi-core-test.js
+++ b/test/unit/general/config-multi-core-test.js
@@ -54,8 +54,7 @@ const iotAgentConfig = {
}
},
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M',
- throttling: 'PT5S'
+ deviceRegistrationDuration: 'P1M'
};
describe('Startup Multi-Core tests', function () {
diff --git a/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js b/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js
index 08f637fe6..94ca1d892 100644
--- a/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js
+++ b/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js
@@ -85,7 +85,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ useCBflowControl: true
};
describe('NGSI-v2 - Secured access to the Context Broker with Keystone', function () {
@@ -128,8 +129,7 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('X-Auth-Token', '12345679ABCDEF')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Light' })
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
@@ -166,8 +166,7 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('X-Auth-Token', '12345679ABCDEF')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Light' })
+ .post('/v2/entities?options=upsert,flowControl')
.reply(403, { name: 'ACCESS_FORBIDDEN' });
iotAgentLib.activate(iotAgentConfig, done);
@@ -199,8 +198,7 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('X-Auth-Token', '12345679ABCDEF')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Light' });
+ .post('/v2/entities?options=upsert,flowControl');
iotAgentLib.activate(iotAgentConfig, done);
});
@@ -278,13 +276,6 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
contextBrokerMock = nock('http://192.168.1.1:1026');
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'electricity')
- .matchHeader('X-Auth-Token', '12345679ABCDEF')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
contextBrokerMock
.post('/v2/registrations')
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
@@ -303,8 +294,8 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
});
it('subscribe requests use auth header', function (done) {
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -324,8 +315,8 @@ describe('NGSI-v2 - Secured access to the Context Broker with Keystone', functio
'X-Subject-Token': '12345679ABCDEF'
});
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
contextBrokerMock.done();
done();
diff --git a/test/unit/general/deviceService-test.js b/test/unit/general/deviceService-test.js
index 9b4fb40e8..720f46365 100644
--- a/test/unit/general/deviceService-test.js
+++ b/test/unit/general/deviceService-test.js
@@ -123,7 +123,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ useCBflowControl: true
};
const groupCreation = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
@@ -153,6 +154,36 @@ const groupCreation = {
'fiware-servicepath': '/testingPath'
}
};
+
+const configGroupCreation = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'TheLightType',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://192.168.1.1:1026',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
+
const deviceCreation = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
method: 'POST',
@@ -180,6 +211,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
async.series([iotAgentLib.clearAll, iotAgentLib.deactivate], done);
});
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When an existing device tries to be retrieved with retrieveOrCreate()', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
@@ -191,7 +223,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
contextBrokerMock
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series(
@@ -213,6 +245,41 @@ describe('NGSI-v2 - Device Service: utils', function () {
});
});
+ describe('When an existing device tries to be retrieved with retrieveOrCreate()', function () {
+ beforeEach(function (done) {
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+
+ contextBrokerMock
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ async.series(
+ [request.bind(request, configGroupCreation), request.bind(request, deviceCreation)],
+ function (error, results) {
+ done();
+ }
+ );
+ });
+
+ it('should return the existing device', function (done) {
+ iotAgentLib.retrieveDevice('Light1', '801230BJKL23Y9090DSFL123HJK09H324HV8732', function (error, device) {
+ should.not.exist(error);
+ should.exist(device);
+
+ device.id.should.equal('Light1');
+ done();
+ });
+ });
+ });
+
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
+
describe('When an unexisting device tries to be retrieved for an existing APIKey', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
@@ -224,7 +291,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
contextBrokerMock
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([request.bind(request, groupCreation)], function (error, results) {
@@ -249,6 +316,42 @@ describe('NGSI-v2 - Device Service: utils', function () {
});
});
+ describe('When an unexisting device tries to be retrieved for an existing APIKey', function () {
+ beforeEach(function (done) {
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+
+ contextBrokerMock
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ async.series([request.bind(request, configGroupCreation)], function (error, results) {
+ done();
+ });
+ });
+
+ it('should register the device and return it', function (done) {
+ iotAgentLib.retrieveDevice(
+ 'UNEXISTENT_DEV',
+ '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ function (error, device) {
+ should.not.exist(error);
+ should.exist(device);
+
+ device.id.should.equal('UNEXISTENT_DEV');
+ should.exist(device.protocol);
+ device.protocol.should.equal('MQTT_UL');
+ done();
+ }
+ );
+ });
+ });
+
describe('When an unexisting device tries to be retrieved for an unexisting APIKey', function () {
it('should raise an error', function (done) {
iotAgentLib.retrieveDevice(
diff --git a/test/unit/general/startup-test.js b/test/unit/general/startup-test.js
index 02f9b24f4..b2e4007be 100644
--- a/test/unit/general/startup-test.js
+++ b/test/unit/general/startup-test.js
@@ -115,6 +115,7 @@ describe('Startup tests', function () {
process.env.IOTA_MONGO_DB = 'themongodb';
process.env.IOTA_MONGO_REPLICASET = 'customReplica';
process.env.IOTA_DEFAULT_RESOURCE = '/iot/custom';
+ process.env.IOTA_HEALTH_CHECK = true;
nock.cleanAll();
@@ -142,6 +143,7 @@ describe('Startup tests', function () {
delete process.env.IOTA_MONGO_DB;
delete process.env.IOTA_MONGO_REPLICASET;
delete process.env.IOTA_DEFAULT_RESOURCE;
+ delete process.env.IOTA_HEALTH_CHECK;
});
afterEach(function (done) {
@@ -182,6 +184,7 @@ describe('Startup tests', function () {
process.env.IOTA_MONGO_AUTH_SOURCE = 'customAuthSource';
process.env.IOTA_MONGO_RETRIES = '10';
process.env.IOTA_MONGO_RETRY_TIME = '5';
+ process.env.IOTA_HEALTH_CHECK = true;
nock.cleanAll();
@@ -202,6 +205,7 @@ describe('Startup tests', function () {
delete process.env.IOTA_MONGO_RETRY_TIME;
delete process.env.IOTA_MONGO_SSL;
delete process.env.IOTA_MONGO_EXTRAARGS;
+ delete process.env.IOTA_HEALTH_CHECK;
});
afterEach(function (done) {
diff --git a/test/unit/general/statistics-persistence_test.js b/test/unit/general/statistics-persistence_test.js
deleted file mode 100644
index 9e47b8d97..000000000
--- a/test/unit/general/statistics-persistence_test.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- */
-
-/* eslint-disable no-unused-vars */
-
-const statsService = require('../../../lib/services/stats/statsRegistry');
-const commonConfig = require('../../../lib/commonConfig');
-const iotAgentLib = require('../../../lib/fiware-iotagent-lib');
-const should = require('should');
-const async = require('async');
-const mongoUtils = require('../mongodb/mongoDBUtils');
-const iotAgentConfig = {
- logLevel: 'FATAL',
- contextBroker: {
- host: '192.168.1.1',
- port: '1026'
- },
- server: {
- port: 4041,
- host: 'localhost',
- baseRoot: '/'
- },
- stats: {
- interval: 50,
- persistence: true
- },
- mongodb: {
- host: 'localhost',
- port: '27017',
- db: 'iotagent'
- },
- types: {},
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
-};
-let iotAgentDb;
-let oldConfig;
-
-describe('Statistics persistence service', function () {
- function insertDummy(n, callback) {
- iotAgentDb.collection('tests').insertOne({ test: 'test' }, function () {
- callback();
- });
- }
-
- beforeEach(function (done) {
- oldConfig = commonConfig.getConfig();
-
- iotAgentLib.activate(iotAgentConfig, function (error) {
- statsService.globalLoad({}, function () {
- iotAgentDb = require('../../../lib/model/dbConn').db;
-
- async.times(10, insertDummy, function () {
- done();
- });
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.deactivate(function (error) {
- commonConfig.setConfig(oldConfig);
- statsService.globalLoad({}, function () {
- mongoUtils.cleanDbs(done);
- });
- });
- });
-
- describe('When a periodic persitence action is set', function () {
- beforeEach(function (done) {
- statsService.globalLoad(
- {
- stat1: 10
- },
- function () {
- statsService.add('stat1', 5, done);
- }
- );
- });
-
- it('should store all the records in the database', function (done) {
- statsService.addTimerAction(statsService.mongodbPersistence, function () {
- setTimeout(function () {
- statsService.clearTimers(function () {
- iotAgentDb
- .collection('kpis')
- .find({})
- .toArray(function (err, docs) {
- should.not.exist(err);
- should.exist(docs);
- docs.length.should.be.above(2);
- done();
- });
- });
- }, 200);
- });
- });
- });
-});
diff --git a/test/unit/general/statistics-service_test.js b/test/unit/general/statistics-service_test.js
index 8d594a6da..cf3ae351c 100644
--- a/test/unit/general/statistics-service_test.js
+++ b/test/unit/general/statistics-service_test.js
@@ -35,9 +35,6 @@ const iotAgentConfig = {
host: 'localhost',
baseRoot: '/'
},
- stats: {
- interval: 100
- },
types: {},
service: 'smartgondor',
subservice: 'gardens',
@@ -51,9 +48,7 @@ describe('Statistics service', function () {
oldConfig = commonConfig.getConfig();
commonConfig.setConfig(iotAgentConfig);
- statsService.globalLoad({}, function () {
- statsService.clearTimers(done);
- });
+ statsService.globalLoad({}, done);
});
afterEach(function (done) {
@@ -74,16 +69,6 @@ describe('Statistics service', function () {
);
});
- it('should appear the modified value in the getCurrent() statistics', function (done) {
- statsService.add(statName, statValue, function () {
- statsService.getCurrent(statName, function (error, value) {
- should.not.exist(error);
- should.exist(value);
- value.should.equal(statValue);
- done();
- });
- });
- });
it('should add the value to the global values', function (done) {
statsService.add(statName, statValue, function () {
statsService.getGlobal(statName, function (error, value) {
@@ -119,62 +104,4 @@ describe('Statistics service', function () {
});
});
});
- describe('When the current statistics are reset', function () {
- beforeEach(function (done) {
- statsService.add('statA', 42, function () {
- statsService.add('statB', 52, done);
- });
- });
-
- it('should return a value of zero for any of the individual statistics', function (done) {
- statsService.resetCurrent(function (error) {
- should.not.exist(error);
-
- statsService.getAllCurrent(function (error, data) {
- should.exist(data);
- should.exist(data.statA);
- should.exist(data.statB);
- data.statA.should.equal(0);
- data.statB.should.equal(0);
- done();
- });
- });
- });
- });
- describe('When a new periodic stats action is set', function () {
- let valueCurrent = 0;
- let valueGlobal = 0;
- let times = 0;
-
- beforeEach(function (done) {
- statsService.globalLoad(
- {
- stat1: 10
- },
- function () {
- statsService.add('stat1', 5, done);
- }
- );
- });
-
- function mockedAction(current, global, callback) {
- valueCurrent = current.stat1;
- valueGlobal = global.stat1;
- times++;
- callback();
- }
-
- it('should be triggered with the periodicity stated in the config.stats.interval parameter', function (done) {
- statsService.addTimerAction(mockedAction, function () {
- setTimeout(function () {
- statsService.clearTimers(function () {
- valueCurrent.should.equal(5);
- valueGlobal.should.equal(15);
- times.should.equal(4);
- done();
- });
- }, 480);
- });
- });
- });
});
diff --git a/test/unit/memoryRegistry/deviceRegistryMemory_test.js b/test/unit/memoryRegistry/deviceRegistryMemory_test.js
index 776ad0f25..8860603c3 100644
--- a/test/unit/memoryRegistry/deviceRegistryMemory_test.js
+++ b/test/unit/memoryRegistry/deviceRegistryMemory_test.js
@@ -48,7 +48,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ useCBflowControl: true
};
let contextBrokerMock;
@@ -64,7 +65,7 @@ describe('NGSI-v2 - In memory device registry', function () {
describe('When a the registry is queried for a device using an arbitrary attribute', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(10)
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
@@ -116,7 +117,7 @@ describe('NGSI-v2 - In memory device registry', function () {
describe('When a the registry is queried for devices in multiple services', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(10)
.reply(204);
@@ -159,7 +160,7 @@ describe('NGSI-v2 - In memory device registry', function () {
describe('When a the registry is queried for devices in a particular service', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(10)
.reply(204);
@@ -202,7 +203,7 @@ describe('NGSI-v2 - In memory device registry', function () {
describe('When a the registry is queried for device in a particular name and type', function () {
beforeEach(function (done) {
contextBrokerMock = nock('http://192.168.1.1:1026')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(10)
.reply(204);
@@ -242,7 +243,7 @@ describe('NGSI-v2 - In memory device registry', function () {
should.exist(device.type);
device.name.should.equal('name5');
device.type.should.equal('Light5');
- Object.keys(device).length.should.equal(12);
+ Object.keys(device).length.should.equal(14);
done();
});
});
diff --git a/test/unit/mongodb/mongodb-configGroup-registry-test.js b/test/unit/mongodb/mongodb-configGroup-registry-test.js
new file mode 100644
index 000000000..8cd95c6e3
--- /dev/null
+++ b/test/unit/mongodb/mongodb-configGroup-registry-test.js
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2024 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Daniel Calvo - ATOS Research & Innovation
+ */
+
+/* eslint-disable no-unused-vars */
+
+const iotAgentLib = require('../../../lib/fiware-iotagent-lib');
+const _ = require('underscore');
+const utils = require('../../tools/utils');
+const request = utils.request;
+const async = require('async');
+const should = require('should');
+const iotAgentConfig = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026'
+ },
+ server: {
+ name: 'testAgent',
+ port: 4041,
+ host: 'localhost',
+ baseRoot: '/'
+ },
+ types: {},
+ deviceRegistry: {
+ type: 'mongodb'
+ },
+ mongodb: {
+ host: 'localhost',
+ port: '27017',
+ db: 'iotagent'
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M',
+ healthCheck: true
+};
+const mongo = require('mongodb').MongoClient;
+const mongoUtils = require('./mongoDBUtils');
+const optionsCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'Light',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://unexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ internal_attributes: [
+ {
+ customField: 'customValue'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
+const optionsDelete = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'DELETE',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
+const optionsList = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'GET',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/*'
+ }
+};
+const optionsUpdate = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'PUT',
+ json: {
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://anotherUnexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'bootstrapServer',
+ type: 'Address',
+ value: '127.0.0.1'
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
+const optionsGet = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'GET',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
+let iotAgentDb;
+
+describe('MongoDB Group Registry test', function () {
+ beforeEach(function (done) {
+ mongoUtils.cleanDbs(function () {
+ iotAgentLib.activate(iotAgentConfig, function () {
+ mongo.connect('mongodb://localhost:27017/iotagent', { useNewUrlParser: true }, function (err, db) {
+ iotAgentDb = db;
+ done();
+ });
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentDb.close(function (error) {
+ mongoUtils.cleanDbs(done);
+ });
+ });
+ });
+ describe('When a new device group creation request arrives', function () {
+ it('should store it in the DB', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs.length);
+ docs.length.should.equal(1);
+ should.exist(docs[0].type);
+ should.exist(docs[0].internalAttributes);
+ should.exist(docs[0].attributes);
+ should.exist(docs[0].apikey);
+ docs[0].type.should.equal('Light');
+ docs[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ docs[0].internalAttributes.length.should.equal(1);
+ docs[0].internalAttributes[0].customField.should.equal('customValue');
+ docs[0].attributes.length.should.equal(1);
+ docs[0].attributes[0].name.should.equal('status');
+ done();
+ });
+ });
+ });
+ it('should store the service information from the headers into the DB', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs[0].service);
+ should.exist(docs[0].subservice);
+ docs[0].service.should.equal('testservice');
+ docs[0].subservice.should.equal('/testingPath');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a new device group creation request arrives with an existant (apikey, resource) pair', function () {
+ it('should return a DUPLICATE_GROUP error', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ request(optionsCreation, function (error, response, body) {
+ response.statusCode.should.equal(409);
+ body.name.should.equal('DUPLICATE_GROUP');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device group removal request arrives', function () {
+ beforeEach(function (done) {
+ request(optionsCreation, done);
+ });
+
+ it('should remove it from the database', function (done) {
+ request(optionsDelete, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs.length);
+ docs.length.should.equal(0);
+ done();
+ });
+ });
+ });
+
+ it('should return a 204 OK statusCode', function (done) {
+ request(optionsDelete, function (error, response, body) {
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ });
+
+ describe('When a device group update request arrives', function () {
+ beforeEach(function (done) {
+ request(optionsCreation, done);
+ });
+
+ it('should update the values in the database', function (done) {
+ request(optionsUpdate, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs[0].cbHost);
+ docs[0].cbHost.should.equal('http://anotherUnexistentHost:1026');
+ should.exist(docs[0].staticAttributes);
+ docs[0].staticAttributes.length.should.equal(1);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a multiple device group creation arrives', function () {
+ const optionsMultipleCreation = _.clone(optionsCreation);
+
+ beforeEach(function (done) {
+ optionsMultipleCreation.json = utils.readExampleFile(
+ './test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json'
+ );
+
+ done();
+ });
+
+ it('should create the values in the database', function (done) {
+ request(optionsMultipleCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ docs.length.should.equal(2);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device group listing request arrives', function () {
+ beforeEach(function (done) {
+ const optionsCreation1 = _.clone(optionsCreation);
+ const optionsCreation2 = _.clone(optionsCreation);
+ const optionsCreation3 = _.clone(optionsCreation);
+
+ optionsCreation2.json = { groups: [] };
+ optionsCreation3.json = { groups: [] };
+
+ optionsCreation2.json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreation3.json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+
+ optionsCreation2.json.groups[0].apikey = 'qwertyuiop';
+ optionsCreation3.json.groups[0].apikey = 'lkjhgfds';
+
+ async.series(
+ [
+ async.apply(request, optionsCreation1),
+ async.apply(request, optionsCreation2),
+ async.apply(request, optionsCreation3)
+ ],
+ done
+ );
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsList, function (error, response, body) {
+ body.count.should.equal(3);
+ done();
+ });
+ });
+ });
+
+ describe('When a device group listing arrives with a limit', function () {
+ const optionsConstrained = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'GET',
+ qs: {
+ limit: 3,
+ offset: 2
+ },
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/*'
+ }
+ };
+
+ beforeEach(function (done) {
+ const optionsCreationList = [];
+ const creationFns = [];
+
+ for (let i = 0; i < 10; i++) {
+ optionsCreationList[i] = _.clone(optionsCreation);
+ optionsCreationList[i].json = { groups: [] };
+ optionsCreationList[i].json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreationList[i].json.groups[0].apikey = 'qwertyuiop' + i;
+ creationFns.push(async.apply(request, optionsCreationList[i]));
+ }
+
+ async.series(creationFns, done);
+ });
+
+ it('should return the appropriate count of groups', function (done) {
+ request(optionsConstrained, function (error, response, body) {
+ body.count.should.equal(10);
+ done();
+ });
+ });
+ });
+
+ describe('When a device info request arrives', function () {
+ beforeEach(function (done) {
+ async.series([async.apply(request, optionsCreation)], done);
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsGet, function (error, response, body) {
+ should.exist(body);
+ should.exist(body.count);
+ body.count.should.equal(1);
+ should.exist(body.groups);
+ should.exist(body.groups.length);
+ body.groups.length.should.equal(1);
+ body.groups[0].service.should.equal('testservice');
+ done();
+ });
+ });
+ });
+
+ describe('When a device info request arrives and multiple groups have been created', function () {
+ beforeEach(function (done) {
+ const optionsCreationList = [];
+ const creationFns = [];
+
+ for (let i = 0; i < 10; i++) {
+ optionsCreationList[i] = _.clone(optionsCreation);
+ optionsCreationList[i].json = { groups: [] };
+ optionsCreationList[i].json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreationList[i].json.groups[0].apikey = 'qwertyuiop' + i;
+ creationFns.push(async.apply(request, optionsCreationList[i]));
+ }
+
+ async.series(creationFns, done);
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsGet, function (error, response, body) {
+ should.exist(body);
+ should.exist(body.count);
+ body.count.should.equal(10);
+ should.exist(body.groups);
+ should.exist(body.groups.length);
+ body.groups.length.should.equal(10);
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/mongodb/mongodb-connectionoptions-test.js b/test/unit/mongodb/mongodb-connectionoptions-test.js
index 31bafafce..220e55a59 100644
--- a/test/unit/mongodb/mongodb-connectionoptions-test.js
+++ b/test/unit/mongodb/mongodb-connectionoptions-test.js
@@ -41,15 +41,15 @@ const iotAgentConfig = {
host: 'localhost',
baseRoot: '/'
},
- stats: {
- interval: 50,
- persistence: true
+ deviceRegistry: {
+ type: 'mongodb'
},
types: {},
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ healthCheck: true
};
let oldConfig;
@@ -75,9 +75,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -87,9 +85,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:98765/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -99,9 +95,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/examples',
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -112,8 +106,7 @@ describe('dbConn.configureDb', function () {
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
options: {
- replicaSet: 'rs0',
- useNewUrlParser: true
+ replicaSet: 'rs0'
}
}
},
@@ -124,9 +117,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -136,9 +127,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -148,13 +137,12 @@ describe('dbConn.configureDb', function () {
password: 'pass01'
},
expected: {
- url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
+ url: 'mongodb://user01:pass01@example.com:27017/' + dbConn.DEFAULT_DB_NAME,
options: {
auth: {
user: 'user01',
password: 'pass01'
- },
- useNewUrlParser: true
+ }
}
}
},
@@ -165,10 +153,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- authSource: 'admin',
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -182,15 +167,13 @@ describe('dbConn.configureDb', function () {
authSource: 'admin'
},
expected: {
- url: 'mongodb://example.com:98765/examples',
+ url: 'mongodb://user01:pass01@example.com:98765/examples?authSource=admin',
options: {
replicaSet: 'rs0',
auth: {
user: 'user01',
password: 'pass01'
- },
- authSource: 'admin',
- useNewUrlParser: true
+ }
}
}
},
@@ -202,8 +185,7 @@ describe('dbConn.configureDb', function () {
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
options: {
- ssl: true,
- useNewUrlParser: true
+ ssl: true
}
}
},
@@ -216,9 +198,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME + '?retryWrites=true',
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -234,9 +214,7 @@ describe('dbConn.configureDb', function () {
'mongodb://example.com:27017/' +
dbConn.DEFAULT_DB_NAME +
'?retryWrites=true&readPreference=nearest',
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -246,9 +224,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -258,9 +234,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -270,9 +244,7 @@ describe('dbConn.configureDb', function () {
},
expected: {
url: 'mongodb://example.com:27017/' + dbConn.DEFAULT_DB_NAME,
- options: {
- useNewUrlParser: true
- }
+ options: {}
}
},
{
@@ -293,16 +265,14 @@ describe('dbConn.configureDb', function () {
unknownparam: 'unknown'
},
expected: {
- url: 'mongodb://example.com:98765/examples?retryWrites=true&readPreference=nearest&w=majority',
+ url: 'mongodb://user01:pass01@example.com:98765/examples?retryWrites=true&readPreference=nearest&w=majority&authSource=admin',
options: {
replicaSet: 'rs0',
auth: {
user: 'user01',
password: 'pass01'
},
- authSource: 'admin',
- ssl: true,
- useNewUrlParser: true
+ ssl: true
}
}
}
@@ -318,7 +288,7 @@ describe('dbConn.configureDb', function () {
const cfg = Object.assign({}, iotAgentConfig, {
mongodb: params.mongodb
});
- stub = sinon.stub(mongoose, 'createConnection').callsFake(function (url, options, fn) {
+ stub = sinon.stub(mongoose, 'connect').callsFake(function (url, options, fn) {
url.should.be.equal(params.expected.url);
options.should.be.eql(params.expected.options);
done();
@@ -353,7 +323,7 @@ describe('dbConn.configureDb', function () {
const cfg = Object.assign({}, iotAgentConfig, {
mongodb: params.mongodb
});
- stub = sinon.stub(mongoose, 'createConnection').callsFake(function (url, options, fn) {
+ stub = sinon.stub(mongoose, 'connect').callsFake(function (url, options, fn) {
should.fail();
});
config.setConfig(cfg);
diff --git a/test/unit/mongodb/mongodb-group-registry-test.js b/test/unit/mongodb/mongodb-group-registry-test.js
index bec77d40d..c20624ad3 100644
--- a/test/unit/mongodb/mongodb-group-registry-test.js
+++ b/test/unit/mongodb/mongodb-group-registry-test.js
@@ -23,6 +23,7 @@
* Modified by: Daniel Calvo - ATOS Research & Innovation
*/
+// #FIXME1649: parallel tests in mongodb-configGroup-registry-test.js. Remove this file if at the end /iot/services API (now Deprecated) is removed
/* eslint-disable no-unused-vars */
const iotAgentLib = require('../../../lib/fiware-iotagent-lib');
@@ -55,15 +56,16 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ healthCheck: true
};
const mongo = require('mongodb').MongoClient;
const mongoUtils = require('./mongoDBUtils');
const optionsCreation = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'POST',
json: {
- services: [
+ groups: [
{
resource: '/deviceTest',
apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
@@ -102,7 +104,7 @@ const optionsCreation = {
}
};
const optionsDelete = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'DELETE',
json: {},
headers: {
@@ -115,7 +117,7 @@ const optionsDelete = {
}
};
const optionsList = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'GET',
json: {},
headers: {
@@ -124,7 +126,7 @@ const optionsList = {
}
};
const optionsUpdate = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'PUT',
json: {
apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
@@ -166,7 +168,7 @@ const optionsUpdate = {
}
};
const optionsGet = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'GET',
json: {},
headers: {
@@ -176,7 +178,7 @@ const optionsGet = {
};
let iotAgentDb;
-describe('MongoDB Group Registry test', function () {
+describe('MongoDB Group Registry test', function () {
beforeEach(function (done) {
mongoUtils.cleanDbs(function () {
iotAgentLib.activate(iotAgentConfig, function () {
@@ -309,7 +311,7 @@ describe('MongoDB Group Registry test', function () {
beforeEach(function (done) {
optionsMultipleCreation.json = utils.readExampleFile(
- './test/unit/examples/groupProvisioningRequests/multipleGroupsCreation.json'
+ './test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json'
);
done();
@@ -337,14 +339,14 @@ describe('MongoDB Group Registry test', function () {
const optionsCreation2 = _.clone(optionsCreation);
const optionsCreation3 = _.clone(optionsCreation);
- optionsCreation2.json = { services: [] };
- optionsCreation3.json = { services: [] };
+ optionsCreation2.json = { groups: [] };
+ optionsCreation3.json = { groups: [] };
- optionsCreation2.json.services[0] = _.clone(optionsCreation.json.services[0]);
- optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]);
+ optionsCreation2.json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreation3.json.groups[0] = _.clone(optionsCreation.json.groups[0]);
- optionsCreation2.json.services[0].apikey = 'qwertyuiop';
- optionsCreation3.json.services[0].apikey = 'lkjhgfds';
+ optionsCreation2.json.groups[0].apikey = 'qwertyuiop';
+ optionsCreation3.json.groups[0].apikey = 'lkjhgfds';
async.series(
[
@@ -366,7 +368,7 @@ describe('MongoDB Group Registry test', function () {
describe('When a device group listing arrives with a limit', function () {
const optionsConstrained = {
- url: 'http://localhost:4041/iot/services',
+ url: 'http://localhost:4041/iot/groups',
method: 'GET',
qs: {
limit: 3,
@@ -385,9 +387,9 @@ describe('MongoDB Group Registry test', function () {
for (let i = 0; i < 10; i++) {
optionsCreationList[i] = _.clone(optionsCreation);
- optionsCreationList[i].json = { services: [] };
- optionsCreationList[i].json.services[0] = _.clone(optionsCreation.json.services[0]);
- optionsCreationList[i].json.services[0].apikey = 'qwertyuiop' + i;
+ optionsCreationList[i].json = { groups: [] };
+ optionsCreationList[i].json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreationList[i].json.groups[0].apikey = 'qwertyuiop' + i;
creationFns.push(async.apply(request, optionsCreationList[i]));
}
@@ -412,10 +414,10 @@ describe('MongoDB Group Registry test', function () {
should.exist(body);
should.exist(body.count);
body.count.should.equal(1);
- should.exist(body.services);
- should.exist(body.services.length);
- body.services.length.should.equal(1);
- body.services[0].service.should.equal('testservice');
+ should.exist(body.groups);
+ should.exist(body.groups.length);
+ body.groups.length.should.equal(1);
+ body.groups[0].service.should.equal('testservice');
done();
});
});
@@ -428,9 +430,9 @@ describe('MongoDB Group Registry test', function () {
for (let i = 0; i < 10; i++) {
optionsCreationList[i] = _.clone(optionsCreation);
- optionsCreationList[i].json = { services: [] };
- optionsCreationList[i].json.services[0] = _.clone(optionsCreation.json.services[0]);
- optionsCreationList[i].json.services[0].apikey = 'qwertyuiop' + i;
+ optionsCreationList[i].json = { groups: [] };
+ optionsCreationList[i].json.groups[0] = _.clone(optionsCreation.json.groups[0]);
+ optionsCreationList[i].json.groups[0].apikey = 'qwertyuiop' + i;
creationFns.push(async.apply(request, optionsCreationList[i]));
}
@@ -442,9 +444,9 @@ describe('MongoDB Group Registry test', function () {
should.exist(body);
should.exist(body.count);
body.count.should.equal(10);
- should.exist(body.services);
- should.exist(body.services.length);
- body.services.length.should.equal(10);
+ should.exist(body.groups);
+ should.exist(body.groups.length);
+ body.groups.length.should.equal(10);
done();
});
});
@@ -464,11 +466,11 @@ describe('MongoDB Group Registry test', function () {
should.exist(body);
should.exist(body.count);
body.count.should.equal(1);
- should.exist(body.services);
- should.exist(body.services.length);
- body.services.length.should.equal(1);
- should.exist(body.services[0].entity_type);
- body.services[0].entity_type.should.equal('Light');
+ should.exist(body.groups);
+ should.exist(body.groups.length);
+ body.groups.length.should.equal(1);
+ should.exist(body.groups[0].entity_type);
+ body.groups[0].entity_type.should.equal('Light');
done();
});
});
diff --git a/test/unit/mongodb/mongodb-registry-test.js b/test/unit/mongodb/mongodb-registry-test.js
index cc2f2a992..19aba3e33 100644
--- a/test/unit/mongodb/mongodb-registry-test.js
+++ b/test/unit/mongodb/mongodb-registry-test.js
@@ -97,7 +97,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ healthCheck: true
};
const device1 = {
id: 'light1',
@@ -387,7 +388,7 @@ describe('NGSI-v2 - MongoDB Device Registry', function () {
});
it('should be removed from MongoDB', function (done) {
- iotAgentLib.unregister(device1.id, 'smartgondor', 'gardens', function (error) {
+ iotAgentLib.unregister(device1.id, null, 'smartgondor', 'gardens', function (error) {
iotAgentDb
.db()
.collection('devices')
diff --git a/test/unit/mongodb/mongodb-service-registry-test.js b/test/unit/mongodb/mongodb-service-registry-test.js
new file mode 100644
index 000000000..305f208b3
--- /dev/null
+++ b/test/unit/mongodb/mongodb-service-registry-test.js
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2024 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Daniel Calvo - ATOS Research & Innovation
+ */
+
+// #FIXME1649: parallel tests in mongodb-configGroup-registry-test.js. Remove this file if at the end /iot/services API (now Deprecated) is removed
+/* eslint-disable no-unused-vars */
+
+const iotAgentLib = require('../../../lib/fiware-iotagent-lib');
+const _ = require('underscore');
+const utils = require('../../tools/utils');
+const request = utils.request;
+const async = require('async');
+const should = require('should');
+const iotAgentConfig = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026'
+ },
+ server: {
+ name: 'testAgent',
+ port: 4041,
+ host: 'localhost',
+ baseRoot: '/'
+ },
+ types: {},
+ deviceRegistry: {
+ type: 'mongodb'
+ },
+ mongodb: {
+ host: 'localhost',
+ port: '27017',
+ db: 'iotagent'
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M',
+ healthCheck: true
+};
+const mongo = require('mongodb').MongoClient;
+const mongoUtils = require('./mongoDBUtils');
+const optionsCreation = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'POST',
+ json: {
+ services: [
+ {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'Light',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://unexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ internal_attributes: [
+ {
+ customField: 'customValue'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
+const optionsDelete = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'DELETE',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
+const optionsList = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'GET',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/*'
+ }
+};
+const optionsUpdate = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'PUT',
+ json: {
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://anotherUnexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'bootstrapServer',
+ type: 'Address',
+ value: '127.0.0.1'
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
+const optionsGet = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'GET',
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
+let iotAgentDb;
+
+describe('MongoDB Service Registry test', function () {
+ beforeEach(function (done) {
+ mongoUtils.cleanDbs(function () {
+ iotAgentLib.activate(iotAgentConfig, function () {
+ mongo.connect('mongodb://localhost:27017/iotagent', function (err, db) {
+ iotAgentDb = db;
+ done();
+ });
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentDb.close(function (error) {
+ mongoUtils.cleanDbs(done);
+ });
+ });
+ });
+ describe('When a new device group creation request arrives', function () {
+ it('should store it in the DB', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs.length);
+ docs.length.should.equal(1);
+ should.exist(docs[0].type);
+ should.exist(docs[0].internalAttributes);
+ should.exist(docs[0].attributes);
+ should.exist(docs[0].apikey);
+ docs[0].type.should.equal('Light');
+ docs[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ docs[0].internalAttributes.length.should.equal(1);
+ docs[0].internalAttributes[0].customField.should.equal('customValue');
+ docs[0].attributes.length.should.equal(1);
+ docs[0].attributes[0].name.should.equal('status');
+ done();
+ });
+ });
+ });
+ it('should store the service information from the headers into the DB', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs[0].service);
+ should.exist(docs[0].subservice);
+ docs[0].service.should.equal('testservice');
+ docs[0].subservice.should.equal('/testingPath');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a new device group creation request arrives with an existant (apikey, resource) pair', function () {
+ it('should return a DUPLICATE_GROUP error', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ request(optionsCreation, function (error, response, body) {
+ response.statusCode.should.equal(409);
+ body.name.should.equal('DUPLICATE_GROUP');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device group removal request arrives', function () {
+ beforeEach(function (done) {
+ request(optionsCreation, done);
+ });
+
+ it('should remove it from the database', function (done) {
+ request(optionsDelete, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs.length);
+ docs.length.should.equal(0);
+ done();
+ });
+ });
+ });
+
+ it('should return a 204 OK statusCode', function (done) {
+ request(optionsDelete, function (error, response, body) {
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ });
+
+ describe('When a device group update request arrives', function () {
+ beforeEach(function (done) {
+ request(optionsCreation, done);
+ });
+
+ it('should update the values in the database', function (done) {
+ request(optionsUpdate, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ should.exist(docs[0].cbHost);
+ docs[0].cbHost.should.equal('http://anotherUnexistentHost:1026');
+ should.exist(docs[0].staticAttributes);
+ docs[0].staticAttributes.length.should.equal(1);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a multiple device group creation arrives', function () {
+ const optionsMultipleCreation = _.clone(optionsCreation);
+
+ beforeEach(function (done) {
+ optionsMultipleCreation.json = utils.readExampleFile(
+ './test/unit/examples/groupProvisioningRequests/multipleGroupsCreation.json'
+ );
+
+ done();
+ });
+
+ it('should create the values in the database', function (done) {
+ request(optionsMultipleCreation, function (error, response, body) {
+ iotAgentDb
+ .db()
+ .collection('groups')
+ .find({})
+ .toArray(function (err, docs) {
+ should.not.exist(err);
+ should.exist(docs);
+ docs.length.should.equal(2);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device group listing request arrives', function () {
+ beforeEach(function (done) {
+ const optionsCreation1 = _.clone(optionsCreation);
+ const optionsCreation2 = _.clone(optionsCreation);
+ const optionsCreation3 = _.clone(optionsCreation);
+
+ optionsCreation2.json = { services: [] };
+ optionsCreation3.json = { services: [] };
+
+ optionsCreation2.json.services[0] = _.clone(optionsCreation.json.services[0]);
+ optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]);
+
+ optionsCreation2.json.services[0].apikey = 'qwertyuiop';
+ optionsCreation3.json.services[0].apikey = 'lkjhgfds';
+
+ async.series(
+ [
+ async.apply(request, optionsCreation1),
+ async.apply(request, optionsCreation2),
+ async.apply(request, optionsCreation3)
+ ],
+ done
+ );
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsList, function (error, response, body) {
+ body.count.should.equal(3);
+ done();
+ });
+ });
+ });
+
+ describe('When a device group listing arrives with a limit', function () {
+ const optionsConstrained = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'GET',
+ qs: {
+ limit: 3,
+ offset: 2
+ },
+ json: {},
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/*'
+ }
+ };
+
+ beforeEach(function (done) {
+ const optionsCreationList = [];
+ const creationFns = [];
+
+ for (let i = 0; i < 10; i++) {
+ optionsCreationList[i] = _.clone(optionsCreation);
+ optionsCreationList[i].json = { services: [] };
+ optionsCreationList[i].json.services[0] = _.clone(optionsCreation.json.services[0]);
+ optionsCreationList[i].json.services[0].apikey = 'qwertyuiop' + i;
+ creationFns.push(async.apply(request, optionsCreationList[i]));
+ }
+
+ async.series(creationFns, done);
+ });
+
+ it('should return the appropriate count of services', function (done) {
+ request(optionsConstrained, function (error, response, body) {
+ body.count.should.equal(10);
+ done();
+ });
+ });
+ });
+
+ describe('When a device info request arrives', function () {
+ beforeEach(function (done) {
+ async.series([async.apply(request, optionsCreation)], done);
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsGet, function (error, response, body) {
+ should.exist(body);
+ should.exist(body.count);
+ body.count.should.equal(1);
+ should.exist(body.services);
+ should.exist(body.services.length);
+ body.services.length.should.equal(1);
+ body.services[0].service.should.equal('testservice');
+ done();
+ });
+ });
+ });
+
+ describe('When a device info request arrives and multiple groups have been created', function () {
+ beforeEach(function (done) {
+ const optionsCreationList = [];
+ const creationFns = [];
+
+ for (let i = 0; i < 10; i++) {
+ optionsCreationList[i] = _.clone(optionsCreation);
+ optionsCreationList[i].json = { services: [] };
+ optionsCreationList[i].json.services[0] = _.clone(optionsCreation.json.services[0]);
+ optionsCreationList[i].json.services[0].apikey = 'qwertyuiop' + i;
+ creationFns.push(async.apply(request, optionsCreationList[i]));
+ }
+
+ async.series(creationFns, done);
+ });
+
+ it('should return all the configured device groups from the database', function (done) {
+ request(optionsGet, function (error, response, body) {
+ should.exist(body);
+ should.exist(body.count);
+ body.count.should.equal(10);
+ should.exist(body.services);
+ should.exist(body.services.length);
+ body.services.length.should.equal(10);
+ done();
+ });
+ });
+ });
+
+ describe('When the device info request with name and type', function () {
+ beforeEach(function (done) {
+ async.series([async.apply(request, optionsCreation)], done);
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.clearRegistry(done);
+ });
+
+ it('should return the name and type of device', function (done) {
+ request(optionsGet, function (error, response, body) {
+ should.exist(body);
+ should.exist(body.count);
+ body.count.should.equal(1);
+ should.exist(body.services);
+ should.exist(body.services.length);
+ body.services.length.should.equal(1);
+ should.exist(body.services[0].entity_type);
+ body.services[0].entity_type.should.equal('Light');
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json
deleted file mode 100644
index f32c1f5e7..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:TheLightType:TheFirstLight",
- "location": {
- "type": "GeoProperty",
- "value": {
- "coordinates": [
- 0,
- 0
- ],
- "type": "Point"
- }
- },
- "type": "TheLightType"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createDatetimeProvisionedDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createDatetimeProvisionedDevice.json
index 911249cab..3aa55258e 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/createDatetimeProvisionedDevice.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/createDatetimeProvisionedDevice.json
@@ -4,10 +4,7 @@
"id": "urn:ngsi-ld:MicroLights:FirstMicroLight",
"timestamp": {
"type": "Property",
- "value": {
- "@type": "DateTime",
- "@value": "1970-01-01T00:00:00.000Z"
- }
+ "value": "1970-01-01T00:00:00.000Z"
},
"type": "MicroLights"
}
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json
index 7ac1e70b7..24ae73918 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json
@@ -3,24 +3,15 @@
"@context": "http://context.json-ld",
"commandAttr_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": " "
},
"commandAttr_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "UNKNOWN"
},
"hardcodedAttr": {
"type": "Property",
- "value": {
- "@type": "hardcodedType",
- "@value": "hardcodedValue"
- }
+ "value": "hardcodedValue"
},
"id": "urn:ngsi-ld:TheLightType:TheFirstLight",
"type": "TheLightType"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json
index 7ac1e70b7..e8c1594d9 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceMultientity.json
@@ -3,24 +3,15 @@
"@context": "http://context.json-ld",
"commandAttr_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": " "
},
"commandAttr_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "UNKNOWN"
},
"hardcodedAttr": {
"type": "Property",
- "value": {
- "@type": "hardcodedType",
- "@value": "hardcodedValue"
- }
+ "value": "hardcodedType"
},
"id": "urn:ngsi-ld:TheLightType:TheFirstLight",
"type": "TheLightType"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json
index d53235976..17eb7b501 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json
@@ -5,45 +5,27 @@
"type": "TheLightType",
"hardcodedAttr": {
"type": "Property",
- "value": {
- "@type": "hardcodedType",
- "@value": "hardcodedValue"
- }
+ "value": "hardcodedType"
},
"bootstrapServer": {
"type": "Property",
- "value": {
- "@type": "Address",
- "@value": "127.0.0.1"
- }
+ "value": "127.0.0.1"
},
"commandAttr_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "UNKNOWN"
},
"commandAttr_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": "commandResult",
},
"wheel1_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "commandStatus"
},
"wheel1_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": "commandResult"
}
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json
index 7fcd1b6a7..6850f2319 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json
@@ -1,49 +1,15 @@
[
{
"@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:SensorMachine:TheFirstLight",
+ "id": "urn:ngsi-ld:SensorMachine:Light1",
"type": "SensorMachine",
- "hardcodedAttr": {
+ "status": {
"type": "Property",
- "value": {
- "@type": "hardcodedType",
- "@value": "hardcodedValue"
- }
+ "value": "STARTING"
},
"bootstrapServer": {
"type": "Property",
- "value": {
- "@type": "Address",
- "@value": "127.0.0.1"
- }
- },
- "commandAttr_status": {
- "type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
- },
- "commandAttr_info": {
- "type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
- },
- "wheel1_status": {
- "type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
- },
- "wheel1_info": {
- "type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": "127.0.0.1"
}
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext1.json
index c0f1143b8..e08981891 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContext1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext1.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"dimming": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "87"
- }
+ "value": "87"
},
"id": "urn:ngsi-ld:Light:light1",
"state": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json
index e79a2174c..d31ae9507 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext3WithStatic.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"bootstrapServer": {
"type": "Property",
- "value": {
- "@type": "Address",
- "@value": "127.0.0.1"
- }
+ "value": "127.0.0.1"
},
"id": "urn:ngsi-ld:SensorMachine:machine1",
"status": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json
index f159dab26..6f02c5ff7 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext4.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"bootstrapServer": {
"type": "Property",
- "value": {
- "@type": "Address",
- "@value": "127.0.0.1"
- }
+ "value": "Address"
},
"id": "urn:ngsi-ld:Light:light1",
"status": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContext5.json b/test/unit/ngsi-ld/examples/contextRequests/updateContext5.json
index 367b0f9c5..0b6bf8cef 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContext5.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContext5.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"bootstrapServer": {
"type": "Property",
- "value": {
- "@type": "Address",
- "@value": "127.0.0.1"
- }
+ "value": "Address"
},
"id": "urn:ngsi-ld:SensorMachine:Light1",
"status": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json
index 29ac970fe..7a61e367e 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin6.json
@@ -2,6 +2,10 @@
{
"@context": "http://context.json-ld",
"id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
+ "type": "Light",
+ "keep_alive": {
+ "type": "Property",
+ "value": "null"
+ }
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json
index 6e2a1a8b6..cc3056898 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin7.json
@@ -4,13 +4,7 @@
"id": "urn:ngsi-ld:Light:light1",
"tags": {
"type": "Property",
- "value": {
- "@type": "Array",
- "@value": [
- "iot",
- "device"
- ]
- }
+ "value":["iot","device"]
},
"type": "Light"
}
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json
index 0fee125e9..cd28c8a55 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin8.json
@@ -4,12 +4,9 @@
"configuration": {
"type": "Property",
"value": {
- "@type": "Object",
- "@value": {
- "firmware": {
- "hash": "cf23df2207d99a74fbe169e3eba035e633b65d94",
- "version": "1.1.0"
- }
+ "firmware": {
+ "version": "1.1.0",
+ "hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"
}
}
},
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin9.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin9.json
index cfde2a162..d9afc1e92 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin9.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin9.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"configuration": {
"type": "Property",
- "value": {
- "@type": "Object",
- "@value": "string_value"
- }
+ "value": "string_value"
},
"id": "urn:ngsi-ld:Light:light1",
"type": "Light"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast1.json
deleted file mode 100644
index c228cfb8c..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast1.json
+++ /dev/null
@@ -1,11 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "pressure": {
- "type": "Property",
- "value": 23
- },
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json
deleted file mode 100644
index 8e3da82c7..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "date": {
- "type": "Property",
- "value": {
- "@type": "Date",
- "@value": "2016-04-30"
- }
- },
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast2.json
deleted file mode 100644
index 8fc03d094..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast2.json
+++ /dev/null
@@ -1,11 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "temperature": {
- "type": "Property",
- "value": 14.4
- },
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast3.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast3.json
deleted file mode 100644
index 69a6ec287..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast3.json
+++ /dev/null
@@ -1,11 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "status": {
- "type": "Property",
- "value": true
- },
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast4.json
deleted file mode 100644
index 149eb0f0c..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast4.json
+++ /dev/null
@@ -1,11 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "status": {
- "type": "Property",
- "value": false
- },
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast5.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast5.json
deleted file mode 100644
index 29ac970fe..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast5.json
+++ /dev/null
@@ -1,7 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast6.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast6.json
deleted file mode 100644
index 6e2a1a8b6..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast6.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "tags": {
- "type": "Property",
- "value": {
- "@type": "Array",
- "@value": [
- "iot",
- "device"
- ]
- }
- },
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast7.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast7.json
deleted file mode 100644
index 0fee125e9..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast7.json
+++ /dev/null
@@ -1,19 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "configuration": {
- "type": "Property",
- "value": {
- "@type": "Object",
- "@value": {
- "firmware": {
- "hash": "cf23df2207d99a74fbe169e3eba035e633b65d94",
- "version": "1.1.0"
- }
- }
- }
- },
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json
deleted file mode 100644
index 281824555..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "time": {
- "type": "Property",
- "value": {
- "@type": "Time",
- "@value": "14:59:46"
- }
- },
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json
deleted file mode 100644
index 4c3262c57..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json
+++ /dev/null
@@ -1,14 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "dateTime": {
- "type": "Property",
- "value": {
- "@type": "DateTime",
- "@value": "2016-04-30T00:00:00.000Z"
- }
- },
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light"
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandError.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandError.json
index 5504bd98e..62edaab80 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandError.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandError.json
@@ -3,19 +3,13 @@
"@context": "http://context.json-ld",
"position_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "ERROR"
- }
+ "value": "ERROR"
},
"position_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": "Stalled"
- }
+ "value": "Stalled"
},
"id": "urn:ngsi-ld:Robot:r2d2",
"type": "Robot"
}
-]
\ No newline at end of file
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandExpired.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandExpired.json
index 7dab289fb..63fbed814 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandExpired.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandExpired.json
@@ -1,21 +1,15 @@
[
- {
- "@context":"http://context.json-ld",
- "position_status": {
- "type":"Property",
- "value": {
- "@type":"commandStatus",
- "@value":"ERROR"
- }
- },
- "position_info": {
- "type":"Property",
- "value": {
- "@type":"commandResult",
- "@value":"EXPIRED"
- }
- },
- "id":"urn:ngsi-ld:Robot:r2d2",
- "type":"Robot"
- }
+ {
+ "@context":"http://context.json-ld",
+ "position_status": {
+ "type":"Property",
+ "value": "ERROR"
+ },
+ "position_info": {
+ "type":"Property",
+ "value": "EXPIRED"
+ },
+ "id":"urn:ngsi-ld:Robot:r2d2",
+ "type":"Robot"
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandFinish.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandFinish.json
index 66bef5c77..c15efac1b 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandFinish.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandFinish.json
@@ -1,21 +1,15 @@
[
- {
- "@context":"http://context.json-ld",
- "position_status": {
- "type":"Property",
- "value": {
- "@type":"commandStatus",
- "@value":"FINISHED"
- }
- },
- "position_info": {
- "type":"Property",
- "value": {
- "@type":"commandResult",
- "@value":"[72, 368, 1]"
- }
- },
- "id":"urn:ngsi-ld:Robot:r2d2",
- "type":"Robot"
- }
+ {
+ "@context":"http://context.json-ld",
+ "position_status": {
+ "type": "Property",
+ "value": "FINISHED"
+ },
+ "position_info": {
+ "type":"Property",
+ "value": [72, 368, 1]
+ },
+ "id":"urn:ngsi-ld:Robot:r2d2",
+ "type":"Robot"
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandStatus1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandStatus1.json
index e74217af2..e0038d816 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandStatus1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCommandStatus1.json
@@ -5,17 +5,12 @@
"type": "Robot",
"position_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "UNKNOWN"
+
},
"position_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": " "
}
}
-]
\ No newline at end of file
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json
index 9b5cdd7f9..5de20c417 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp1.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"TheTargetValue": {
"type": "Property",
- "value": {
- "@type": "DateTime",
- "@value": "2007-11-03T13:18:05.000Z"
- }
+ "value": "2007-11-03T13:18:05.000Z"
},
"id": "urn:ngsi-ld:Light:light1",
"state": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json
index d2bd8aa82..84940e1a0 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextCompressTimestamp2.json
@@ -3,10 +3,7 @@
"@context": "http://context.json-ld",
"TheTargetValue": {
"type": "Property",
- "value": {
- "@type": "DateTime",
- "@value": "2007-11-03T13:18:05.000Z"
- }
+ "value": "2007-11-03T13:18:05.000Z"
},
"id": "urn:ngsi-ld:Light:light1",
"state": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json
index 3715ad44d..1683e5130 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json
@@ -1,18 +1,15 @@
[
{
"@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:WeatherStation:ws1",
+ "type": "WeatherStation",
"pressure": {
"type": "Property",
"value": 1040
},
"weather": {
"type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity NaN and pressure 1040"
- }
- },
- "id": "urn:ngsi-ld:WeatherStation:ws1",
- "type": "WeatherStation"
+ "value": "Humidity NaN and pressure 20800"
+ }
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json
index cb9258b14..b8120457b 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json
@@ -1,25 +1,19 @@
[
- {
- "@context": "http://context.json-ld",
- "humidity": {
- "type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
- },
- "id": "urn:ngsi-ld:WeatherStation:ws1",
- "pressure": {
- "type": "Property",
- "value": 1040
- },
- "type": "WeatherStation",
- "weather": {
- "type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity 6 and pressure 1040"
- }
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:WeatherStation:ws1",
+ "type": "WeatherStation",
+ "pressure": {
+ "type": "Property",
+ "value": 1040
+ },
+ "humidity": {
+ "type": "Property",
+ "value": "12"
+ },
+ "weather": {
+ "type": "Property",
+ "value": "Humidity 6 and pressure 20800"
}
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json
index 3715ad44d..1683e5130 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json
@@ -1,18 +1,15 @@
[
{
"@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:WeatherStation:ws1",
+ "type": "WeatherStation",
"pressure": {
"type": "Property",
"value": 1040
},
"weather": {
"type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity NaN and pressure 1040"
- }
- },
- "id": "urn:ngsi-ld:WeatherStation:ws1",
- "type": "WeatherStation"
+ "value": "Humidity NaN and pressure 20800"
+ }
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json
index 91013ba04..b85657ee1 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json
@@ -1,18 +1,17 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:GPS:gps1",
- "type": "GPS",
- "location": {
- "type": "GeoProperty",
- "value": {
- "coordinates": [
- 13,
- 52
- ],
- "type": "Point"
- },
- "observedAt": "1970-01-01T00:00:00.001Z"
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:GPS:gps1",
+ "type": "GPS",
+ "location": {
+ "type": "GeoProperty",
+ "value": {
+ "coordinates": [
+ 13,
+ 52
+ ],
+ "type": "Point"
+ }
}
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json
index 9d06bc7cb..f7939414e 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json
@@ -5,10 +5,7 @@
"type": "WeatherStation",
"weather": {
"type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity 6 and pressure 1040"
- }
+ "value": "Humidity 6 and pressure 1040"
},
"pressure25": {
"type": "Property",
@@ -16,10 +13,7 @@
},
"humidity12": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
+ "value": "12"
}
}
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json
index e2d7752a8..49aa562d6 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json
@@ -5,10 +5,7 @@
"type": "WeatherStation",
"weather": {
"type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity 6 and pressure 1040"
- }
+ "value": "Humidity 6 and pressure 1040"
},
"pressure25": {
"type": "Property",
@@ -16,17 +13,11 @@
},
"humidity12": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
+ "value": "12"
},
"alive": {
"type": "Property",
- "value": {
- "@type": "None",
- "@value": "undefined"
- }
+ "value": "undefined"
},
"updated": {
"type": "Property",
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin6.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin6.json
index abd2b49da..62450a3c3 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin6.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin6.json
@@ -5,11 +5,9 @@
"manufacturer": {
"type": "Property",
"value": {
- "@type": "Object",
- "@value": {
- "VAT": "U12345678",
- "name": "Manufacturer1"
- }
+ "VAT": "U12345678",
+ "name": "Manufacturer1"
+
}
},
"type": "Light"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin7.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin7.json
index 34e1869a5..7916a6396 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin7.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin7.json
@@ -3,14 +3,11 @@
"@context": "http://context.json-ld",
"revisions": {
"type": "Property",
- "value": {
- "@type": "Array",
- "@value": [
+ "value": [
"v0.1",
"v0.2",
"v0.3"
]
- }
},
"id": "urn:ngsi-ld:Light:light1",
"type": "Light"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin8a.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin8a.json
index 2e95aa9bf..bd37a0910 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin8a.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin8a.json
@@ -7,10 +7,7 @@
},
"weather": {
"type": "Property",
- "value": {
- "@type": "Summary",
- "@value": "Humidity NaN and pressure NaN"
- }
+ "value": "Humidity NaN and pressure NaN"
},
"id": "urn:ngsi-ld:WeatherStation:ws1",
"type": "WeatherStation"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextJsonProperty.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextJsonProperty.json
new file mode 100644
index 000000000..4df548440
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextJsonProperty.json
@@ -0,0 +1,13 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "config": {
+ "type": "JsonProperty",
+ "json": {
+ "foo": "bar"
+ }
+ },
+ "id": "urn:ngsi-ld:Light:light1",
+ "type": "Light"
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperties1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperty.json
similarity index 100%
rename from test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperties1.json
rename to test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperty.json
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextListProperty.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextListProperty.json
new file mode 100644
index 000000000..6f000d283
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextListProperty.json
@@ -0,0 +1,14 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "lightValues": {
+ "type": "ListProperty",
+ "listValue": [
+ 0,
+ 1
+ ]
+ },
+ "id": "urn:ngsi-ld:Light:light1",
+ "type": "Light"
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextListRelationship.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextListRelationship.json
new file mode 100644
index 000000000..293a95eb0
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextListRelationship.json
@@ -0,0 +1,14 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "placedIn": {
+ "type": "ListRelationship",
+ "listObject": [
+ "unr:ngsi-ld:xxx",
+ "unr:ngsi-ld:yyyy"
+ ]
+ },
+ "id": "urn:ngsi-ld:Light:light1",
+ "type": "Light"
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json
index 2b3d4dd04..a0b46d6f5 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json
@@ -1,26 +1,20 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WeatherStation:ws4",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "52"
- }
- },
- "type": "WeatherStation"
- },
- {
- "@context": "http://context.json-ld",
- "humidity": {
- "type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
- },
- "id": "urn:ngsi-ld:Higrometer:Higro2000",
- "type": "Higrometer"
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:WeatherStation:ws4",
+ "type": "WeatherStation",
+ "pressure": {
+ "type": "Property",
+ "value": "52"
}
+ },
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2000",
+ "type": "Higrometer",
+ "humidity": {
+ "type": "Property",
+ "value": "12"
+ }
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json
index c06827078..760599f78 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json
@@ -1,9 +1,4 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:GPS:gps1",
- "type": "GPS"
- },
{
"@context": "http://context.json-ld",
"explicit": {
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin2.json
index e5cd1461d..b565aa1d3 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin2.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin2.json
@@ -4,10 +4,7 @@
"id": "urn:ngsi-ld:WeatherStation:ws4",
"pressure": {
"type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "52"
- }
+ "value": "52"
},
"type": "WeatherStation"
},
@@ -15,10 +12,7 @@
"@context": "http://context.json-ld",
"humidity": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
+ "value": "12"
},
"id": "urn:ngsi-ld:WeatherStation:Higro2000",
"type": "WeatherStation"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin3.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin3.json
index af10f62ab..0bec28bb9 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin3.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin3.json
@@ -4,10 +4,7 @@
"id": "urn:ngsi-ld:WeatherStation:ws4",
"pressure": {
"type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "52"
- }
+ "value": "52"
},
"sn": {
"type": "Property",
@@ -19,10 +16,8 @@
"@context": "http://context.json-ld",
"humidity": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- }
+ "value": "12"
+
},
"id": "urn:ngsi-ld:WeatherStation:Station Number 50",
"type": "WeatherStation"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json
index 5271e6c90..d64cb78b0 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json
@@ -1,19 +1,11 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WeatherStation:ws5",
- "type": "WeatherStation"
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2000",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "16"
- }
- },
- "type": "Higrometer"
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2000",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Property",
+ "value": "16"
}
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json
index 4f11b81b9..403d21c9c 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json
@@ -1,31 +1,20 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WeatherStation:ws6",
- "type": "WeatherStation"
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2000",
- "type": "Higrometer",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "16"
- }
- }
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2002",
- "type": "Higrometer",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "17"
- }
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2002",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Property",
+ "value": "17"
}
+ },
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2000",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Property",
+ "value": "16"
+ }
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json
index 5f2038409..133397b41 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json
@@ -1,16 +1,11 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Sensor:Sensor",
- "type": "Sensor"
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WM:SO1",
- "type": "WM",
- "vol": {
- "type": "Property",
- "value": 38
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:WM:SO1",
+ "type": "WM",
+ "vol": {
+ "type": "Property",
+ "value": 38
}
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json
index ae16c90ef..fb32a654e 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json
@@ -1,9 +1,4 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Sensor:Sensor",
- "type": "Sensor"
- },
{
"@context": "http://context.json-ld",
"id": "urn:ngsi-ld:WM:SO1",
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json
index f1b3973a2..d1f24a80e 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json
@@ -1,32 +1,21 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WeatherStation:ws7",
- "type": "WeatherStation"
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2000",
- "type": "Higrometer",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "16"
- }
- }
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2002",
- "type": "Higrometer",
- "pressure": {
- "type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "17"
- },
- "unitCode": "Hgmm"
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2002",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Property",
+ "value": "17",
+ "unitCode": "Hgmm"
}
+ },
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2000",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Property",
+ "value": "16"
+ }
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json
index 8e0f228ae..078558e43 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json
@@ -5,10 +5,7 @@
"type": "WeatherStation",
"pressure": {
"type": "Property",
- "value": {
- "@type": "Hgmm",
- "@value": "52"
- },
+ "value": "52",
"observedAt": "2016-05-30T16:25:22.304Z"
}
},
@@ -18,10 +15,7 @@
"type": "Higrometer",
"humidity": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- },
+ "value": "12",
"observedAt": "2023-03-21T18:30:30.948Z"
}
}
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
index b3fe3fae5..595303fd8 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
@@ -1,20 +1,12 @@
[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:WeatherStation:ws4",
- "type": "WeatherStation"
- },
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Higrometer:Higro2000",
- "type": "Higrometer",
- "humidity": {
- "type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "12"
- },
- "observedAt": "2023-03-21T16:54:11.464Z"
- }
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Higrometer:Higro2000",
+ "type": "Higrometer",
+ "humidity": {
+ "type": "Property",
+ "value": "12",
+ "observedAt": "2024-06-25T16:04:13.914Z"
}
+ }
]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
index 2c103d9dd..1f4724269 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
@@ -10,10 +10,7 @@
"type": "Higrometer",
"humidity": {
"type": "Property",
- "value": {
- "@type": "Percentage",
- "@value": "16"
- },
+ "value": "16",
"observedAt": "2023-03-21T16:57:54.046Z"
}
}
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin4.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
index 0878e85bc..2d6676a49 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
@@ -4,18 +4,12 @@
"PING_info": {
"observedAt": "2015-08-05T07:35:01.468Z",
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": "1234567890"
- }
+ "value": "1234567890"
},
"PING_status": {
"observedAt": "2015-08-05T07:35:01.468Z",
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "OK"
- }
+ "value": "OK"
},
"id": "urn:ngsi-ld:SensorCommand:sensorCommand",
"type": "SensorCommand"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json
deleted file mode 100644
index 71be00a49..000000000
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json
+++ /dev/null
@@ -1,12 +0,0 @@
-[
- {
- "@context": "http://context.json-ld",
- "id": "urn:ngsi-ld:Light:light1",
- "type": "Light",
- "state": {
- "type": "Property",
- "value": true,
- "observedAt": "2016-05-30T16:25:22.304Z"
- }
- }
-]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextRelationship.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextRelationship.json
new file mode 100644
index 000000000..7e6a5dfa6
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextRelationship.json
@@ -0,0 +1,11 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "locatedIn": {
+ "type": "Relationship",
+ "object": "urn:ngsi-ld:XXX"
+ },
+ "id": "urn:ngsi-ld:Light:light1",
+ "type": "Light"
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json
index b6795bf8b..47ce7fce0 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json
@@ -1,14 +1,8 @@
[
{
"@context": "http://context.json-ld",
- "luminosity": {
- "type": "Property",
- "value": 87,
- "providedBy": {
- "type": "Relationship",
- "object": "urn:ngsi-ld:Lamp:lamp1"
- }
- },
+ "id": "urn:ngsi-ld:Lamp:lamp1",
+ "type": "Lamp",
"controlledAsset": {
"type": "Relationship",
"object": "urn:ngsi-ld:Building:001"
@@ -21,8 +15,15 @@
"value": "bell"
}
},
- "id": "urn:ngsi-ld:Lamp:lamp1",
- "type": "Lamp"
+ "luminosity": {
+ "type": "Property",
+ "value": 87,
+ "unitCode": "CAL",
+ "providedBy": {
+ "type": "Relationship",
+ "object": "urn:ngsi-ld:Lamp:lamp1"
+ }
+ }
},
{
"@context": "http://context.json-ld",
@@ -31,6 +32,7 @@
"luminosity": {
"type": "Property",
"value": 87,
+ "unitCode": "CAL",
"providedBy": {
"type": "Relationship",
"object": "urn:ngsi-ld:Lamp:lamp1"
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType1.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType1.json
new file mode 100644
index 000000000..0a0cd0560
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType1.json
@@ -0,0 +1,51 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Light:ws1",
+ "type": "Light",
+ "consumption": {
+ "type": "Property",
+ "value": 1,
+ "valueType": "Number"
+ },
+ "alive": {
+ "type": "Property",
+ "value": "foo"
+ },
+ "updated": {
+ "type": "Property",
+ "value": false,
+ "valueType": "Boolean"
+ },
+ "manufacturer": {
+ "type": "Property",
+ "value": {
+ "hello": "world"
+ },
+ "valueType": "Object"
+ },
+ "revisions": {
+ "type": "Property",
+ "value": [
+ 1,
+ 2
+ ],
+ "valueType": "Array"
+ },
+ "date": {
+ "type": "Property",
+ "value": "2025-07-18",
+ "valueType": "Date"
+ },
+ "time": {
+ "type": "Property",
+ "value": "14:22:11",
+ "valueType": "Time"
+ },
+ "datetime": {
+ "type": "Property",
+ "value": "2025-07-18T14:22:11.000Z",
+ "valueType": "DateTime"
+ }
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType2.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType2.json
new file mode 100644
index 000000000..d8a4dad2c
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextValueType2.json
@@ -0,0 +1,65 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "id": "urn:ngsi-ld:Light:ws1",
+ "type": "Light",
+ "consumption": {
+ "type": "Property",
+ "value": {
+ "@type": "Number",
+ "@value": 1
+ }
+ },
+ "alive": {
+ "type": "Property",
+ "value": "foo"
+ },
+ "updated": {
+ "type": "Property",
+ "value": {
+ "@type": "Boolean",
+ "@value": false
+ }
+ },
+ "manufacturer": {
+ "type": "Property",
+ "value": {
+ "@type": "Object",
+ "@value": {
+ "hello": "world"
+ }
+ }
+ },
+ "revisions": {
+ "type": "Property",
+ "value": {
+ "@type": "Array",
+ "@value": [
+ 1,
+ 2
+ ]
+ }
+ },
+ "date": {
+ "type": "Property",
+ "value": {
+ "@type": "Date",
+ "@value": "2025-07-18"
+ }
+ },
+ "time": {
+ "type": "Property",
+ "value": {
+ "@type": "Time",
+ "@value": "14:22:11"
+ }
+ },
+ "datetime": {
+ "type": "Property",
+ "value": {
+ "@type": "DateTime",
+ "@value": "2025-07-18T14:22:11.000Z"
+ }
+ }
+ }
+ ]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateContextVocabProperty.json b/test/unit/ngsi-ld/examples/contextRequests/updateContextVocabProperty.json
new file mode 100644
index 000000000..65530775e
--- /dev/null
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateContextVocabProperty.json
@@ -0,0 +1,11 @@
+[
+ {
+ "@context": "http://context.json-ld",
+ "category": {
+ "type": "VocabProperty",
+ "vocab": "actuator"
+ },
+ "id": "urn:ngsi-ld:Light:light1",
+ "type": "Light"
+ }
+]
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateProvisionCommands1.json b/test/unit/ngsi-ld/examples/contextRequests/updateProvisionCommands1.json
index 1c4baad27..dfab9572c 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateProvisionCommands1.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateProvisionCommands1.json
@@ -4,17 +4,11 @@
"id": "urn:ngsi-ld:Light:light1",
"move_info": {
"type": "Property",
- "value": {
- "@type": "commandResult",
- "@value": " "
- }
+ "value": " "
},
"move_status": {
"type": "Property",
- "value": {
- "@type": "commandStatus",
- "@value": "UNKNOWN"
- }
+ "value": "UNKNOWN"
},
"type": "Light"
}
diff --git a/test/unit/ngsi-ld/examples/contextRequests/updateProvisionDeviceStatic.json b/test/unit/ngsi-ld/examples/contextRequests/updateProvisionDeviceStatic.json
index 091ca684c..aa26869d4 100644
--- a/test/unit/ngsi-ld/examples/contextRequests/updateProvisionDeviceStatic.json
+++ b/test/unit/ngsi-ld/examples/contextRequests/updateProvisionDeviceStatic.json
@@ -18,10 +18,7 @@
},
"serverURL": {
"type": "Property",
- "value": {
- "@type": "URL",
- "@value": "http://fakeserver.com"
- }
+ "value": "http://fakeserver.com"
},
"type": "MicroLights"
}
diff --git a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json b/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json
deleted file mode 100644
index 6397da5b9..000000000
--- a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "data": [
- {
- "id": "TheFirstLight",
- "location": {
- "type": "geo:point",
- "value": "12.4, -9.6"
- },
- "type": "TheLightType"
- }
- ],
- "subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
-}
diff --git a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json b/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json
deleted file mode 100644
index 39c804bd0..000000000
--- a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "data": [
- {
- "id": "TheFirstLight",
- "location": [
- {
- "type": "GeoProperty",
- "value": "12.4, -9.6",
- "datasetId": "urn:ngsi-ld:Property:do-this"
- },
- {
- "type": "GeoProperty",
- "value": "6, 10",
- "datasetId": "urn:ngsi-ld:Property:then-do-this"
- }
- ],
- "type": "TheLightType"
- }
- ],
- "subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
-}
diff --git a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json b/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json
deleted file mode 100644
index c9915a958..000000000
--- a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "data": [
- {
- "id": "TheFirstLight",
- "location": {
- "type": "GeoProperty",
- "value": "12.4, -9.6",
- "qos": {
- "type": "Property",
- "value": 1
- }
- },
- "type": "TheLightType"
- }
- ],
- "subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
-}
diff --git a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json b/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json
deleted file mode 100644
index 734509404..000000000
--- a/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "@context": "http://context.json-ld",
- "type": "Subscription",
- "entities": [
- {
- "id": "TheFirstLight",
- "type": "TheLightType"
- }
- ],
- "watchedAttributes": [
- "location"
- ],
- "notification": {
- "endpoint": {
- "uri": "http://smartgondor.com/notify",
- "accept": "application/json"
- },
- "attributes": [
- "location"
- ],
- "format": "normalized"
- }
-}
diff --git a/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js b/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js
index a7debd1de..43bf6707e 100644
--- a/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js
+++ b/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js
@@ -253,8 +253,7 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M',
- throttling: 'PT5S'
+ deviceRegistrationDuration: 'P1M'
};
const iotAgentConfigTS = {
@@ -306,40 +305,6 @@ describe('NGSI-LD: JEXL', function () {
});
});
- describe('When an update comes for expressions with syntax errors', function () {
- // Case: Update for an attribute with bad expression
- const values = [
- {
- name: 'p',
- type: 'centigrades',
- value: '52'
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin30.json'
- )
- )
- .reply(204);
- });
-
- it('should ignore the expression before sending the values', function (done) {
- iotAgentLib.update('light1', 'LightError', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
describe('When there are expression attributes that are just calculated (not sent by the device)', function () {
// Case: Expression which results is sent as a new attribute
const values = [
@@ -557,41 +522,6 @@ describe('NGSI-LD: JEXL', function () {
});
});
- describe('When an update comes for attributes without expressions and NULL type', function () {
- // Case: Update for a Null attribute without expression
-
- const values = [
- {
- name: 'a',
- type: 'None',
- value: null
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin5.json'
- )
- )
- .reply(204);
- });
-
- it('should apply the expression before sending the values', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
describe('When an update comes for attributes without expressions and Boolean type', function () {
// Case: Update for a Boolean attribute without expression
@@ -696,39 +626,6 @@ describe('NGSI-LD: JEXL', function () {
});
});
- describe('When there are expressions including other attributes and they are not updated', function () {
- const values = [
- {
- name: 'x',
- type: 'Number',
- value: 0.44
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin12a.json'
- )
- )
- .reply(204);
- });
-
- it('should apply the expression before sending the values', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
describe('When there are expressions including other attributes and they are updated', function () {
const values = [
{
diff --git a/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js b/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js
index 9bfdd7590..980425b73 100644
--- a/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js
+++ b/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js
@@ -59,8 +59,7 @@ const iotAgentConfig = {
}
},
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M',
- throttling: 'PT5S'
+ deviceRegistrationDuration: 'P1M'
};
describe('NGSI-LD - JSON-LD @context parsing from environment variable', function () {
diff --git a/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js b/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js
index 8e248ced3..884b7cf3a 100644
--- a/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js
+++ b/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js
@@ -289,16 +289,6 @@ describe('NGSI-LD - Secured access to the Context Broker with OAuth2 provider',
)
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations//6319a7f5254b05844116584d' });
- contextBrokerMock
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/' +
- 'contextRequests/createProvisionedDeviceWithGroupAndStatic3.json'
- )
- )
- .reply(204);
-
contextBrokerMock
.post(
'/ngsi-ld/v1/subscriptions/',
@@ -318,8 +308,8 @@ describe('NGSI-LD - Secured access to the Context Broker with OAuth2 provider',
});
it('subscribe requests use auth header', function (done) {
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -339,8 +329,8 @@ describe('NGSI-LD - Secured access to the Context Broker with OAuth2 provider',
contextBrokerMock.delete('/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8', '').reply(204);
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
contextBrokerMock.done();
done();
@@ -745,7 +735,7 @@ describe(
.matchHeader('fiware-service', 'testservice')
.matchHeader('authorization', 'Bearer bea752e377680acd1349a3ed59db855a1db07zxc')
.post(
- '/ngsi-ld/v1/entityOperations/upsert/',
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
utils.readExampleFile(
'./test/unit/ngsi-ld/examples/' +
'contextRequests/createProvisionedDeviceWithGroupAndStatic2.json'
@@ -753,15 +743,6 @@ describe(
)
.reply(204);
- contextBrokerMock3 = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('authorization', 'Bearer zzz752e377680acd1349a3ed59db855a1db07bbb')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContext5.json')
- )
- .reply(204);
-
iotAgentConfig.authentication.tokenPath = '/oauth2/token';
iotAgentLib.activate(iotAgentConfig, function () {
done();
@@ -779,7 +760,6 @@ describe(
should.not.exist(error);
response.statusCode.should.equal(201);
contextBrokerMock.done();
- contextBrokerMock2.done();
done();
});
});
@@ -787,7 +767,7 @@ describe(
it('should send the mixed data to the Context Broker', function (done) {
iotAgentLib.update('Light1', 'SensorMachine', '', values, function (error) {
should.not.exist(error);
- contextBrokerMock3.done();
+ contextBrokerMock2.done();
done();
});
});
@@ -833,7 +813,7 @@ describe(
contextBrokerMock = nock('http://unexistentHost:1026')
.matchHeader('fiware-service', 'testservice')
- .matchHeader('Authorization', 'Bearer 999210dacf913772606c95dd0b895d5506cbc988')
+ .matchHeader('Authorization', 'Bearer 000210dacf913772606c95dd0b895d5506cbc700')
.post(
'/ngsi-ld/v1/entityOperations/upsert/?options=update',
utils.readExampleFile(
@@ -849,6 +829,7 @@ describe(
});
});
});
+
it('should send the permanent token in the auth header', function (done) {
iotAgentLib.update('machine1', 'SensorMachine', '', values, function (error) {
should.not.exist(error);
diff --git a/test/unit/ngsi-ld/general/https-support-test.js b/test/unit/ngsi-ld/general/https-support-test.js
index fb6221091..ca855c7c3 100644
--- a/test/unit/ngsi-ld/general/https-support-test.js
+++ b/test/unit/ngsi-ld/general/https-support-test.js
@@ -213,8 +213,8 @@ describe('NGSI-LD - HTTPS support tests', function () {
});
it('should send the appropriate request to the Context Broker', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
diff --git a/test/unit/ngsi-ld/general/startup-test.js b/test/unit/ngsi-ld/general/startup-test.js
index 8ffee430a..d37bfe4ab 100644
--- a/test/unit/ngsi-ld/general/startup-test.js
+++ b/test/unit/ngsi-ld/general/startup-test.js
@@ -89,6 +89,7 @@ describe('NGSI-LD - Startup tests', function () {
process.env.IOTA_FALLBACK_PATH = 'smartgondor';
process.env.IOTA_LD_SUPPORT_NULL = 'false';
process.env.IOTA_LD_SUPPORT_DATASET_ID = 'false';
+ process.env.IOTA_LD_SUPPORT_DATA_TYPE = 'valueType';
nock.cleanAll();
@@ -125,6 +126,7 @@ describe('NGSI-LD - Startup tests', function () {
delete process.env.IOTA_FALLBACK_PATH;
delete process.env.IOTA_LD_SUPPORT_NULL;
delete process.env.IOTA_LD_SUPPORT_DATASET_ID;
+ delete process.env.IOTA_LD_SUPPORT_DATA_TYPE;
});
afterEach(function (done) {
@@ -155,6 +157,7 @@ describe('NGSI-LD - Startup tests', function () {
config.getConfig().mongodb.port.should.equal('5555');
config.getConfig().mongodb.db.should.equal('themongodb');
config.getConfig().mongodb.replicaSet.should.equal('customReplica');
+ config.getConfig().server.ldSupport.dataType.should.equal('valueType');
done();
});
});
diff --git a/test/unit/ngsi-ld/lazyAndCommands/command-test.js b/test/unit/ngsi-ld/lazyAndCommands/command-test.js
index 2883baf48..a493b4820 100644
--- a/test/unit/ngsi-ld/lazyAndCommands/command-test.js
+++ b/test/unit/ngsi-ld/lazyAndCommands/command-test.js
@@ -131,11 +131,6 @@ describe('NGSI-LD - Command functionalities', function () {
)
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
iotAgentLib.activate(iotAgentConfig, done);
});
@@ -1192,7 +1187,7 @@ describe('NGSI-LD - Command functionalities', function () {
});
it('should update its value and status in the Context Broker', function (done) {
- iotAgentLib.setCommandResult('r2d2', 'Robot', '', 'position', '[72, 368, 1]', 'FINISHED', function (error) {
+ iotAgentLib.setCommandResult('r2d2', 'Robot', '', 'position', [72, 368, 1], 'FINISHED', function (error) {
should.not.exist(error);
statusAttributeMock.done();
done();
diff --git a/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js b/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js
index 0ae8b1eba..c7ed8ffbc 100644
--- a/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js
+++ b/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js
@@ -226,7 +226,7 @@ describe('NGSI-LD - IoT Agent Lazy Devices', function () {
it('should call the device handler for each of the contexts');
});
- describe('When a context query arrives to the IoT Agent', function () {
+ describe('When a context query arrives to the IoT Agent using retrieveEntity', function () {
const options = {
url:
'http://localhost:' +
@@ -294,6 +294,76 @@ describe('NGSI-LD - IoT Agent Lazy Devices', function () {
});
});
+ describe('When a context query arrives to the IoT Agent using queryEntities', function () {
+ const options = {
+ url:
+ 'http://localhost:' +
+ iotAgentConfig.server.port +
+ '/ngsi-ld/v1/entities/?id=urn:ngsi-ld:Light:light1&attrs=dimming',
+ method: 'GET',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': 'gardens'
+ }
+ };
+ const sensorData = [
+ {
+ id: 'Light:light1',
+ type: 'Light',
+ dimming: {
+ type: 'Percentage',
+ value: 19
+ }
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .post(
+ '/ngsi-ld/v1/csourceRegistrations/',
+ utils.readExampleFile(
+ './test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent1.json'
+ )
+ )
+ .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
+
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .post('/ngsi-ld/v1/entityOperations/upsert/')
+ .reply(204);
+
+ async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
+ });
+
+ it('should return the information querying the underlying devices', function (done) {
+ const expectedResponse = [
+ utils.readExampleFile(
+ './test/unit/ngsi-ld/examples/contextProviderResponses/queryInformationResponse.json'
+ )
+ ];
+
+ iotAgentLib.setDataUpdateHandler(function (id, type, service, subservice, attributes, callback) {
+ callback(null);
+ });
+
+ iotAgentLib.setDataQueryHandler(function (id, type, service, subservice, attributes, callback) {
+ id.should.equal('urn:ngsi-ld:' + device1.type + ':' + device1.id);
+ type.should.equal(device1.type);
+ attributes[0].should.equal('dimming');
+ callback(null, sensorData[0]);
+ });
+
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ body.should.eql(expectedResponse);
+ done();
+ });
+ });
+ });
+
describe('When a context query arrives to the IoT Agent and no handler is set', function () {
const options = {
url:
diff --git a/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js b/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js
index d6ff3d08d..40d0261b4 100644
--- a/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js
+++ b/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js
@@ -55,17 +55,7 @@ const iotAgentConfig = {
types: {
Robot: {
internalAttributes: [],
- commands: [
- {
- name: 'position',
- object_id: 'pos',
- type: 'Object'
- },
- {
- name: 'orientation',
- type: 'Object'
- }
- ],
+ commands: [],
lazy: [
{
name: 'batteryLevel',
@@ -82,7 +72,18 @@ const iotAgentConfig = {
const device3 = {
id: 'r2d2',
type: 'Robot',
- service: 'smartgondor'
+ service: 'smartgondor',
+ commands: [
+ {
+ name: 'position',
+ object_id: 'pos',
+ type: 'Object'
+ },
+ {
+ name: 'orientation',
+ type: 'Object'
+ }
+ ]
};
describe('NGSI-LD - Merge-Patch functionalities', function () {
diff --git a/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js b/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js
index 322c37f23..f0959ceb0 100644
--- a/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js
+++ b/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js
@@ -90,12 +90,7 @@ const iotAgentConfig = {
active: []
},
Robot: {
- commands: [
- {
- name: 'position',
- type: 'Array'
- }
- ],
+ commands: [],
lazy: [],
staticAttributes: [],
active: []
@@ -121,7 +116,13 @@ const device3 = {
type: 'Robot',
service: 'smartgondor',
subservice: 'gardens',
- polling: true
+ polling: true,
+ commands: [
+ {
+ name: 'position',
+ type: 'Array'
+ }
+ ]
};
describe('NGSI-LD - Polling commands', function () {
@@ -170,16 +171,6 @@ describe('NGSI-LD - Polling commands', function () {
};
beforeEach(function (done) {
- statusAttributeMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextCommandStatus1.json'
- )
- )
- .reply(204);
-
iotAgentLib.register(device3, function (error) {
done();
});
@@ -209,6 +200,7 @@ describe('NGSI-LD - Polling commands', function () {
done();
});
});
+
it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
callback(null);
@@ -216,7 +208,6 @@ describe('NGSI-LD - Polling commands', function () {
request(options, function (error, response, body) {
should.not.exist(error);
- statusAttributeMock.done();
done();
});
});
diff --git a/test/unit/ngsi-ld/ngsiService/active-devices-test.js b/test/unit/ngsi-ld/ngsiService/active-devices-test.js
index 151ce0e4b..b799af782 100644
--- a/test/unit/ngsi-ld/ngsiService/active-devices-test.js
+++ b/test/unit/ngsi-ld/ngsiService/active-devices-test.js
@@ -600,7 +600,6 @@ describe('NGSI-LD - Active attributes test', function () {
should.exist(error.name);
error.code.should.equal(207);
error.details.notUpdated.should.equal('someEntities');
- error.message.should.equal('Error accesing entity data for device: light1 of type: Light');
error.name.should.equal('ENTITY_GENERIC_ERROR');
contextBrokerMock.done();
done();
diff --git a/test/unit/ngsiv2/ngsiService/autocast-test.js b/test/unit/ngsi-ld/ngsiService/attributeTypes-test.js
similarity index 50%
rename from test/unit/ngsiv2/ngsiService/autocast-test.js
rename to test/unit/ngsi-ld/ngsiService/attributeTypes-test.js
index e7777459f..bf5716ac6 100644
--- a/test/unit/ngsiv2/ngsiService/autocast-test.js
+++ b/test/unit/ngsi-ld/ngsiService/attributeTypes-test.js
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
+ * Copyright 2022 Telefonica Investigación y Desarrollo, S.A.U
*
* This file is part of fiware-iotagent-lib
*
@@ -23,20 +23,21 @@
* Modified by: Daniel Calvo - ATOS Research & Innovation
*/
-/* eslint-disable no-useless-concat */
+/* eslint-disable no-unused-vars */
const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
const utils = require('../../../tools/utils');
+const request = utils.request;
const should = require('should');
const logger = require('logops');
const nock = require('nock');
let contextBrokerMock;
const iotAgentConfig = {
- autocast: true,
contextBroker: {
host: '192.168.1.1',
port: '1026',
- ngsiVersion: 'v2'
+ ngsiVersion: 'ld',
+ jsonLdContext: 'http://context.json-ld'
},
server: {
port: 4041,
@@ -48,32 +49,28 @@ const iotAgentConfig = {
type: 'Light',
active: [
{
- name: 'pressure',
- type: 'Number'
+ name: 'name',
+ type: 'LanguageProperty'
},
{
- name: 'temperature',
- type: 'Number'
+ name: 'locatedIn',
+ type: 'Relationship'
},
{
- name: 'id',
- type: 'String'
+ name: 'lightValues',
+ type: 'ListProperty'
},
{
- name: 'status',
- type: 'Boolean'
+ name: 'placedIn',
+ type: 'ListRelationship'
},
{
- name: 'keep_alive',
- type: 'None'
+ name: 'categories',
+ type: 'VocabProperty'
},
{
- name: 'tags',
- type: 'Array'
- },
- {
- name: 'configuration',
- type: 'Object'
+ name: 'config',
+ type: 'JsonProperty'
}
]
}
@@ -83,7 +80,7 @@ const iotAgentConfig = {
providerUrl: 'http://smartgondor.com'
};
-describe('NGSI-v2 - JSON native types autocast test', function () {
+describe('NGSI-LD: Attribute types test', function () {
beforeEach(function () {
logger.setLevel('FATAL');
});
@@ -92,46 +89,16 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
iotAgentLib.deactivate(done);
});
- describe('When the IoT Agent receives new information from a device. Observation with Number type and Integer value', function () {
- const values = [
- {
- name: 'pressure',
- type: 'Number',
- value: '23'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast1.json')
- )
- .query({ type: 'Light' })
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Number type and Float value', function () {
+ describe('When the IoT Agent receives new exonym from a device name with LanguageProperty type and a JSON object', function () {
const values = [
{
- name: 'temperature',
- type: 'Number',
- value: '14.4'
+ name: 'name',
+ type: 'LanguageProperty',
+ value: {
+ el: 'Κωνσταντινούπολις',
+ en: 'Constantinople',
+ tr: 'İstanbul'
+ }
}
];
@@ -140,18 +107,18 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast2.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile(
+ './test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperty.json'
+ )
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the LanguageMap of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -160,12 +127,12 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
});
});
- describe('When the IoT Agent receives new information from a device. Observation with Boolean type and True value', function () {
+ describe('When the IoT Agent receives new list from a device with ListProperty type', function () {
const values = [
{
- name: 'status',
- type: 'Boolean',
- value: 'true'
+ name: 'lightValues',
+ type: 'ListProperty',
+ value: [0, 1]
}
];
@@ -174,18 +141,16 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast3.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextListProperty.json')
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the listValue of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -194,12 +159,12 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
});
});
- describe('When the IoT Agent receives new information from a device. Observation with Boolean type and False value', function () {
+ describe('When the IoT Agent receives new list from a device with ListRelationship type', function () {
const values = [
{
- name: 'status',
- type: 'Boolean',
- value: 'false'
+ name: 'placedIn',
+ type: 'ListRelationship',
+ value: ['unr:ngsi-ld:xxx', 'unr:ngsi-ld:yyyy']
}
];
@@ -208,18 +173,18 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast4.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile(
+ './test/unit/ngsi-ld/examples/contextRequests/updateContextListRelationship.json'
+ )
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the listObject of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -228,12 +193,12 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
});
});
- describe('When the IoT Agent receives new information from a device. Observation with None type', function () {
+ describe('When the IoT Agent receives new list from a device with JsonProperty type', function () {
const values = [
{
- name: 'keep_alive',
- type: 'None',
- value: 'null'
+ name: 'config',
+ type: 'JsonProperty',
+ value: { foo: 'bar' }
}
];
@@ -242,18 +207,16 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast5.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextJsonProperty.json')
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the json of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -262,12 +225,12 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
});
});
- describe('When the IoT Agent receives new information from a device. Observation with Array type', function () {
+ describe('When the IoT Agent receives new list from a device with VocabProperty type', function () {
const values = [
{
- name: 'tags',
- type: 'Array',
- value: '["iot","device"]'
+ name: 'category',
+ type: 'VocabProperty',
+ value: 'actuator'
}
];
@@ -276,18 +239,18 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast6.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile(
+ './test/unit/ngsi-ld/examples/contextRequests/updateContextVocabProperty.json'
+ )
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the vocab of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -296,12 +259,12 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
});
});
- describe('When the IoT Agent receives new information from a device. Observation with Object type', function () {
+ describe('When the IoT Agent receives new urn from a device with Relationship type', function () {
const values = [
{
- name: 'configuration',
- type: 'Object',
- value: '{"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}'
+ name: 'locatedIn',
+ type: 'Relationship',
+ value: 'urn:ngsi-ld:XXX'
}
];
@@ -310,18 +273,16 @@ describe('NGSI-v2 - JSON native types autocast test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAutocast7.json')
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextRelationship.json')
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
});
- it('should change the value of the corresponding attribute in the context broker', function (done) {
+ it('should change the vocab of the corresponding attribute in the context broker', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
diff --git a/test/unit/ngsi-ld/ngsiService/autocast-test.js b/test/unit/ngsi-ld/ngsiService/autocast-test.js
deleted file mode 100644
index 50db7f966..000000000
--- a/test/unit/ngsi-ld/ngsiService/autocast-test.js
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Jason Fox - FIWARE Foundation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- autocast: true,
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- active: [
- {
- name: 'pressure',
- type: 'Number'
- },
- {
- name: 'temperature',
- type: 'Number'
- },
- {
- name: 'id',
- type: 'String'
- },
- {
- name: 'status',
- type: 'Boolean'
- },
- {
- name: 'keep_alive',
- type: 'None'
- },
- {
- name: 'tags',
- type: 'Array'
- },
- {
- name: 'configuration',
- type: 'Object'
- },
- {
- name: 'date',
- type: 'Date'
- },
- {
- name: 'time',
- type: 'Time'
- },
- {
- name: 'dateTime',
- type: 'DateTime'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-LD - JSON native types autocast test', function () {
- beforeEach(function () {
- logger.setLevel('FATAL');
- });
-
- afterEach(function (done) {
- iotAgentLib.deactivate(done);
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Number type and Integer value', function () {
- const values = [
- {
- name: 'pressure',
- type: 'Number',
- value: '23'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast1.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Number type and Float value', function () {
- const values = [
- {
- name: 'temperature',
- type: 'Number',
- value: '14.4'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast2.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Boolean type and True value', function () {
- const values = [
- {
- name: 'status',
- type: 'Boolean',
- value: 'true'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast3.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Boolean type and False value', function () {
- const values = [
- {
- name: 'status',
- type: 'Boolean',
- value: 'false'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast4.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with None type', function () {
- const values = [
- {
- name: 'keep_alive',
- type: 'None',
- value: 'null'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast5.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Array type', function () {
- const values = [
- {
- name: 'tags',
- type: 'Array',
- value: '["iot","device"]'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast6.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Object type', function () {
- const values = [
- {
- name: 'configuration',
- type: 'Object',
- value: '{"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast7.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Time type', function () {
- const values = [
- {
- name: 'time',
- type: 'Time',
- value: '2016-04-30T14:59:46.000Z'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with DateTime type', function () {
- const values = [
- {
- name: 'dateTime',
- type: 'DateTime',
- value: '2016-04-30Z'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new information from a device. Observation with Date type', function () {
- const values = [
- {
- name: 'date',
- type: 'Date',
- value: '2016-04-30T14:59:46.000Z'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json')
- )
-
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/ngsiService/geoproperties-test.js b/test/unit/ngsi-ld/ngsiService/geoproperties-test.js
deleted file mode 100644
index 712642322..000000000
--- a/test/unit/ngsi-ld/ngsiService/geoproperties-test.js
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- autocast: true,
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- active: [
- {
- name: 'location',
- type: 'GeoProperty'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-LD - Geo-JSON types autocast test', function () {
- beforeEach(function () {
- logger.setLevel('FATAL');
- });
-
- afterEach(function (done) {
- iotAgentLib.deactivate(done);
- });
-
- describe(
- 'When the IoT Agent receives new geo-information from a device.' +
- 'Location with GeoProperty type and String value',
- function () {
- const values = [
- {
- name: 'location',
- type: 'GeoProperty',
- value: '23,12.5'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- }
- );
-
- describe('When the IoT Agent receives new geo-information from a device. Location with Point type and Array value', function () {
- const values = [
- {
- name: 'location',
- type: 'Point',
- value: [23, 12.5]
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties1.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe(
- 'When the IoT Agent receives new geo-information from a device.' +
- 'Location with LineString type and Array value',
- function () {
- const values = [
- {
- name: 'location',
- type: 'LineString',
- value: [
- [23, 12.5],
- [22, 12.5]
- ]
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- }
- );
-
- describe(
- 'When the IoT Agent receives new geo-information from a device.' +
- 'Location with LineString type and Array of Strings',
- function () {
- const values = [
- {
- name: 'location',
- type: 'LineString',
- value: ['23,12.5', '22,12.5']
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties2.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- }
- );
-
- describe('When the IoT Agent receives new geo-information from a device. Location with None type', function () {
- const values = [
- {
- name: 'location',
- type: 'None',
- value: 'null'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties3.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe(
- 'When the IoT Agent receives new geo-information from a device.' +
- 'Location with Polygon type - Array of coordinates',
- function () {
- const values = [
- {
- name: 'location',
- type: 'Polygon',
- value: [
- [23, 12.5],
- [22, 13.5],
- [22, 13.5]
- ]
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- }
- );
-
- describe(
- 'When the IoT Agent receives new geo-information from a device.' +
- 'Location with Polygon type - list of coordinates',
- function () {
- const values = [
- {
- name: 'location',
- type: 'Polygon',
- value: '23,12.5,22,13.5,22,13.5'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextGeoproperties4.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- }
- );
-
- describe('When the IoT Agent receives new geo-information from a device. Location with a missing latitude', function () {
- const values = [
- {
- name: 'location',
- type: 'Point',
- value: '23,12.5,22,13.5,22'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should throw a BadGeocoordinates Error', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.exist(error);
- done();
- });
- });
- });
-
- describe('When the IoT Agent receives new geo-information from a device. Location invalid coordinates', function () {
- const values = [
- {
- name: 'location',
- type: 'Point',
- value: '2016-04-30Z'
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should throw a BadGeocoordinates Error', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.exist(error);
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/ngsiService/languageProperties-test.js b/test/unit/ngsi-ld/ngsiService/languageProperties-test.js
deleted file mode 100644
index 62df2233e..000000000
--- a/test/unit/ngsi-ld/ngsiService/languageProperties-test.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2022 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- autocast: true,
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- active: [
- {
- name: 'name',
- type: 'LanguageProperty'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-LD - LanguageProperty test', function () {
- beforeEach(function () {
- logger.setLevel('FATAL');
- });
-
- afterEach(function (done) {
- iotAgentLib.deactivate(done);
- });
-
- describe('When the IoT Agent receives new exonym from a device name with LanguageProperty type and a JSON object', function () {
- const values = [
- {
- name: 'name',
- type: 'LanguageProperty',
- value: {
- el: 'Κωνσταντινούπολις',
- en: 'Constantinople',
- tr: 'İstanbul'
- }
- }
- ];
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/?options=update',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperties1.json'
- )
- )
- .reply(204);
-
- iotAgentLib.activate(iotAgentConfig, done);
- });
-
- it('should change the value of the corresponding attribute in the context broker', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/ngsiService/subscriptions-test.js b/test/unit/ngsi-ld/ngsiService/subscriptions-test.js
index 25c8baceb..a1eb9b5ed 100644
--- a/test/unit/ngsi-ld/ngsiService/subscriptions-test.js
+++ b/test/unit/ngsi-ld/ngsiService/subscriptions-test.js
@@ -66,16 +66,6 @@ describe('NGSI-LD - Subscription tests', function () {
iotAgentLib.activate(iotAgentConfig, function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(204);
-
- contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.post(
'/ngsi-ld/v1/subscriptions/',
@@ -103,8 +93,8 @@ describe('NGSI-LD - Subscription tests', function () {
describe('When a client invokes the subscribe() function for device', function () {
it('should send the appropriate request to the Context Broker', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -114,9 +104,9 @@ describe('NGSI-LD - Subscription tests', function () {
});
});
it('should store the subscription ID in the Device Registry', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
should.not.exist(error);
should.exist(device);
should.exist(device.subscriptions);
@@ -138,11 +128,12 @@ describe('NGSI-LD - Subscription tests', function () {
done();
});
+
it('should delete the subscription from the CB', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
contextBrokerMock.done();
done();
});
@@ -151,10 +142,10 @@ describe('NGSI-LD - Subscription tests', function () {
});
});
it('should remove the id from the subscriptions array', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
should.not.exist(error);
should.exist(device);
should.exist(device.subscriptions);
@@ -177,9 +168,9 @@ describe('NGSI-LD - Subscription tests', function () {
});
it('should delete the subscription from the CB', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.unregister(device.id, 'smartgondor', '/gardens', function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
+ iotAgentLib.unregister(device.id, null, 'smartgondor', '/gardens', function (error) {
contextBrokerMock.done();
done();
});
@@ -189,8 +180,8 @@ describe('NGSI-LD - Subscription tests', function () {
});
describe('When a new notification comes to the IoTAgent', function () {
beforeEach(function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
done();
});
});
diff --git a/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js b/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js
index feeef0a78..666798fa3 100644
--- a/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js
+++ b/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js
@@ -108,16 +108,6 @@ describe('NGSI-LD - Unsupported Endpoints', function () {
});
describe('When accessing an Unsupported Endpoint', function () {
- it('GET /entities should return a valid NSGI-LD error message', function (done) {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/ngsi-ld/v1/entities',
- method: 'GET'
- };
- request(options, function (error, response, body) {
- response.statusCode.should.equal(501);
- done();
- });
- });
it('POST /entities should return a valid NSGI-LD error message', function (done) {
const options = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/ngsi-ld/v1/entities',
diff --git a/test/unit/ngsi-ld/ngsiService/value-types-test.js b/test/unit/ngsi-ld/ngsiService/value-types-test.js
new file mode 100644
index 000000000..fe02bce6e
--- /dev/null
+++ b/test/unit/ngsi-ld/ngsiService/value-types-test.js
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, seehttp://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Developed by: Federico M. Facca - Martel Innovate
+ */
+
+/* jshint camelcase: false */
+
+const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
+const utils = require('../../../tools/utils');
+const should = require('should');
+const nock = require('nock');
+let contextBrokerMock;
+const iotAgentConfig = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026',
+ ngsiVersion: 'ld',
+ jsonLdContext: 'http://context.json-ld'
+ },
+ server: {
+ port: 4041,
+ ldSupport: {
+ dataType: 'valueType'
+ }
+ },
+ types: {
+ Light: {
+ commands: [],
+ type: 'Light',
+ lazy: [],
+ active: [
+ {
+ object_id: 'p',
+ name: 'pressure',
+ type: 'Number'
+ },
+ {
+ object_id: 'e',
+ name: 'consumption',
+ type: 'Number'
+ },
+ {
+ object_id: 'a',
+ name: 'alive',
+ type: 'Property'
+ },
+ {
+ object_id: 'u',
+ name: 'updated',
+ type: 'Boolean'
+ },
+ {
+ object_id: 'm',
+ name: 'manufacturer',
+ type: 'Object'
+ },
+ {
+ object_id: 'r',
+ name: 'revisions',
+ type: 'Array'
+ },
+ {
+ object_id: 'd',
+ name: 'date',
+ type: 'Date'
+ },
+ {
+ object_id: 't',
+ name: 'time',
+ type: 'Time'
+ },
+ {
+ object_id: 'dt',
+ name: 'datetime',
+ type: 'DateTime'
+ }
+ ]
+ }
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M'
+};
+
+describe('NGSI-LD: Value Type', function () {
+ // Case: Expression which results is sent as a new attribute
+ const values = [
+ {
+ name: 'e',
+ type: 'Number',
+ value: 1
+ },
+ {
+ name: 'a',
+ type: 'Property',
+ value: 'foo'
+ },
+ {
+ name: 'u',
+ type: 'Boolean',
+ value: false
+ },
+ {
+ name: 'm',
+ type: 'Object',
+ value: '{"hello": "world"}'
+ },
+ {
+ name: 'r',
+ type: 'Array',
+ value: '[1,2]'
+ },
+ {
+ name: 'd',
+ type: 'Date',
+ value: '2025-07-18'
+ },
+ {
+ name: 't',
+ type: 'Time',
+ value: '14:22:11Z'
+ },
+ {
+ name: 'dt',
+ type: 'Datetime',
+ value: '2025-07-18T14:22:11Z'
+ }
+ ];
+
+ describe('When valueType is set to valueType', function () {
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ iotAgentConfig.contextBroker.valueType = 'valueType';
+ iotAgentLib.activate(iotAgentConfig, function () {
+ iotAgentLib.clearAll(function () {
+ done();
+ });
+ });
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextValueType1.json')
+ )
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ it('should add valueType to the payload', function (done) {
+ iotAgentLib.update('ws1', 'Light', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+ describe('When valueType is set to @type', function () {
+ beforeEach(function (done) {
+ iotAgentConfig.server.ldSupport.dataType = '@type';
+ iotAgentLib.activate(iotAgentConfig, function () {
+ iotAgentLib.clearAll(function () {
+ done();
+ });
+ });
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/ngsi-ld/v1/entityOperations/upsert/?options=update',
+ utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/updateContextValueType2.json')
+ )
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ it('should add @type to the payload', function (done) {
+ iotAgentLib.update('ws1', 'Light', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/ngsi-ld/plugins/alias-plugin_test.js b/test/unit/ngsi-ld/plugins/alias-plugin_test.js
index bb758b044..f10ec8d08 100644
--- a/test/unit/ngsi-ld/plugins/alias-plugin_test.js
+++ b/test/unit/ngsi-ld/plugins/alias-plugin_test.js
@@ -33,7 +33,6 @@ const logger = require('logops');
const nock = require('nock');
let contextBrokerMock;
const iotAgentConfig = {
- autocast: true,
contextBroker: {
host: '192.168.1.1',
port: '1026',
@@ -154,7 +153,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -183,7 +182,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -242,7 +241,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -272,7 +271,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -302,7 +301,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -332,7 +331,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -362,7 +361,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -392,7 +391,7 @@ describe('NGSI-LD - Attribute alias plugin', function () {
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
diff --git a/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js b/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js
deleted file mode 100644
index a834d68ff..000000000
--- a/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js
+++ /dev/null
@@ -1,697 +0,0 @@
-/*
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Jason Fox - FIWARE Foundation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-
-let contextBrokerMock;
-const iotAgentConfig = {
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {},
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-LD - Bidirectional data plugin', function () {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function (done) {
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- iotAgentLib.addDeviceProvisionMiddleware(iotAgentLib.dataPlugins.bidirectionalData.deviceProvision);
- iotAgentLib.addConfigurationProvisionMiddleware(
- iotAgentLib.dataPlugins.bidirectionalData.groupProvision
- );
- iotAgentLib.addNotificationMiddleware(iotAgentLib.dataPlugins.bidirectionalData.notification);
- done();
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
-
- describe('When a new provisioning request arrives to the IoTA with bidirectionality', function () {
- beforeEach(function () {
- logger.setLevel('FATAL');
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(options, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When a device with bidirectionality subscriptions is removed', function () {
- const deleteRequest = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
- method: 'DELETE',
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .delete('/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8', '')
- .reply(204);
- });
-
- it('should remove its subscriptions from the Context Broker', function (done) {
- request(options, function (error, response, body) {
- request(deleteRequest, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
-
- describe('When a notification arrives for a bidirectional attribute', function () {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should execute the original handler', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- executedHandler.should.equal(true);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return a 200 OK', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- response.statusCode.should.equal(200);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- contextBrokerMock.done();
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
-
- describe('When a notification with metadata arrives for a bidirectional attribute', function () {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should execute the original handler', function (done) {
- function mockedHandler(device, notification, callback) {
- notification[0].name.should.equal('location');
- notification[0].value.should.equal('12.4, -9.6');
- notification[0].metadata.qos.value.should.equal(1);
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- executedHandler.should.equal(true);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return a 200 OK', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- response.statusCode.should.equal(200);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- contextBrokerMock.done();
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
-
- describe('When a sequential notification with datasetId arrives for a bidirectional attribute', function () {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should execute the original handler', function (done) {
- function mockedHandler(device, notification, callback) {
- notification[0].name.should.equal('location');
- notification[0].value.should.equal('12.4, -9.6');
- notification[0].datasetId.should.equal('urn:ngsi-ld:Property:do-this');
-
- notification[1].name.should.equal('location');
- notification[1].value.should.equal('6, 10');
- notification[1].datasetId.should.equal('urn:ngsi-ld:Property:then-do-this');
-
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- executedHandler.should.equal(true);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return a 200 OK', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- response.statusCode.should.equal(200);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- contextBrokerMock.done();
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
-
- describe('When a new Group provisioning request arrives with bidirectional attributes', function () {
- const provisionGroup = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const provisionDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(provisionGroup, function (error, response, body) {
- request(provisionDevice, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
-
- describe('When a notification arrives for a bidirectional attribute in a Configuration Group', function () {
- const provisionGroup = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const provisionDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(provisionGroup, function (error, response, body) {
- request(provisionDevice, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
- });
-});
-
-describe('NGSI-LD - Bidirectional data plugin and CB is defined using environment variables', function () {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function (done) {
- logger.setLevel('FATAL');
- process.env.IOTA_CB_HOST = 'cbhost';
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- iotAgentLib.addDeviceProvisionMiddleware(iotAgentLib.dataPlugins.bidirectionalData.deviceProvision);
- iotAgentLib.addConfigurationProvisionMiddleware(
- iotAgentLib.dataPlugins.bidirectionalData.groupProvision
- );
- iotAgentLib.addNotificationMiddleware(iotAgentLib.dataPlugins.bidirectionalData.notification);
- done();
- });
- });
- });
-
- afterEach(function (done) {
- process.env.IOTA_CB_HOST = '';
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
-
- describe('When a new provisioning request arrives to the IoTA with bidirectionality', function () {
- beforeEach(function () {
- contextBrokerMock = nock('http://cbhost:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/subscriptions/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(options, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/plugins/multientity-plugin_test.js b/test/unit/ngsi-ld/plugins/multientity-plugin_test.js
index c354bc5f1..901cab411 100644
--- a/test/unit/ngsi-ld/plugins/multientity-plugin_test.js
+++ b/test/unit/ngsi-ld/plugins/multientity-plugin_test.js
@@ -696,17 +696,16 @@ describe('NGSI-LD - Multi-entity plugin is executed before timestamp process plu
'./test/unit/ngsi-ld/examples' +
'/contextRequests/updateContextMultientityTimestampPlugin2.json'
);
-
// Note that TimeInstant fields are not included in the json used by this mock as they are dynamic
// fields. The following code just checks that TimeInstant fields are present.
- if (!body[1].humidity.observedAt) {
+ if (!body[0].humidity.observedAt) {
return false;
}
- const timeInstantAtt = body[1].humidity.observedAt;
+ const timeInstantAtt = body[0].humidity.observedAt;
if (moment(timeInstantAtt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid) {
- delete body[1].humidity.observedAt;
- delete expectedBody[1].humidity.observedAt;
+ delete body[0].humidity.observedAt;
+ delete expectedBody[0].humidity.observedAt;
return JSON.stringify(body) === JSON.stringify(expectedBody);
}
return false;
diff --git a/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js b/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js
deleted file mode 100644
index e39b06605..000000000
--- a/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Jason Fox - FIWARE Foundation
- */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-const moment = require('moment');
-let contextBrokerMock;
-const iotAgentConfig = {
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- lazy: [
- {
- name: 'temperature',
- type: 'centigrades'
- }
- ],
- active: [
- {
- name: 'pressure',
- type: 'Hgmm'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-LD - Timestamp processing plugin', function () {
- beforeEach(function (done) {
- logger.setLevel('FATAL');
-
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- done();
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
- describe('When an update comes with a timestamp through the plugin', function () {
- const values = [
- {
- name: 'state',
- type: 'Boolean',
- value: true
- },
- {
- name: 'TimeInstant',
- type: 'DateTime',
- value: '2016-05-30T16:25:22.304Z'
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/?options=update', function (body) {
- const expectedBody = utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json'
- );
-
- // Note that TimeInstant fields are not included in the json used by this mock as they are dynamic
- // fields. The following code just checks that TimeInstant fields are present.
- if (!body[0].state.observedAt) {
- return false;
- }
-
- const timeInstantAtt = body[0].state.observedAt;
- if (moment(timeInstantAtt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid) {
- delete body[0].state.observedAt;
- delete expectedBody[0].state.observedAt;
- return JSON.stringify(body) === JSON.stringify(expectedBody);
- }
- return false;
- })
- .reply(204);
- });
-
- it('should return an entity with all its timestamps expanded to have separators', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js
index 45dde09ad..f5f314613 100644
--- a/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js
+++ b/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js
@@ -96,14 +96,6 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
)
)
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createProvisionedDevice.json')
- )
- .reply(204);
});
const options = {
@@ -209,7 +201,7 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
});
});
- it('should create the initial entity in the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
response.statusCode.should.equal(201);
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
@@ -243,18 +235,10 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json')
- )
- .reply(204);
-
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
contextBrokerMock.done();
done();
@@ -286,20 +270,11 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createTimeinstantDevice.json')
- )
- .reply(204);
-
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -329,19 +304,11 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile('./test/unit/ngsi-ld/examples/contextRequests/createAutoprovisionDevice.json')
- )
- .reply(204);
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -371,22 +338,12 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createTimeInstantMinimumDevice.json'
- )
- )
- .reply(204);
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -405,22 +362,11 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -463,22 +409,11 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createGeopointProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
- it('should send the appropriate initial values to the Context Broker', function (done) {
+ it('should not send any initial values to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -497,22 +432,11 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createDatetimeProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
- it('should send the appropriate initial values to the Context Broker', function (done) {
+ it('should not send any initial values to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -590,46 +514,6 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
});
});
- describe('When the Context Broker returns an unrecognized status code provisioning an entity', function () {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function (done) {
- nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(207);
-
- done();
- });
-
- it('should return an error message in the response body', function (done) {
- request(options, function (error, response, body) {
- should.not.exist(error);
- response.body.name.should.equal('ENTITY_GENERIC_ERROR');
- response.body.message.should.equal(
- 'Error accesing entity data for device: MicroLight1 of type: MicroLights'
- );
- response.statusCode.should.equal(200);
-
- done();
- });
- });
- });
-
describe('When the Context Broker returns a 200 status code (NGSI-LD v1.2.1) provisioning an entity', function () {
const options = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
@@ -737,7 +621,7 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
it('should return a valid return code', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
- response.statusCode.should.equal(500);
+ response.statusCode.should.equal(201);
done();
});
@@ -761,15 +645,7 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.post('/ngsi-ld/v1/csourceRegistrations/')
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .replyWithError({ message: 'Description of the error', code: 123456789 });
+ .reply(400, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
done();
});
@@ -777,8 +653,7 @@ describe('NGSI-LD - Device provisioning API: Provision devices', function () {
it('should return a valid return code (three character number)', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
- response.statusCode.should.equal(500);
-
+ response.statusCode.should.equal(201);
done();
});
});
diff --git a/test/unit/ngsi-ld/provisioning/device-registration_test.js b/test/unit/ngsi-ld/provisioning/device-registration_test.js
index d93e78517..3b9bd6e04 100644
--- a/test/unit/ngsi-ld/provisioning/device-registration_test.js
+++ b/test/unit/ngsi-ld/provisioning/device-registration_test.js
@@ -109,19 +109,10 @@ describe('NGSI-LD - IoT Agent Device Registration', function () {
describe('When a new device is connected to the IoT Agent', function () {
beforeEach(function (done) {
nock.cleanAll();
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
const nockBody = utils.readExampleFile(
'./test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgent1.json'
);
- contextBrokerMock
+ contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.post('/ngsi-ld/v1/csourceRegistrations/', nockBody)
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
@@ -222,7 +213,7 @@ describe('NGSI-LD - IoT Agent Device Registration', function () {
it("should return all the device's information", function (done) {
iotAgentLib.register(device1, function (error) {
- iotAgentLib.getDevice('light1', 'smartgondor', 'gardens', function (error, data) {
+ iotAgentLib.getDevice('light1', null, 'smartgondor', 'gardens', function (error, data) {
should.not.exist(error);
should.exist(data);
data.type.should.equal('Light');
@@ -252,7 +243,7 @@ describe('NGSI-LD - IoT Agent Device Registration', function () {
it('should return a ENTITY_NOT_FOUND error', function (done) {
iotAgentLib.register(device1, function (error) {
- iotAgentLib.getDevice('lightUnexistent', 'smartgondor', 'gardens', function (error, data) {
+ iotAgentLib.getDevice('lightUnexistent', null, 'smartgondor', 'gardens', function (error, data) {
should.exist(error);
should.not.exist(data);
error.code.should.equal(404);
@@ -299,14 +290,14 @@ describe('NGSI-LD - IoT Agent Device Registration', function () {
);
});
});
-
- it('should update the devices information in Context Broker', function (done) {
- iotAgentLib.unregister(device1.id, 'smartgondor', 'gardens', function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
+ // FIXME: disabled test by #1421
+ // it('should update the devices information in Context Broker', function (done) {
+ // iotAgentLib.unregister(device1.id, null, 'smartgondor', 'gardens', function (error) {
+ // should.not.exist(error);
+ // contextBrokerMock.done();
+ // done();
+ // });
+ // });
});
describe('When the Context Broker returns an error while unregistering a device', function () {
@@ -346,7 +337,7 @@ describe('NGSI-LD - IoT Agent Device Registration', function () {
it('should not remove the device from the internal registry');
it('should return a UNREGISTRATION_ERROR error to the caller', function (done) {
- iotAgentLib.unregister(device1.id, 'smartgondor', 'gardens', function (error) {
+ iotAgentLib.unregister(device1.id, null, 'smartgondor', 'gardens', function (error) {
should.exist(error);
should.exist(error.name);
error.name.should.equal('UNREGISTRATION_ERROR');
diff --git a/test/unit/ngsi-ld/provisioning/device-update-registration_test.js b/test/unit/ngsi-ld/provisioning/device-update-registration_test.js
index ff2ede4bf..3c39b33fa 100644
--- a/test/unit/ngsi-ld/provisioning/device-update-registration_test.js
+++ b/test/unit/ngsi-ld/provisioning/device-update-registration_test.js
@@ -148,14 +148,6 @@ describe('NGSI-LD - IoT Agent Device Update Registration', function () {
.post('/ngsi-ld/v1/csourceRegistrations/')
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
iotAgentLib.activate(iotAgentConfig, function (error) {
iotAgentLib.register(device1, function (error) {
done();
@@ -194,15 +186,15 @@ describe('NGSI-LD - IoT Agent Device Update Registration', function () {
});
it('should register as ContextProvider of its lazy attributes', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
});
});
it('should store the new values in the registry', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error, data) {
- iotAgentLib.getDevice(deviceUpdated.id, 'smartgondor', 'gardens', function (error, deviceResult) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error, data) {
+ iotAgentLib.getDevice(deviceUpdated.id, null, 'smartgondor', 'gardens', function (error, deviceResult) {
should.not.exist(error);
should.exist(deviceResult);
deviceResult.internalId.should.equal(deviceUpdated.internalId);
@@ -235,18 +227,19 @@ describe('NGSI-LD - IoT Agent Device Update Registration', function () {
)
.reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
});
-
- it('should register as ContextProvider of its commands and create the additional attributes', function (done) {
- iotAgentLib.updateRegister(deviceCommandUpdated, false, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
+ // FIXME: disabled test by #1421
+ // it('should register as ContextProvider of its commands and create the additional attributes', function (done) {
+ // iotAgentLib.updateRegister(deviceCommandUpdated, false, function (error) {
+ // should.not.exist(error);
+ // contextBrokerMock.done();
+ // done();
+ // });
+ // });
it('should store the new values in the registry', function (done) {
- iotAgentLib.updateRegister(deviceCommandUpdated, false, function (error, data) {
+ iotAgentLib.updateRegister(deviceCommandUpdated, device1, false, function (error, data) {
iotAgentLib.getDevice(
deviceCommandUpdated.id,
+ null,
'smartgondor',
'gardens',
function (error, deviceResult) {
@@ -264,7 +257,7 @@ describe('NGSI-LD - IoT Agent Device Update Registration', function () {
describe('When a update action is executed in a non registered device', function () {
it('should return a DEVICE_NOT_FOUND error', function (done) {
- iotAgentLib.updateRegister(unknownDevice, false, function (error) {
+ iotAgentLib.updateRegister(unknownDevice, device1, false, function (error) {
should.exist(error);
error.name.should.equal('DEVICE_NOT_FOUND');
done();
@@ -280,7 +273,7 @@ describe('NGSI-LD - IoT Agent Device Update Registration', function () {
});
it('should return a REGISTRATION_ERROR error in the update action', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error) {
should.exist(error);
//error.name.should.equal('UNREGISTRATION_ERROR');
done();
diff --git a/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js b/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js
index 3a12084bb..fbe307897 100644
--- a/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js
+++ b/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js
@@ -184,11 +184,9 @@ describe('NGSI-LD - Device provisioning API: List provisioned devices', function
should.exist(body.devices[2].attributes[0].entity_name);
should.exist(body.devices[2].attributes[0].entity_type);
should.exist(body.devices[2].attributes[1].expression);
- should.exist(body.devices[2].attributes[2].reverse);
body.devices[2].attributes[0].entity_name.should.equal('Higro2000');
body.devices[2].attributes[0].entity_type.should.equal('Higrometer');
body.devices[2].attributes[1].expression.should.equal('${@humidity * 20}');
- body.devices[2].attributes[2].reverse.length.should.equal(2);
done();
});
});
@@ -245,11 +243,9 @@ describe('NGSI-LD - Device provisioning API: List provisioned devices', function
should.exist(body.attributes[0].entity_name);
should.exist(body.attributes[0].entity_type);
should.exist(body.attributes[1].expression);
- should.exist(body.attributes[2].reverse);
body.attributes[0].entity_name.should.equal('Higro2000');
body.attributes[0].entity_type.should.equal('Higrometer');
body.attributes[1].expression.should.equal('${@humidity * 20}');
- body.attributes[2].reverse.length.should.equal(2);
done();
});
});
diff --git a/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js b/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js
deleted file mode 100644
index 0dfc0c4e1..000000000
--- a/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Jason Fox - FIWARE Foundation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-
-const should = require('should');
-const nock = require('nock');
-let contextBrokerMock;
-
-const iotAgentConfig = {
- logLevel: 'FATAL',
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'ld',
- jsonLdContext: 'http://context.json-ld'
- },
- server: {
- port: 4041,
- host: 'localhost',
- baseRoot: '/'
- },
- types: {},
- service: 'smartgondor',
- singleConfigurationMode: true,
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-const groupCreation = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionFullGroup.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
-};
-const deviceCreation = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
-};
-
-describe('NGSI-LD - Provisioning API: Single service mode', function () {
- beforeEach(function (done) {
- nock.cleanAll();
-
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(done);
- });
- });
-
- afterEach(function (done) {
- nock.cleanAll();
- iotAgentLib.setProvisioningHandler();
- iotAgentLib.deactivate(done);
- });
-
- describe('When a new configuration arrives to an already configured subservice', function () {
- const groupCreationDuplicated = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionDuplicateGroup.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- request(groupCreation, done);
- });
-
- it('should raise a DUPLICATE_GROUP error', function (done) {
- request(groupCreationDuplicated, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(409);
- should.exist(body.name);
- body.name.should.equal('DUPLICATE_GROUP');
- done();
- });
- });
- });
- describe('When a device is provisioned with an ID that already exists in the configuration', function () {
- const deviceCreationDuplicated = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionDuplicatedDev.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/csourceRegistrations/')
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
- request(groupCreation, function (error) {
- request(deviceCreation, function (error, response, body) {
- done();
- });
- });
- });
-
- it('should raise a DUPLICATE_DEVICE_ID error', function (done) {
- request(deviceCreationDuplicated, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(409);
- should.exist(body.name);
- body.name.should.equal('DUPLICATE_DEVICE_ID');
- done();
- });
- });
- });
- describe('When a device is provisioned with an ID that exists globally but not in the configuration', function () {
- const alternativeDeviceCreation = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
- headers: {
- 'fiware-service': 'AlternateService',
- 'fiware-servicepath': '/testingPath'
- }
- };
- const alternativeGroupCreation = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionFullGroup.json'),
- headers: {
- 'fiware-service': 'AlternateService',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/csourceRegistrations/')
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'AlternateService')
- .post('/ngsi-ld/v1/csourceRegistrations/')
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'AlternateService')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
- request(groupCreation, function (error) {
- request(deviceCreation, function (error, response, body) {
- request(alternativeGroupCreation, function (error, response, body) {
- done();
- });
- });
- });
- });
-
- it('should return a 201 OK', function (done) {
- request(alternativeDeviceCreation, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(201);
- done();
- });
- });
- });
- describe('When a device is provisioned without a type and with a default configuration type', function () {
- const getDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
- method: 'GET',
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
- let oldType;
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/csourceRegistrations/')
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
- oldType = deviceCreation.json.devices[0].entity_type;
- delete deviceCreation.json.devices[0].entity_type;
- request(groupCreation, done);
- });
-
- afterEach(function () {
- deviceCreation.json.devices[0].entity_type = oldType;
- });
-
- it('should be provisioned with the default type', function (done) {
- request(deviceCreation, function (error, response, body) {
- request(getDevice, function (error, response, body) {
- body.entity_type.should.equal('SensorMachine');
- done();
- });
- });
- });
- });
- describe('When a device is provisioned for a configuration', function () {
- beforeEach(function (done) {
- nock.cleanAll();
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .post(
- '/ngsi-ld/v1/csourceRegistrations/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples' +
- '/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json'
- )
- )
- .reply(201, null, { Location: '/ngsi-ld/v1/csourceRegistrations/6319a7f5254b05844116584d' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .post(
- '/ngsi-ld/v1/entityOperations/upsert/',
- utils.readExampleFile(
- './test/unit/ngsi-ld/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json'
- )
- )
- .reply(204);
-
- request(groupCreation, done);
- });
-
- it('should not raise any error', function (done) {
- request(deviceCreation, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(201);
- done();
- });
- });
-
- it('should send the mixed data to the Context Broker', function (done) {
- request(deviceCreation, function (error, response, body) {
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js b/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js
index 17d739af9..678e3e079 100644
--- a/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js
+++ b/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js
@@ -322,11 +322,6 @@ describe('NGSI-LD - Device provisioning API: Update provisioned devices', functi
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .post('/ngsi-ld/v1/entityOperations/upsert/')
- .reply(204);
-
- contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.post(
'/ngsi-ld/v1/entityOperations/upsert/?options=update',
@@ -358,13 +353,6 @@ describe('NGSI-LD - Device provisioning API: Update provisioned devices', functi
});
});
});
- it('should create the initial values for the attributes in the Context Broker', function (done) {
- request(optionsUpdate, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
});
describe('When a device is updated to add static attributes', function () {
diff --git a/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js b/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js
index d8a20916c..a2fedd04d 100644
--- a/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js
+++ b/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js
@@ -55,7 +55,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ useCBflowControl: true
};
const mongo = require('mongodb').MongoClient;
const mongoUtils = require('../../mongodb/mongoDBUtils');
@@ -68,6 +69,7 @@ const optionsCreationDefault = {
apikey: 'default-test',
entity_type: 'Device',
resource: '/iot/default',
+ useCBflowControl: true,
attributes: [
{
object_id: 's',
@@ -93,6 +95,7 @@ const optionsCreationV2 = {
ngsiVersion: 'v2',
entity_type: 'Device',
resource: '/iot/v2',
+ useCBflowControl: true,
attributes: [
{
object_id: 's',
@@ -189,8 +192,7 @@ describe('Mixed Mode: ngsiVersion test', function () {
nock.cleanAll();
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Device' })
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
request(optionsCreationDefault, function (error, response, body) {
@@ -211,8 +213,7 @@ describe('Mixed Mode: ngsiVersion test', function () {
nock.cleanAll();
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Device' })
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
request(optionsCreationV2, function (error, response, body) {
@@ -248,39 +249,37 @@ describe('Mixed Mode: ngsiVersion test', function () {
});
});
- describe('When a new NGSI-LD device group is provisioned and overridden', function () {
- beforeEach(function (done) {
- nock.cleanAll();
+ // FIXME: ngisld may use also upsert when update entities in appendMode true (default behaviour)
+ // describe('When a new NGSI-LD device group is provisioned and overridden', function () {
+ // beforeEach(function (done) {
+ // nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
+ // contextBrokerMock = nock('http://192.168.1.1:1026')
+ // .matchHeader('fiware-service', 'smartgondor')
+ // .matchHeader('fiware-servicepath', 'gardens')
+ // .post('/v2/entities?options=upsert,flowControl')
+ // .reply(204);
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .post('/v2/entities/light2/attrs')
- .query({ type: 'Device' })
- .reply(204);
- request(optionsCreationLD, function (error, response, body) {
- request(deviceCreationV2, function (error, response, body) {
- done();
- });
- });
- });
- it('should operate using NGSI-v2', function (done) {
- iotAgentLib.update(
- 'light2',
- 'Device',
- 'v2-test',
- values,
- { ngsiVersion: 'v2', type: 'Device' },
- function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- }
- );
- });
- });
+ // contextBrokerMock = nock('http://192.168.1.1:1026').post('/v2/entities?options=upsert,flowControl').reply(204);
+ // request(optionsCreationLD, function (error, response, body) {
+ // request(deviceCreationV2, function (error, response, body) {
+ // done();
+ // });
+ // });
+ // });
+ // it('should operate using NGSI-v2', function (done) {
+ // iotAgentLib.update(
+ // 'light2',
+ // 'Device',
+ // 'v2-test',
+ // values,
+ // { ngsiVersion: 'v2', type: 'Device' },
+ // function (error) {
+ // should.not.exist(error);
+ // contextBrokerMock.done();
+ // done();
+ // }
+ // );
+ // });
+ // });
});
diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json
new file mode 100644
index 000000000..184d54c88
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json
@@ -0,0 +1,24 @@
+{
+ "description": "Managed by IOTA: Robot:r2d2 Robot position",
+ "subject": {
+ "entities":[
+ {
+ "id":"Robot:r2d2","type":"Robot"
+ }
+ ],
+ "condition": {
+ "attrs":["position"]
+ }
+ },
+ "notification":{
+ "http":{
+ "url":"http://smartgondor.com/notify"
+ },
+ "attrs":["position"],
+ "attrsFormat":"normalized",
+ "onlyChangedAttrs":true
+ }
+}
+
+
+
diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json
new file mode 100644
index 000000000..05c1a53d5
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json
@@ -0,0 +1,24 @@
+{
+ "description": "Managed by IOTA: RobotT:r2d3 RobotT position",
+ "subject": {
+ "entities":[
+ {
+ "id":"RobotT:r2d3","type":"RobotT"
+ }
+ ],
+ "condition": {
+ "attrs":["position"]
+ }
+ },
+ "notification":{
+ "http":{
+ "url":"http://smartgondor.com/notify"
+ },
+ "attrs":["position"],
+ "attrsFormat":"normalized",
+ "onlyChangedAttrs":true
+ }
+}
+
+
+
diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json
new file mode 100644
index 000000000..2982db60b
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json
@@ -0,0 +1,24 @@
+{
+ "description": "Managed by IOTA: RobotT:r2d4 RobotT position",
+ "subject": {
+ "entities":[
+ {
+ "id":"RobotT:r2d4","type":"RobotT"
+ }
+ ],
+ "condition": {
+ "attrs":["position"]
+ }
+ },
+ "notification":{
+ "http":{
+ "url":"http://smartgondor.com/notify"
+ },
+ "attrs":["position"],
+ "attrsFormat":"normalized",
+ "onlyChangedAttrs":true
+ }
+}
+
+
+
diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json
new file mode 100644
index 000000000..d122a489a
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json
@@ -0,0 +1,24 @@
+{
+ "description": "Managed by IOTA: RobotT:r2d3 RobotT reset",
+ "subject": {
+ "entities":[
+ {
+ "id":"RobotT:r2d3","type":"RobotT"
+ }
+ ],
+ "condition": {
+ "attrs":["reset"]
+ }
+ },
+ "notification":{
+ "http":{
+ "url":"http://smartgondor.com/notify"
+ },
+ "attrs":["reset"],
+ "attrsFormat":"normalized",
+ "onlyChangedAttrs":true
+ }
+}
+
+
+
diff --git a/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json b/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json
deleted file mode 100644
index d29d80454..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "id": "TheFirstLight",
- "type": "TheLightType",
- "location": {
- "type": "geo:point",
- "value": "0, 0"
- }
-}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext.json b/test/unit/ngsiv2/examples/contextRequests/updateContext.json
index fa230afa5..8e1ab4ab2 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContext.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext.json
@@ -1,4 +1,6 @@
{
+ "id": "light1",
+ "type": "Light",
"state":{
"value": true,
"type": "boolean"
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext1.json b/test/unit/ngsiv2/examples/contextRequests/updateContext1.json
index 701e4e841..d85d55ca0 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContext1.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext1.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"type": "Boolean",
"value": "true"
@@ -8,4 +10,4 @@
"type": "Percentage",
"value": "87"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json b/test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json
index 7c8ab3fa2..d6ba39e4b 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json
@@ -1,4 +1,6 @@
{
+ "id":"machine1",
+ "type":"SensorMachine",
"status": {
"value": "STARTING",
"type": "String"
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext4.json b/test/unit/ngsiv2/examples/contextRequests/updateContext4.json
index a50b55b9c..760d37dc8 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContext4.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext4.json
@@ -1,4 +1,6 @@
{
+ "id":"Light1",
+ "type":"SensorMachine",
"status": {
"type": "String",
"value": "STARTING"
@@ -7,4 +9,5 @@
"type": "Address",
"value": "127.0.0.1"
}
-}
\ No newline at end of file
+}
+
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext5.json b/test/unit/ngsiv2/examples/contextRequests/updateContext5.json
new file mode 100644
index 000000000..c1e589f95
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext5.json
@@ -0,0 +1,12 @@
+{
+ "id":"humSensor",
+ "type":"Humidity",
+ "state":{
+ "value": true,
+ "type": "boolean"
+ },
+ "dimming":{
+ "value": 87,
+ "type": "number"
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContext6.json b/test/unit/ngsiv2/examples/contextRequests/updateContext6.json
new file mode 100644
index 000000000..8e1ab4ab2
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContext6.json
@@ -0,0 +1,12 @@
+{
+ "id": "light1",
+ "type": "Light",
+ "state":{
+ "value": true,
+ "type": "boolean"
+ },
+ "dimming":{
+ "value": 87,
+ "type": "number"
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json
index b6a4fa4bb..b35c1553f 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"temperature": {
"type":"Number",
"value":52,
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json
index ef3e33c76..05142c867 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"luminance": {
"type": "Number",
"value": 9,
@@ -7,4 +9,4 @@
"value":"CAL"
}
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json
index 31ddbade9..c9b9f886c 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"unix_timestamp": {
"type": "Number",
"value": 99823423
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json
index 5bbdf2ec8..11a013874 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"active_power": {
"type": "Number",
"value": 0.45
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json
index d2e0f4906..146c09b98 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"status": {
"type": "Boolean",
"value": false
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json
index 6932986b4..6995b0a56 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"keep_alive": {
"type": "None",
"value": null
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json
index 0a7c798b1..e1ed8bdee 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"tags": {
"type": "Array",
"value": ["iot","device"]
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json
index cd06afa38..790ec6e90 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json
@@ -1,6 +1,8 @@
{
- "configuration": {
- "type": "Object",
- "value": {"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}
- }
-}
\ No newline at end of file
+ "id":"light1",
+ "type":"Light",
+ "configuration": {
+ "type": "Object",
+ "value": {"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json
index c039e86dc..9a1741295 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json
@@ -1,6 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"configuration": {
"type": "Object",
"value": "string_value"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast1.json
deleted file mode 100644
index e1c25e49e..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast1.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "pressure":{
- "value": 23,
- "type": "Number"
- }
-}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast2.json
deleted file mode 100644
index 81c76a634..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast2.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "temperature":{
- "value": 14.4,
- "type": "Number"
- }
-}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast3.json
deleted file mode 100644
index b691bfe5a..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast3.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "status": {
- "type": "Boolean",
- "value": true
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast4.json
deleted file mode 100644
index d2e0f4906..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast4.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "status": {
- "type": "Boolean",
- "value": false
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast5.json
deleted file mode 100644
index 6932986b4..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast5.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "keep_alive": {
- "type": "None",
- "value": null
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast6.json
deleted file mode 100644
index 0a7c798b1..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast6.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "tags": {
- "type": "Array",
- "value": ["iot","device"]
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast7.json
deleted file mode 100644
index cd06afa38..000000000
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast7.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "configuration": {
- "type": "Object",
- "value": {"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json
index 052db683e..ad068f9fe 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json
@@ -1,4 +1,6 @@
{
+ "id":"r2d2",
+ "type":"Robot",
"position_status": {
"value": "ERROR",
"type": "commandStatus"
@@ -7,4 +9,4 @@
"value": "Stalled",
"type": "commandResult"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json
index 9aa4f0a4c..ed583e2d6 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json
@@ -1,4 +1,6 @@
{
+ "id":"Robot:r2d2",
+ "type":"Robot",
"position_status": {
"value": "ERROR",
"type": "commandStatus"
@@ -7,4 +9,4 @@
"value": "EXPIRED",
"type": "commandResult"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json
index 02deadef8..257d4df7a 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json
@@ -1,4 +1,6 @@
{
+ "id":"r2d2",
+ "type":"Robot",
"position_status": {
"value": "FINISHED",
"type": "commandStatus"
@@ -7,4 +9,4 @@
"value": "[72, 368, 1]",
"type": "commandResult"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json
index 5b197f013..0d140f6ac 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json
@@ -1,4 +1,6 @@
{
+ "id":"Robot:r2d2",
+ "type":"Robot",
"position_status": {
"type": "commandStatus",
"value": "PENDING"
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json
index f32be9ec6..11578e0ab 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json
@@ -1,4 +1,6 @@
{
+ "id":"RobotExp:r2d4",
+ "type":"RobotExp",
"positionExp_status": {
"type": "commandStatus",
"value": "PENDING"
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json
index 8d82c08bc..fc1187471 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"type": "Boolean",
"value": "true"
@@ -7,4 +9,4 @@
"type": "DateTime",
"value": "+002007-11-03T13:18:05"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json
index 6251c895e..8730b63d3 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"type": "Boolean",
"value": true,
@@ -13,4 +15,4 @@
"type": "DateTime",
"value": "+002007-11-03T13:18:05"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json
index 865129424..3a30a8908 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json
@@ -1,22 +1,12 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"pressure": {
"value": 1040,
"type": "Number"
},
- "consumption":{
- "type":"Number",
- "value":null
- },
"weather":{
"type":"Summary",
"value":"Humidity NaN and pressure 1040"
- },
- "alive":{
- "type":"None",
- "value":null
- },
- "updated":{
- "type":"Boolean",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json
index dcea170b0..6ead39a0a 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"consumption": {
"value": 52,
"type": "Number"
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json
index 0365aeddd..637939863 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json
@@ -1,6 +1,4 @@
{
- "consumption_x": {
- "type": "Number",
- "value": null
- }
+ "id":"light1",
+ "type":"Light"
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json
index faca3937e..4bed0e0db 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"pressure": {
"type": "Number",
"value": 10
@@ -7,4 +9,4 @@
"type": "Number",
"value": 200
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json
index 79aab5dfe..c1fcd9063 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json
@@ -1,4 +1,6 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"pressure":{
"type": "Number",
"value": 1040
@@ -10,17 +12,5 @@
"weather":{
"type": "Summary",
"value": "Humidity 6 and pressure 1040"
- },
- "consumption":{
- "type":"Number",
- "value":null
- },
- "alive":{
- "type":"None",
- "value":null
- },
- "updated":{
- "type":"Boolean",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json
index a0da45ed6..fdef65449 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json
@@ -1,22 +1,12 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"pressure": {
"type": "Number",
"value": 1040
},
- "consumption": {
- "type": "Number",
- "value": null
- },
"weather": {
"type": "Summary",
"value": "Humidity NaN and pressure 1040"
- },
- "alive": {
- "type": "None",
- "value": null
- },
- "updated": {
- "type": "Boolean",
- "value": null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json
new file mode 100644
index 000000000..57b07e067
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json
@@ -0,0 +1,8 @@
+{
+ "id": "1234",
+ "type": "WeatherStation",
+ "pressure": {
+ "value": 1040,
+ "type": "Number"
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json
index c322c2354..285d1891b 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"consumption": {
"type": "Number",
"value": 0.44
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json
index 297e11be3..d94537daa 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json
@@ -1,6 +1,8 @@
{
+ "id": "light1",
+ "type": "Light",
"pressure": {
"type": "Number",
- "value": 1040
+ "value": null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json
index 31ac46d87..8a346dcc9 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json
@@ -1,4 +1,6 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"falsy": {
"type": "Boolean",
"value": false
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json
index e308414aa..48570ed81 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json
@@ -1,4 +1,6 @@
{
+ "id":"gps1",
+ "type":"GPS",
"location": {
"type": "geo:json",
"value": {
@@ -7,12 +9,6 @@
52
],
"type": "Point"
- },
- "metadata": {
- "TimeInstant": {
- "type": "DateTime",
- "value": "1970-01-01T00:00:00.001Z"
- }
}
},
"TimeInstant": {
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json
index 6cdedb35a..c3053895d 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json
@@ -1,4 +1,6 @@
{
+ "id":"gps1",
+ "type":"GPS",
"location": {
"type": "geo:json",
"value": {
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json
index d60608da5..1dfb9f6a0 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json
@@ -1,4 +1,14 @@
{
+ "id":"gps1",
+ "type":"GPS",
+ "lat": {
+ "type": "Number",
+ "value": 52
+ },
+ "lon": {
+ "type": "Number",
+ "value": 13
+ },
"location": {
"type": "geo:json",
"value": {
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json
new file mode 100644
index 000000000..c49081d54
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json
@@ -0,0 +1,14 @@
+{
+ "id":"gps1",
+ "type":"GPS",
+ "location": {
+ "type": "geo:json",
+ "value": {
+ "coordinates": [
+ 13,
+ 52
+ ],
+ "type": "Point"
+ }
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json
index 6a5a5a3a8..67f720cfb 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json
@@ -1,8 +1,6 @@
{
- "TimeInstant": {
- "type": "DateTime",
- "value": "2015-08-05T07:35:01.468+00:00"
- },
+ "id":"gps1",
+ "type":"GPS",
"location": {
"value": {
"coordinates": [
@@ -11,12 +9,6 @@
],
"type": "Point"
},
- "type": "geo:json",
- "metadata": {
- "TimeInstant": {
- "type": "DateTime",
- "value": "2015-08-05T07:35:01.468+00:00"
- }
- }
+ "type": "geo:json"
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json
index 1647948a9..21240ce92 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json
@@ -1,4 +1,5 @@
{
+ "id":"gps1","type":"GPS",
"mylocation": {
"type": "geo:json",
"value": {
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json
new file mode 100644
index 000000000..2d868a46d
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json
@@ -0,0 +1,13 @@
+{
+ "id":"gps1","type":"GPS",
+ "mylocation": {
+ "type": "geo:json",
+ "value": {
+ "coordinates": [
+ 13,
+ 52
+ ],
+ "type": "Point"
+ }
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json
new file mode 100644
index 000000000..1f71a2f54
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json
@@ -0,0 +1,8 @@
+{
+ "id": "gps1",
+ "type": "GPS",
+ "color": {
+ "value": "blue",
+ "type": "string"
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json
index 21e7862d7..34738ad5a 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json
@@ -1,4 +1,6 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"pressure25":{
"type": "Number",
"value": 52
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json
index 60550da08..20751ec73 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json
@@ -1,8 +1,5 @@
{
- "TimeInstant": {
- "type": "DateTime",
- "value": "2015-08-05T07:35:01.468+00:00"
- },
+ "id":"gps1","type":"GPS",
"location": {
"value": {
"coordinates": [
@@ -11,22 +8,6 @@
],
"type": "Point"
},
- "type": "geo:json",
- "metadata": {
- "TimeInstant": {
- "type": "DateTime",
- "value": "2015-08-05T07:35:01.468+00:00"
- }
- }
- },
- "temperature": {
- "value": null,
- "type": "Number",
- "metadata": {
- "TimeInstant": {
- "type": "DateTime",
- "value": "2015-08-05T07:35:01.468+00:00"
- }
- }
+ "type": "geo:json"
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json
index c70cb34c4..36494f1ad 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"alive": {
"type": "None",
"value": null
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json
index 2030b636c..b796c07bb 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"manufacturer": {
"type": "Object",
"value": { "name":"Manufacturer1", "VAT":"U12345678" }
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json
index 4ea03e491..0e231b098 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"revisions": {
"type": "Array",
"value": [ "v0.1", "v0.2", "v0.3" ]
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json
index b76cd53cd..e5a2432c7 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json
@@ -1,22 +1,12 @@
{
+ "id":"ws1",
+ "type":"WeatherStation",
"consumption": {
"type": "Number",
"value": 8.8
},
- "pressure":{
- "type":"Number",
- "value":null
- },
"weather":{
"type":"Summary",
"value":"Humidity NaN and pressure NaN"
- },
- "alive":{
- "type":"None",
- "value":null
- },
- "updated":{
- "type":"Boolean",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json
index d71cbb1e4..8130f6d43 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json
@@ -1,10 +1,8 @@
{
+ "id":"light1",
+ "type":"Light",
"updated": {
"value": true,
"type": "Boolean"
- },
- "consumption_x":{
- "type":"Number",
- "value":null
}
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionSkip.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionSkip.json
new file mode 100644
index 000000000..bd69dc278
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionSkip.json
@@ -0,0 +1,12 @@
+{
+ "id":"skip1",
+ "type":"skipvalue",
+ "condition": {
+ "value": 33,
+ "type": "Number"
+ },
+ "neverSkip": {
+ "value": false,
+ "type": "Number"
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json
new file mode 100644
index 000000000..c5d1d52b4
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json
@@ -0,0 +1,37 @@
+{
+ "actionType": "append",
+ "entities": [
+ {
+ "id": "ws9",
+ "type": "WeatherStation",
+ "st1": {
+ "value": 1,
+ "type": "Number"
+ },
+ "st2": {
+ "value": 2,
+ "type": "Number"
+ },
+ "vol": {
+ "type": "Number",
+ "value": 0
+ }
+ },
+ {
+ "vol": {
+ "type": "Number",
+ "value": 100
+ },
+ "type": "WeatherStation",
+ "id": "WeatherStation1"
+ },
+ {
+ "vol": {
+ "type": "Number",
+ "value": 200
+ },
+ "type": "WeatherStation",
+ "id": "WeatherStation2"
+ }
+ ]
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json
index 97a44f128..293022ef1 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json
@@ -1,10 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "ws11",
- "type": "WrongStation"
- },
{
"id": "WrongStation1",
"type": "WrongStation",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json
index 8eabddc23..960d7b7a0 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json
@@ -1,10 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "gps1",
- "type": "GPS"
- },
{
"explicit": {
"type": "number",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json
index 9432d0a64..5bf8c40c1 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json
@@ -1,10 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "gps1",
- "type": "GPS"
- },
{
"attr1": {
"type": "number",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json
index 7b1a4edf3..0892e5049 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json
@@ -12,10 +12,10 @@
"type": "number",
"value": 13
},
- "nonexpectedAtt": {
- "type": "number",
- "value": null
- }
+ "alsoexpectedAtt": {
+ "value": null,
+ "type": "number"
+ }
},
{
"explicit": {
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json
index a4ab4da8d..c4beec37a 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json
@@ -1,9 +1,6 @@
{
"actionType": "append",
"entities": [
- { "id": "ws5",
- "type": "WeatherStation"
- },
{
"id": "Higro2000",
"type": "Higrometer",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json
index 6b1be25ef..a483a8562 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json
@@ -1,17 +1,7 @@
{
"actionType": "append",
"entities": [
- { "id": "ws6",
- "type": "WeatherStation"
- },
- {
- "id": "Higro2000",
- "type": "Higrometer",
- "pressure": {
- "type": "Hgmm",
- "value": "16"
- }
- },
+
{
"id": "Higro2002",
"type": "Higrometer",
@@ -19,6 +9,14 @@
"type": "Hgmm",
"value": "17"
}
- }
+ },
+ {
+ "id": "Higro2000",
+ "type": "Higrometer",
+ "pressure": {
+ "type": "Hgmm",
+ "value": "16"
+ }
+ }
]
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json
index b853d3bc7..e6e84e1b4 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json
@@ -1,10 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "Sensor",
- "type": "Sensor"
- },
{
"vol": {
"type": "number",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json
index eef32c3ea..2126c890e 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json
@@ -1,10 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "Sensor",
- "type": "Sensor"
- },
{
"vol": {
"type": "number",
@@ -17,7 +13,7 @@
"vol": {
"type": "number",
"value": "39"
- },
+ },
"type": "WM",
"id": "SO2"
},
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json
index 2c54d0598..48ff6bd5c 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json
@@ -1,18 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "ws7",
- "type": "WeatherStation"
- },
- {
- "pressure": {
- "type": "Hgmm",
- "value": "16"
- },
- "type": "Higrometer",
- "id": "Higro2000"
- },
{
"pressure": {
"type": "Hgmm",
@@ -26,6 +14,14 @@
},
"type": "Higrometer",
"id": "Higro2002"
+ },
+ {
+ "pressure": {
+ "type": "Hgmm",
+ "value": "16"
+ },
+ "type": "Higrometer",
+ "id": "Higro2000"
}
]
}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
index ea03aaf23..8a5a11ba0 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json
@@ -1,9 +1,5 @@
{
"entities": [
- {
- "id": "ws4",
- "type": "WeatherStation"
- },
{
"id": "Higro2000",
"type": "Higrometer",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
index 383eeb2e1..380551909 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json
@@ -1,14 +1,6 @@
{
"actionType": "append",
"entities": [
- {
- "id": "ws5",
- "type": "WeatherStation",
- "TimeInstant": {
- "type": "DateTime",
- "value": "2018-06-13T13:28:34.611Z"
- }
- },
{
"id": "Higro2000",
"type": "Higrometer",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
index da2d339ff..305d3cd64 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json
@@ -1,4 +1,6 @@
{
+ "id":"sensorCommand",
+ "type":"SensorCommand",
"PING_status": {
"type": "commandStatus",
"value": "OK",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json b/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json
index 857e30ccb..c948d7de8 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state":{
"type": "Boolean",
"value": true,
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json
index dda3c5ad0..b7970d603 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json
@@ -1,4 +1,6 @@
{
+ "id":"motion1",
+ "type":"Motion",
"moving":{
"value": true,
"type": "boolean"
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json
index 5cbe2a7cf..0a6616e04 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json
@@ -1,7 +1,15 @@
{
+ "id":"lamp1",
+ "type":"Lamp",
"luminosity": {
"value": "100",
- "type": "text"
+ "type": "text",
+ "metadata": {
+ "unitCode": {
+ "type": "Text",
+ "value": "CAL"
+ }
+ }
},
"controlledProperty": {
"value": "StaticValue",
@@ -13,4 +21,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json
index 3e72881c2..f78da553f 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"value": true,
"type": "boolean",
@@ -26,4 +28,4 @@
"type": "DateTime",
"value": "2015-08-05T07:35:01.468Z"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalse.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalse.json
new file mode 100644
index 000000000..bed0f564c
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalse.json
@@ -0,0 +1,12 @@
+{
+ "id": "lightNoTimestamp1",
+ "type": "Light",
+ "state": {
+ "value": true,
+ "type": "boolean"
+ },
+ "dimming": {
+ "value": 87,
+ "type": "number"
+ }
+}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalseTimeInstant.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalseTimeInstant.json
new file mode 100644
index 000000000..efa861305
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalseTimeInstant.json
@@ -0,0 +1,12 @@
+{
+ "id": "lightNoTimestamp1",
+ "type": "Light",
+ "state": {
+ "value": true,
+ "type": "boolean"
+ },
+ "TimeInstant": {
+ "value": "2015-12-14T08:06:01.468Z",
+ "type": "DateTime"
+ }
+}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json
index 2c139904b..6a515eee8 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"value": true,
"type": "boolean",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json
index 098178926..17795348f 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"value": true,
"type": "boolean",
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json
index 0b7cfc465..e87fd092c 100644
--- a/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json
+++ b/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json
@@ -1,4 +1,6 @@
{
+ "id":"light1",
+ "type":"Light",
"state": {
"value": true,
"type": "boolean",
@@ -26,4 +28,4 @@
"type": "DateTime",
"value": "2015-08-05T00:35:01.468-07:00"
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateEntity.json b/test/unit/ngsiv2/examples/contextRequests/updateEntity.json
new file mode 100644
index 000000000..1f2389b18
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateEntity.json
@@ -0,0 +1,5 @@
+{
+ "id":"Robot:r2d2",
+ "type":"Robot",
+ "position":{"type":"command","value":null}
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateEntity2.json b/test/unit/ngsiv2/examples/contextRequests/updateEntity2.json
new file mode 100644
index 000000000..500dd550d
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateEntity2.json
@@ -0,0 +1,5 @@
+{
+ "id":"RobotT:r2d3",
+ "type":"RobotT",
+ "position":{"type":"command","value":null}
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json b/test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json
new file mode 100644
index 000000000..b29bcc63b
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json
@@ -0,0 +1,7 @@
+{
+ "pressure":{"type":"Hgmm","value":null},
+ "reset": {
+ "type": "command",
+ "value": null
+ }
+}
diff --git a/test/unit/ngsiv2/examples/contextRequests/updateEntity3.json b/test/unit/ngsiv2/examples/contextRequests/updateEntity3.json
new file mode 100644
index 000000000..04e8442d7
--- /dev/null
+++ b/test/unit/ngsiv2/examples/contextRequests/updateEntity3.json
@@ -0,0 +1,5 @@
+{
+ "id":"RobotT:r2d4",
+ "type":"RobotT",
+ "position":{"type":"command","value":null}
+}
diff --git a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json b/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json
deleted file mode 100644
index 8674a7ecd..000000000
--- a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "subscriptionId": "51c0ac9ed714fb3b37d7d5a8",
- "data": [
- {
- "location": {
- "type": "geo:point",
- "value": "12.4, -9.6"
- },
- "type": "TheLightType",
- "id": "TheFirstLight"
- }
- ]
-}
diff --git a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json b/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json
deleted file mode 100644
index 713361185..000000000
--- a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "subscriptionId": "51c0ac9ed714fb3b37d7d5a8",
- "data": [
- {
- "location": {
- "type": "geo:point",
- "value": "12.4, -9.6",
- "metadata": {
- "qos": {
- "type": "Number",
- "value": 1
- }
- }
- },
- "type": "TheLightType",
- "id": "TheFirstLight"
- }
- ]
-}
diff --git a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json b/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json
deleted file mode 100644
index ca770ddc5..000000000
--- a/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "subject":{
- "entities": [
- {
- "id": "TheFirstLight",
- "type": "TheLightType"
- }
- ],
- "condition":{
- "attrs":[
- "location"
- ]
- }
- },
- "notification":{
- "http":{
- "url":"http://smartgondor.com/notify"
- },
- "attrs":[
- "location"
- ],
- "attrsFormat": "normalized"
- }
-}
\ No newline at end of file
diff --git a/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json b/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json
index 88feb7ea8..8879bc622 100644
--- a/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json
+++ b/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json
@@ -1,4 +1,5 @@
{
+ "description": "Managed by IOTA: FirstMicroLight MicroLights attr_name",
"subject": {
"entities": [
{
@@ -17,6 +18,7 @@
"url": "http://smartgondor.com/notify"
},
"attrs": [],
- "attrsFormat": "normalized"
+ "attrsFormat": "normalized",
+ "onlyChangedAttrs": true
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json b/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json
index 6e77b673c..ae78c9062 100644
--- a/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json
+++ b/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json
@@ -1,4 +1,5 @@
{
+ "description": "Managed by IOTA: light1 Light dimming",
"subject": {
"entities": [
{
@@ -17,6 +18,7 @@
"url": "http://smartgondor.com/notify"
},
"attrs": [],
- "attrsFormat": "normalized"
+ "attrsFormat": "normalized",
+ "onlyChangedAttrs": true
}
-}
\ No newline at end of file
+}
diff --git a/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js b/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js
index 9656e338a..0a557eae2 100644
--- a/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js
+++ b/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js
@@ -33,7 +33,6 @@ const nock = require('nock');
const timekeeper = require('timekeeper');
let contextBrokerMock;
const iotAgentConfig = {
- logLevel: 'FATAL',
contextBroker: {
host: '192.168.1.1',
port: '1026',
@@ -62,7 +61,8 @@ const iotAgentConfig = {
{
object_id: 'a',
name: 'alive',
- type: 'None'
+ type: 'None',
+ skipValue: 'null passes'
},
{
object_id: 'u',
@@ -96,7 +96,8 @@ const iotAgentConfig = {
object_id: 'p',
name: 'pressure',
type: 'Number',
- expression: 'pressure * / 20'
+ expression: 'pressure * / 20',
+ skipValue: 'null passes'
}
]
},
@@ -141,6 +142,20 @@ const iotAgentConfig = {
}
]
},
+ WeatherStationWithIdNumber: {
+ commands: [],
+ type: 'WeatherStation',
+ entityNameExp: 'id',
+ lazy: [],
+ active: [
+ {
+ object_id: 'p',
+ name: 'pressure',
+ type: 'Number',
+ expression: 'pressure * 20'
+ }
+ ]
+ },
WeatherStationUndef: {
commands: [],
type: 'WeatherStation',
@@ -271,7 +286,7 @@ const iotAgentConfig = {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'color',
type: 'string',
@@ -295,7 +310,7 @@ const iotAgentConfig = {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'color',
type: 'string',
@@ -319,7 +334,7 @@ const iotAgentConfig = {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'color',
type: 'string',
@@ -343,11 +358,11 @@ const iotAgentConfig = {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'lat',
type: 'string',
- value: '52'
+ value: 52
}
],
active: [
@@ -362,13 +377,13 @@ const iotAgentConfig = {
expression: "{coordinates: [lon,lat], type: 'Point'}"
}
],
- explicitAttrs: "theLocation ? [{object_id: 'theLocation'}] : []"
+ explicitAttrs: "mylocation ? [{object_id: 'theLocation'}] : []"
},
GPS6: {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'lat',
type: 'Number',
@@ -393,7 +408,7 @@ const iotAgentConfig = {
commands: [],
type: 'GPS',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'color',
type: 'string',
@@ -412,17 +427,313 @@ const iotAgentConfig = {
}
],
explicitAttrs: '[ ]'
+ },
+ skipvalue: {
+ commands: [],
+ type: 'skipvalue',
+ lazy: [],
+ active: [
+ {
+ name: 'alwaysSkip',
+ type: 'Number',
+ skipValue: true,
+ expression: 'true'
+ },
+ {
+ name: 'neverSkip',
+ type: 'Number',
+ skipValue: true,
+ expression: 'false'
+ },
+ {
+ name: 'skip',
+ type: 'Number',
+ skipValue: 33,
+ expression: 'condition'
+ },
+ {
+ object_id: 'condition',
+ name: 'condition',
+ type: 'Number'
+ },
+ {
+ object_id: 'nonProgressAtt1',
+ name: 'nonProgressatt1',
+ type: 'Number',
+ expression: 'nonexistent * 2'
+ },
+ {
+ object_id: 'nonProgressAtt2',
+ name: 'nonProgressatt2',
+ type: 'Number',
+ expression: 'nonexistent * 2',
+ skipValue: null
+ }
+ ]
+ },
+ nestedExpressionsObj: {
+ commands: [],
+ type: 'nestedExpressionsObj',
+ lazy: [],
+ active: [
+ {
+ name: 'value3',
+ object_id: 'v3',
+ type: 'Number',
+ expression: 'v*2'
+ },
+ {
+ name: 'value2',
+ object_id: 'v2',
+ type: 'Number',
+ expression: 'v3*2'
+ },
+ {
+ name: 'value1',
+ object_id: 'v1',
+ type: 'Number',
+ expression: 'v2*2'
+ }
+ ]
+ },
+ nestedExpressionsName: {
+ commands: [],
+ type: 'nestedExpressionsName',
+ lazy: [],
+ active: [
+ {
+ name: 'prefix',
+ object_id: 't1',
+ type: 'text',
+ expression: '"pre_"+t'
+ },
+ {
+ name: 'postfix',
+ object_id: 't2',
+ type: 'text',
+ expression: 'prefix+"_post"'
+ }
+ ]
+ },
+ nestedExpressionsSkip: {
+ commands: [],
+ type: 'nestedExpressionsSkip',
+ lazy: [],
+ active: [
+ {
+ name: 'prefix',
+ object_id: 't1',
+ type: 'text',
+ expression: '"pre_"+t'
+ },
+ {
+ name: 'postfix',
+ object_id: 't2',
+ type: 'text',
+ expression: 'prefix+"_post"'
+ },
+ {
+ name: 't',
+ object_id: 't',
+ type: 'text',
+ expression: 'null'
+ }
+ ]
+ },
+ nestedExpressionDirect: {
+ commands: [],
+ type: 'nestedExpressionsDirect',
+ lazy: [],
+ active: [
+ {
+ name: 'correctedLevel',
+ type: 'Number',
+ expression: 'level * 0.897'
+ },
+ {
+ name: 'normalizedLevel',
+ type: 'Number',
+ expression: 'correctedLevel / 100'
+ }
+ ]
+ },
+ nestedExpressionReverse: {
+ commands: [],
+ type: 'nestedExpressionsReverse',
+ lazy: [],
+ active: [
+ {
+ name: 'normalizedLevel',
+ type: 'Number',
+ expression: 'correctedLevel / 100'
+ },
+ {
+ name: 'correctedLevel',
+ type: 'Number',
+ expression: 'level * 0.897'
+ }
+ ]
+ },
+ nestedExpressionsAnti: {
+ commands: [],
+ type: 'nestedExpressionsAnti',
+ lazy: [],
+ active: [
+ {
+ name: 'a',
+ type: 'Number',
+ expression: 'b*10'
+ },
+ {
+ name: 'b',
+ type: 'Number',
+ expression: 'a*10'
+ }
+ ]
+ },
+ testNull: {
+ commands: [],
+ type: 'testNull',
+ lazy: [],
+ active: [
+ {
+ name: 'a',
+ type: 'Number',
+ expression: 'v'
+ },
+ {
+ name: 'b',
+ type: 'Number',
+ expression: 'v*3'
+ },
+ {
+ name: 'c',
+ type: 'Boolean',
+ expression: 'v==null'
+ },
+ {
+ name: 'd',
+ type: 'Text',
+ expression: "v?'no soy null':'soy null'"
+ },
+ {
+ name: 'e',
+ type: 'Text',
+ expression: "v==null?'soy null':'no soy null'"
+ },
+ {
+ name: 'f',
+ type: 'Text',
+ expression: "(v*3)==null?'soy null':'no soy null'"
+ },
+ {
+ name: 'g',
+ type: 'Boolean',
+ expression: 'v == undefined'
+ }
+ ]
+ },
+ testNullSkip: {
+ commands: [],
+ type: 'testNullSkip',
+ lazy: [],
+ active: [
+ {
+ name: 'a',
+ type: 'Number',
+ expression: 'v',
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'b',
+ type: 'Number',
+ expression: 'v*3',
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'c',
+ type: 'Boolean',
+ expression: 'v==null',
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'd',
+ type: 'Text',
+ expression: "v?'no soy null':'soy null'",
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'e',
+ type: 'Text',
+ expression: "v==null?'soy null':'no soy null'",
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'f',
+ type: 'Text',
+ expression: "(v*3)==null?'soy null':'no soy null'",
+ skipValue: 'avoidNull'
+ },
+ {
+ name: 'g',
+ type: 'Boolean',
+ expression: 'v == undefined',
+ skipValue: 'avoidNull'
+ }
+ ]
+ },
+ testNullExplicit: {
+ type: 'testNullExplicit',
+ explicitAttrs: true,
+ commands: [],
+ lazy: [],
+ active: [
+ {
+ name: 'a',
+ type: 'Number',
+ expression: 'v'
+ },
+ {
+ name: 'b',
+ type: 'Number',
+ expression: 'v*3'
+ },
+ {
+ name: 'c',
+ type: 'Boolean',
+ expression: 'v==null'
+ },
+ {
+ name: 'd',
+ type: 'Text',
+ expression: "v?'no soy null':'soy null'"
+ },
+ {
+ name: 'e',
+ type: 'Text',
+ expression: "v==null?'soy null':'no soy null'"
+ },
+ {
+ name: 'f',
+ type: 'Text',
+ expression: "(v*3)==null?'soy null':'no soy null'"
+ },
+ {
+ name: 'g',
+ type: 'Boolean',
+ expression: 'v == undefined'
+ }
+ ]
}
},
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
deviceRegistrationDuration: 'P1M',
- throttling: 'PT5S'
+ useCBflowControl: true
};
const iotAgentConfigTS = {
- logLevel: 'FATAL',
contextBroker: {
host: '192.168.1.1',
port: '1026',
@@ -501,7 +812,8 @@ const iotAgentConfigTS = {
timestamp: true,
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('Java expression language (JEXL) based transformations plugin', function () {
@@ -538,12 +850,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -556,6 +867,298 @@ describe('Java expression language (JEXL) based transformations plugin', functio
});
});
+ describe('When applying expressions with null values', function () {
+ // Case: Update for an attribute with bad expression
+ const values = [
+ {
+ name: 'v',
+ type: 'Number',
+ value: null
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'testNull1',
+ type: 'testNull',
+ v: {
+ value: null,
+ type: 'Number'
+ },
+ c: {
+ value: true,
+ type: 'Boolean'
+ },
+ d: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ e: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ f: {
+ value: 'no soy null',
+ type: 'Text'
+ },
+ g: {
+ value: true,
+ type: 'Boolean'
+ }
+ })
+ .reply(204);
+ });
+
+ it('it should be handled properly', function (done) {
+ iotAgentLib.update('testNull1', 'testNull', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When applying expressions without values (NaN)', function () {
+ // Case: Update for an attribute with bad expression
+ const values = [
+ {
+ name: 'z',
+ type: 'Number',
+ value: null
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'testNull2',
+ type: 'testNull',
+ z: {
+ value: null,
+ type: 'Number'
+ },
+ c: {
+ value: true,
+ type: 'Boolean'
+ },
+ d: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ e: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ f: {
+ value: 'no soy null',
+ type: 'Text'
+ },
+ g: {
+ value: true,
+ type: 'Boolean'
+ }
+ })
+ .reply(204);
+ });
+
+ it('it should be handled properly', function (done) {
+ iotAgentLib.update('testNull2', 'testNull', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When applying expressions with null values - Skip values disabled', function () {
+ // Case: Update for an attribute with bad expression
+ const values = [
+ {
+ name: 'v',
+ type: 'Number',
+ value: null
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'testNullSkip1',
+ type: 'testNullSkip',
+ v: {
+ value: null,
+ type: 'Number'
+ },
+ a: {
+ value: null,
+ type: 'Number'
+ },
+ b: {
+ value: null,
+ type: 'Number'
+ },
+ c: {
+ value: true,
+ type: 'Boolean'
+ },
+ d: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ e: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ f: {
+ value: 'no soy null',
+ type: 'Text'
+ },
+ g: {
+ value: true,
+ type: 'Boolean'
+ }
+ })
+ .reply(204);
+ });
+
+ it('it should be handled properly', function (done) {
+ iotAgentLib.update('testNullSkip1', 'testNullSkip', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When applying expressions without values (NaN) - Skip values disabled', function () {
+ // Case: Update for an attribute with bad expression
+ const values = [
+ {
+ name: 'z',
+ type: 'Number',
+ value: null
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'testNullSkip2',
+ type: 'testNullSkip',
+ z: {
+ value: null,
+ type: 'Number'
+ },
+ a: {
+ value: null,
+ type: 'Number'
+ },
+ b: {
+ value: null,
+ type: 'Number'
+ },
+ c: {
+ value: true,
+ type: 'Boolean'
+ },
+ d: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ e: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ f: {
+ value: 'no soy null',
+ type: 'Text'
+ },
+ g: {
+ value: true,
+ type: 'Boolean'
+ }
+ })
+ .reply(204);
+ });
+
+ it('it should be handled properly', function (done) {
+ iotAgentLib.update('testNullSkip2', 'testNullSkip', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When applying expressions with not explicit measures - explicitAttrs = true', function () {
+ // Case: Update for an attribute with bad expression
+ const values = [
+ {
+ name: 'v',
+ type: 'Number',
+ value: null
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'testNullExplicit1',
+ type: 'testNullExplicit',
+ c: {
+ value: true,
+ type: 'Boolean'
+ },
+ d: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ e: {
+ value: 'soy null',
+ type: 'Text'
+ },
+ f: {
+ value: 'no soy null',
+ type: 'Text'
+ },
+ g: {
+ value: true,
+ type: 'Boolean'
+ }
+ })
+ .reply(204);
+ });
+
+ it('it should be handled properly', function (done) {
+ iotAgentLib.update('testNullExplicit1', 'testNullExplicit', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
describe('When there are expression attributes that are just calculated (not sent by the device)', function () {
// Case: Expression which results is sent as a new attribute
const values = [
@@ -573,17 +1176,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
@@ -619,12 +1220,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
@@ -654,12 +1254,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -689,12 +1288,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
@@ -725,12 +1323,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -761,12 +1358,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
@@ -792,17 +1388,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -833,12 +1427,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -868,12 +1461,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -904,30 +1496,50 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
it('should apply the expression before sending the values', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
- contextBrokerMock.done();
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When there are expressions including other attributes and they are not updated', function () {
+ const values = [
+ {
+ name: 'x',
+ type: 'Number',
+ value: 0.44
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+ });
+
+ it('should apply the expression before sending the values', function (done) {
+ iotAgentLib.update('light1', 'Light', '', values, function (error) {
+ should.not.exist(error);
done();
});
});
});
- describe('When there are expressions including other attributes and they are not updated', function () {
+ describe('When there are expressions including other attributes and they are updated', function () {
const values = [
{
- name: 'x',
+ name: 'p',
type: 'Number',
- value: 0.44
+ value: 10
}
];
@@ -938,12 +1550,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -956,8 +1567,13 @@ describe('Java expression language (JEXL) based transformations plugin', functio
});
});
- describe('When there are expressions including other attributes and they are updated', function () {
+ describe('When there are expressions including other attributes and they are updated (overriding situation)', function () {
const values = [
+ {
+ name: 'x',
+ type: 'Number',
+ value: 0.44
+ },
{
name: 'p',
type: 'Number',
@@ -972,12 +1588,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -990,17 +1605,12 @@ describe('Java expression language (JEXL) based transformations plugin', functio
});
});
- describe('When there are expressions including other attributes and they are updated (overriding situation)', function () {
+ describe('When a measure arrives and there is not enough information to calculate an expression', function () {
const values = [
- {
- name: 'x',
- type: 'Number',
- value: 0.44
- },
{
name: 'p',
- type: 'Number',
- value: 10
+ type: 'centigrades',
+ value: '52'
}
];
@@ -1011,17 +1621,16 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json'
)
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should apply the expression before sending the values', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
+ it('should not calculate the expression', function (done) {
+ iotAgentLib.update('ws1', 'WeatherStation', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
@@ -1029,7 +1638,7 @@ describe('Java expression language (JEXL) based transformations plugin', functio
});
});
- describe('When a measure arrives and there is not enough information to calculate an expression', function () {
+ describe('When a measure arrives with id number', function () {
const values = [
{
name: 'p',
@@ -1037,6 +1646,14 @@ describe('Java expression language (JEXL) based transformations plugin', functio
value: '52'
}
];
+ const typeInformation = {
+ service: 'smartgondor',
+ subservice: 'gardens',
+ name: '1234',
+ id: '1234',
+ type: 'WeatherStation',
+ active: [{ object_id: 'p', name: 'pressure', type: 'Number', expression: 'pressure * 20' }]
+ };
beforeEach(function () {
nock.cleanAll();
@@ -1045,23 +1662,23 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29b.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
- it('should not calculate the expression', function (done) {
- iotAgentLib.update('ws1', 'WeatherStation', '', values, function (error) {
+ it('should calculate the expression', function (done) {
+ iotAgentLib.update(1234, 'WeatherStationWithIdNumber', '', values, typeInformation, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
});
});
});
+
describe('When a measure arrives and there is not enough information to calculate an expression', function () {
const values = [
{
@@ -1078,12 +1695,11 @@ describe('Java expression language (JEXL) based transformations plugin', functio
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/ws1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json'
)
)
- .query({ type: 'WeatherStation' })
.reply(204);
});
@@ -1118,17 +1734,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1163,17 +1777,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1213,17 +1825,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1258,17 +1868,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1303,17 +1911,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1353,17 +1959,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1393,17 +1997,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36b.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1428,17 +2030,15 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1478,6 +2078,16 @@ describe('Java expression language (JEXL) based transformations plugin', functio
beforeEach(function () {
nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin37.json'
+ )
+ )
+ .reply(204);
});
it('should calculate them and remove non-explicitAttrs by jexl expression with context from the payload ', function (done) {
@@ -1487,6 +2097,328 @@ describe('Java expression language (JEXL) based transformations plugin', functio
});
});
});
+
+ describe('When using skipValue is expression in a device', function () {
+ // Case: Expression which results is sent as a new attribute
+ const values = [
+ {
+ name: 'alwaysSkip',
+ type: 'Number',
+ value: 1
+ },
+ {
+ name: 'neverSkip',
+ type: 'Number',
+ value: 2
+ },
+ {
+ name: 'skip',
+ type: 'Number',
+ value: 3
+ },
+ {
+ name: 'condition',
+ type: 'Number',
+ value: 33
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionSkip.json'
+ )
+ )
+ .reply(204);
+ });
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should not propagate skipped values', function (done) {
+ iotAgentLib.update('skip1', 'skipvalue', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions by pointing to previous objetc_ids in a device ', function () {
+ const values = [
+ {
+ name: 'v',
+ type: 'Number',
+ value: 5
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nested1',
+ type: 'nestedExpressionsObj',
+ v: {
+ value: 5,
+ type: 'Number'
+ },
+ value3: {
+ value: 10,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should not calculate values using nested object_ids', function (done) {
+ iotAgentLib.update('nested1', 'nestedExpressionsObj', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions by pointing to previous attributes names in a device ', function () {
+ const values = [
+ {
+ name: 't',
+ type: 'Text',
+ value: 'nestedText'
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nested2',
+ type: 'nestedExpressionsName',
+ t: {
+ value: 'nestedText',
+ type: 'Text'
+ },
+ prefix: {
+ value: 'pre_nestedText',
+ type: 'text'
+ },
+ postfix: {
+ value: 'pre_nestedText_post',
+ type: 'text'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should calculate values using nested attributes names', function (done) {
+ iotAgentLib.update('nested2', 'nestedExpressionsName', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions by pointing to previous attributes names and skipValue ', function () {
+ const values = [
+ {
+ name: 't',
+ type: 'Text',
+ value: 'nestedText'
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nested3',
+ type: 'nestedExpressionsSkip',
+ prefix: {
+ value: 'pre_nestedText',
+ type: 'text'
+ },
+ postfix: {
+ value: 'pre_nestedText_post',
+ type: 'text'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should calculate values using nested attributes names and skip measures', function (done) {
+ iotAgentLib.update('nested3', 'nestedExpressionsSkip', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions - Direct case', function () {
+ const values = [
+ {
+ name: 'level',
+ type: 'Number',
+ value: 100
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nestedDirect',
+ type: 'nestedExpressionsDirect',
+ level: {
+ value: 100,
+ type: 'Number'
+ },
+ correctedLevel: {
+ value: 89.7,
+ type: 'Number'
+ },
+ normalizedLevel: {
+ value: 0.897,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should calculate values using nested attributes names and skip measures', function (done) {
+ iotAgentLib.update('nestedDirect', 'nestedExpressionDirect', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions - Reverse case - Antipattern', function () {
+ const values = [
+ {
+ name: 'level',
+ type: 'Number',
+ value: 100
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nestedReverse',
+ type: 'nestedExpressionsReverse',
+ level: {
+ value: 100,
+ type: 'Number'
+ },
+ correctedLevel: {
+ value: 89.7,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should calculate values using nested attributes names and skip measures', function (done) {
+ iotAgentLib.update('nestedReverse', 'nestedExpressionReverse', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using nested expressions - Antipattern', function () {
+ const values = [
+ {
+ name: 'a',
+ type: 'Number',
+ value: 10
+ },
+ {
+ name: 'b',
+ type: 'Number',
+ value: 20
+ }
+ ];
+
+ beforeEach(function () {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'nestedAnti',
+ type: 'nestedExpressionsAnti',
+ a: {
+ value: 200,
+ type: 'Number'
+ },
+ b: {
+ value: 2000,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+ });
+
+ afterEach(function (done) {
+ done();
+ });
+
+ it('should calculate values using nested attributes names and skip measures', function (done) {
+ iotAgentLib.update('nestedAnti', 'nestedExpressionsAnti', '', values, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
});
describe('Java expression language (JEXL) based transformations plugin - Timestamps', function () {
@@ -1536,12 +2468,11 @@ describe('Java expression language (JEXL) based transformations plugin - Timesta
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/gps1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json'
)
)
- .query({ type: 'GPS' })
.reply(204);
});
@@ -1574,12 +2505,11 @@ describe('Java expression language (JEXL) based transformations plugin - Timesta
timekeeper.freeze(time);
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json'
)
diff --git a/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js b/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js
index 3e5fc0d65..d949c3d4f 100644
--- a/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js
+++ b/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js
@@ -88,7 +88,8 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider', function () {
@@ -130,10 +131,9 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('Authorization', 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3cHdWclJ3')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext1.json')
)
- .query({ type: 'Light' })
.reply(204, {});
iotAgentLib.activate(iotAgentConfig, done);
@@ -170,10 +170,9 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('Authorization', 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3cHdWclJ3')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext1.json')
)
- .query({ type: 'Light' })
.reply(403, {});
iotAgentLib.activate(iotAgentConfig, done);
@@ -206,7 +205,7 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('Authorization', 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ3cHdWclJ3')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext1.json')
)
.reply(204, {});
@@ -293,15 +292,6 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
)
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- contextBrokerMock
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic3.json'
- )
- )
- .reply(204, {});
-
contextBrokerMock
.post(
'/v2/subscriptions',
@@ -321,8 +311,8 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
});
it('subscribe requests use auth header', function (done) {
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -342,8 +332,8 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider',
contextBrokerMock.delete('/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8', '').reply(204);
- iotAgentLib.getDevice('Light1', 'smartgondor', 'electricity', function (error, device) {
- iotAgentLib.subscribe(device, ['dimming'], null, function (error) {
+ iotAgentLib.getDevice('Light1', null, 'smartgondor', 'electricity', function (error, device) {
+ iotAgentLib.subscribe(device, ['dimming'], null, 'normalized', function (error) {
iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
contextBrokerMock.done();
done();
@@ -397,10 +387,9 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider (F
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('Authorization', 'Bearer c1b752e377680acd1349a3ed59db855a1db07605')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext1.json')
)
- .query({ type: 'Light' })
.reply(204, {});
iotAgentConfig.authentication.tokenPath = '/oauth2/token';
@@ -537,10 +526,9 @@ describe('NGSI-v2 - Secured access to the Context Broker with OAuth2 provider (F
.matchHeader('fiware-servicepath', 'electricity')
.matchHeader('Authorization', 'Bearer c1b752e377680acd1349a3ed59db855a1db07605')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext1.json')
)
- .query({ type: 'Light' })
.reply(401, 'Auth-token not found in request header');
iotAgentLib.activate(iotAgentConfig, done);
@@ -625,12 +613,11 @@ describe(
.matchHeader('fiware-servicepath', '/testingPath')
.matchHeader('Authorization', 'Bearer c1b752e377680acd1349a3ed59db855a1db07605')
.post(
- '/v2/entities/machine1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json'
)
)
- .query({ type: 'SensorMachine' })
.reply(204, {});
contextBrokerMock2 = nock('http://unexistentHost:1026')
@@ -638,12 +625,11 @@ describe(
.matchHeader('fiware-servicepath', '/testingPath')
.matchHeader('Authorization', 'Bearer bbb752e377680acd1349a3ed59db855a1db076aa')
.post(
- '/v2/entities/machine1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json'
)
)
- .query({ type: 'SensorMachine' })
.reply(204, {});
iotAgentConfig.authentication.tokenPath = '/oauth2/token';
@@ -693,7 +679,6 @@ describe(
'fiware-servicepath': '/testingPath'
}
};
- let contextBrokerMock2;
let contextBrokerMock3;
beforeEach(function (done) {
const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
@@ -750,27 +735,14 @@ describe(
)
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- contextBrokerMock2 = nock('http://unexistenthost:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .matchHeader('authorization', 'Bearer bea752e377680acd1349a3ed59db855a1db07zxc')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic2.json'
- )
- )
- .reply(204, {});
-
contextBrokerMock3 = nock('http://unexistentHost:1026')
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .matchHeader('authorization', 'Bearer zzz752e377680acd1349a3ed59db855a1db07bbb')
+ .matchHeader('authorization', 'Bearer bea752e377680acd1349a3ed59db855a1db07zxc')
.post(
- '/v2/entities/Light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext4.json')
)
- .query({ type: 'SensorMachine' })
.reply(204, {});
iotAgentConfig.authentication.tokenPath = '/oauth2/token';
@@ -790,7 +762,6 @@ describe(
should.not.exist(error);
response.statusCode.should.equal(201);
contextBrokerMock.done();
- contextBrokerMock2.done();
done();
});
});
@@ -845,14 +816,13 @@ describe(
contextBrokerMock = nock('http://unexistentHost:1026')
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .matchHeader('Authorization', 'Bearer 999210dacf913772606c95dd0b895d5506cbc988')
+ .matchHeader('Authorization', 'Bearer 000210dacf913772606c95dd0b895d5506cbc700')
.post(
- '/v2/entities/machine1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json'
)
)
- .query({ type: 'SensorMachine' })
.reply(204, {});
iotAgentConfig.authentication.tokenPath = '/oauth2/token';
@@ -865,7 +835,6 @@ describe(
it('should send the permanent token in the auth header', function (done) {
iotAgentLib.update('machine1', 'SensorMachine', '', values, function (error) {
should.not.exist(error);
- contextBrokerMock.done();
done();
});
});
@@ -873,7 +842,6 @@ describe(
it('should use the permanent trust token in the following requests', function (done) {
iotAgentLib.update('machine1', 'SensorMachine', '', values, function (error) {
should.not.exist(error);
- contextBrokerMock.done();
done();
});
});
diff --git a/test/unit/ngsiv2/general/deviceService-test.js b/test/unit/ngsiv2/general/deviceService-test.js
index ce4a4043a..92819f9d2 100644
--- a/test/unit/ngsiv2/general/deviceService-test.js
+++ b/test/unit/ngsiv2/general/deviceService-test.js
@@ -126,7 +126,8 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
const groupCreation = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
@@ -156,6 +157,34 @@ const groupCreation = {
'fiware-servicepath': '/testingPath'
}
};
+const configGroupCreation = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'TheLightType',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://192.168.1.1:1026',
+ commands: [],
+ lazy: [],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ static_attributes: []
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'testservice',
+ 'fiware-servicepath': '/testingPath'
+ }
+};
const deviceCreation = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
method: 'POST',
@@ -182,7 +211,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
nock.cleanAll();
async.series([iotAgentLib.clearAll, iotAgentLib.deactivate], done);
});
-
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When an existing device tries to be retrieved with retrieveOrCreate()', function () {
beforeEach(function (done) {
// This mock does not check the payload since the aim of the test is not to verify
@@ -191,7 +220,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series(
@@ -212,7 +241,39 @@ describe('NGSI-v2 - Device Service: utils', function () {
});
});
});
+ describe('When an existing device tries to be retrieved with retrieveOrCreate()', function () {
+ beforeEach(function (done) {
+ // This mock does not check the payload since the aim of the test is not to verify
+ // device provisioning functionality. Appropriate verification is done in tests under
+ // provisioning folder
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ async.series(
+ [
+ utils.request.bind(utils.request, configGroupCreation),
+ utils.request.bind(utils.request, deviceCreation)
+ ],
+ function (error, results) {
+ done();
+ }
+ );
+ });
+
+ it('should return the existing device', function (done) {
+ iotAgentLib.retrieveDevice('Light1', '801230BJKL23Y9090DSFL123HJK09H324HV8732', function (error, device) {
+ should.not.exist(error);
+ should.exist(device);
+ device.id.should.equal('Light1');
+ done();
+ });
+ });
+ });
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When an unexisting device tries to be retrieved for an existing APIKey', function () {
beforeEach(function (done) {
// This mock does not check the payload since the aim of the test is not to verify
@@ -221,7 +282,7 @@ describe('NGSI-v2 - Device Service: utils', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([utils.request.bind(utils.request, groupCreation)], function (error, results) {
@@ -246,6 +307,39 @@ describe('NGSI-v2 - Device Service: utils', function () {
});
});
+ describe('When an unexisting device tries to be retrieved for an existing APIKey', function () {
+ beforeEach(function (done) {
+ // This mock does not check the payload since the aim of the test is not to verify
+ // device provisioning functionality. Appropriate verification is done in tests under
+ // provisioning folder
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'testservice')
+ .matchHeader('fiware-servicepath', '/testingPath')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ async.series([utils.request.bind(utils.request, configGroupCreation)], function (error, results) {
+ done();
+ });
+ });
+
+ it('should register the device and return it', function (done) {
+ iotAgentLib.retrieveDevice(
+ 'UNEXISTENT_DEV',
+ '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ function (error, device) {
+ should.not.exist(error);
+ should.exist(device);
+
+ device.id.should.equal('UNEXISTENT_DEV');
+ should.exist(device.protocol);
+ device.protocol.should.equal('MQTT_UL');
+ done();
+ }
+ );
+ });
+ });
+
describe('When an unexisting device tries to be retrieved for an unexisting APIKey', function () {
it('should raise an error', function (done) {
iotAgentLib.retrieveDevice(
diff --git a/test/unit/ngsiv2/general/healthcheck-test.js b/test/unit/ngsiv2/general/healthcheck-test.js
new file mode 100644
index 000000000..e08d48f04
--- /dev/null
+++ b/test/unit/ngsiv2/general/healthcheck-test.js
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: AVG
+ */
+
+/* eslint-disable no-unused-vars */
+/* eslint-disable no-unused-expressions */
+
+const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
+const should = require('should');
+const nock = require('nock');
+const utils = require('../../../tools/utils');
+const request = utils.request;
+const config = require('../../../../lib/commonConfig');
+const iotAgentConfig = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026'
+ },
+ server: {
+ port: 4041,
+ host: 'localhost'
+ },
+ iotManager: {
+ host: '192.168.1.1',
+ port: 9876,
+ path: '/protocols',
+ protocol: 'GENERIC_PROTOCOL',
+ description: 'A generic protocol',
+ agentPath: '/iot'
+ },
+ types: {
+ Light: {
+ commands: [],
+ type: 'Light',
+ lazy: [
+ {
+ name: 'temperature',
+ type: 'centigrades'
+ }
+ ],
+ attributes: [
+ {
+ name: 'pressure',
+ type: 'Hgmm'
+ }
+ ]
+ }
+ },
+ deviceRegistry: {
+ type: 'mongodb'
+ },
+ mongodb: {
+ host: 'localhost',
+ port: '27017',
+ db: 'iotagent'
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M'
+};
+
+const iotAgentConfig2 = {
+ logLevel: 'FATAL',
+ server: {
+ port: 4041,
+ host: 'localhost'
+ },
+ types: {
+ Light: {
+ commands: [],
+ type: 'Light',
+ lazy: [
+ {
+ name: 'temperature',
+ type: 'centigrades'
+ }
+ ],
+ attributes: [
+ {
+ name: 'pressure',
+ type: 'Hgmm'
+ }
+ ]
+ }
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M'
+};
+
+const iotAgentConfig3 = {
+ logLevel: 'FATAL',
+ server: {
+ port: 4041,
+ host: 'localhost'
+ },
+ contextBroker: {
+ host: 'bad',
+ port: 'bad'
+ },
+ iotManager: {
+ host: 'bad',
+ port: 0,
+ path: '/protocols',
+ protocol: 'GENERIC_PROTOCOL',
+ description: 'A generic protocol',
+ agentPath: '/iot'
+ },
+ types: {
+ Light: {
+ commands: [],
+ type: 'Light',
+ lazy: [
+ {
+ name: 'temperature',
+ type: 'centigrades'
+ }
+ ],
+ attributes: [
+ {
+ name: 'pressure',
+ type: 'Hgmm'
+ }
+ ]
+ }
+ },
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ deviceRegistrationDuration: 'P1M'
+};
+
+let contextBrokerMock;
+let iotamMock;
+
+describe('About API with check health', function () {
+ beforeEach(function (done) {
+ process.env.IOTA_HEALTH_CHECK = true;
+ process.env.IOTA_HEALTH_CHECK_INTERVAL = 10000;
+ process.env.IOTA_HEALTH_CHECK_TIMEOUT = 1500;
+ process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS = 2;
+ process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP = false;
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026').get('/version').reply(200, '4.9.0');
+
+ iotamMock = nock('http://192.168.1.1:9876')
+ .post('/protocols', utils.readExampleFile('./test/unit/examples/iotamRequests/registrationEmpty.json'))
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+ iotamMock.get('/iot/protocols').reply(200, 'UP');
+
+ iotAgentLib.activate(iotAgentConfig, function (err) {
+ iotAgentLib.clearAll(function (err2) {
+ done();
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ delete process.env.IOTA_HEALTH_CHECK;
+ delete process.env.IOTA_HEALTH_CHECK_INTERVAL;
+ delete process.env.IOTA_HEALTH_CHECK_TIMEOUT;
+ delete process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS;
+ delete process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP;
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When the IoT Agent is started with health check in /iot/about', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/about',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ }
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(200);
+ body.connections.contextBroker.ok.should.equal(true);
+ body.connections.iotagentManager.ok.should.equal(true);
+ body.connections.mongodb.ok.should.equal(true);
+ body.connections.mqtt.configured.should.equal(false);
+
+ contextBrokerMock.done();
+ iotamMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When the IoT Agent is started with health check in /metrics', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/metrics',
+ method: 'GET',
+ headers: {
+ Accept: 'text/plain'
+ },
+ responseType: 'text'
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(200);
+ contextBrokerMock.done();
+ iotamMock.done();
+ done();
+ });
+ });
+ });
+});
+
+describe('About API with check health with errors in endpoints', function () {
+ beforeEach(function (done) {
+ process.env.IOTA_HEALTH_CHECK = true;
+ process.env.IOTA_HEALTH_CHECK_INTERVAL = 10000;
+ process.env.IOTA_HEALTH_CHECK_TIMEOUT = 1500;
+ process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS = 1;
+ process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP = false;
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026').get('/version').reply(500);
+
+ iotamMock = nock('http://192.168.1.1:9876')
+ .post('/protocols', utils.readExampleFile('./test/unit/examples/iotamRequests/registrationEmpty.json'))
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+ iotamMock.get('/iot/protocols').reply(500, 'DOWN');
+
+ iotAgentLib.activate(iotAgentConfig, function (err) {
+ iotAgentLib.clearAll(function (err2) {
+ done();
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ delete process.env.IOTA_HEALTH_CHECK;
+ delete process.env.IOTA_HEALTH_CHECK_INTERVAL;
+ delete process.env.IOTA_HEALTH_CHECK_TIMEOUT;
+ delete process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS;
+ delete process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP;
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When the IoT Agent is started with health check', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/about',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ }
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(200);
+ body.connections.contextBroker.ok.should.equal(false);
+ body.connections.iotagentManager.ok.should.equal(false);
+ body.connections.mongodb.ok.should.equal(true);
+ body.connections.mqtt.configured.should.equal(false);
+
+ contextBrokerMock.done();
+ iotamMock.done();
+ done();
+ });
+ });
+ });
+});
+
+describe('About API with check health with bad urls in endpoints', function () {
+ beforeEach(function (done) {
+ process.env.IOTA_HEALTH_CHECK = true;
+ process.env.IOTA_HEALTH_CHECK_INTERVAL = 10000;
+ process.env.IOTA_HEALTH_CHECK_TIMEOUT = 1500;
+ process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS = 1;
+ process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP = true;
+ nock.cleanAll();
+
+ iotAgentLib.activate(iotAgentConfig3, function (err) {
+ iotAgentLib.clearAll(function (err2) {
+ done();
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ delete process.env.IOTA_HEALTH_CHECK;
+ delete process.env.IOTA_HEALTH_CHECK_INTERVAL;
+ delete process.env.IOTA_HEALTH_CHECK_TIMEOUT;
+ delete process.env.IOTA_HEALTH_CHECK_DOWN_AFTER_FAILS;
+ delete process.env.IOTA_HEALTH_CHECK_CONSIDER_HTTP_RESPONSE_UP;
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When the IoT Agent is started with health check', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/about',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ }
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(200);
+ body.connections.contextBroker.ok.should.equal(false);
+ body.connections.iotagentManager.ok.should.equal(false);
+ body.connections.mongodb.configured.should.equal(false);
+ body.connections.mqtt.configured.should.equal(false);
+
+ done();
+ });
+ });
+ });
+});
+
+describe('About API with check health without endpoints', function () {
+ beforeEach(function (done) {
+ process.env.IOTA_HEALTH_CHECK = true;
+ nock.cleanAll();
+
+ iotAgentLib.activate(iotAgentConfig2, function (err) {
+ iotAgentLib.clearAll(function (err2) {
+ done();
+ });
+ });
+ });
+
+ afterEach(function (done) {
+ delete process.env.IOTA_HEALTH_CHECK;
+ nock.cleanAll();
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When the IoT Agent is started with health check', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/about',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ }
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(200);
+ body.connections.contextBroker.configured.should.equal(false);
+ body.connections.iotagentManager.configured.should.equal(false);
+ body.connections.mongodb.configured.should.equal(false);
+ body.connections.mqtt.configured.should.equal(false);
+
+ done();
+ });
+ });
+ });
+
+ describe('When the IoT Agent is started with health check in /ready', function () {
+ it('should respond health check state in about API', function (done) {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/ready',
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json'
+ }
+ };
+ /* eslint-disable-next-line consistent-return */
+ request(options, function (error, response, body) {
+ if (error) {
+ return done(error);
+ }
+ response.statusCode.should.equal(503);
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/ngsiv2/general/https-support-test.js b/test/unit/ngsiv2/general/https-support-test.js
index 4679c0254..ab9bfd587 100644
--- a/test/unit/ngsiv2/general/https-support-test.js
+++ b/test/unit/ngsiv2/general/https-support-test.js
@@ -175,7 +175,7 @@ describe('NGSI-v2 - HTTPS support tests', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
)
@@ -210,8 +210,8 @@ describe('NGSI-v2 - HTTPS support tests', function () {
});
it('should send the appropriate request to the Context Broker', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
+ iotAgentLib.getDevice('MicroLight1', null, 'smartgondor', '/gardens', function (error, device) {
+ iotAgentLib.subscribe(device, ['attr_name'], null, 'normalized', function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -229,11 +229,7 @@ describe('NGSI-v2 - HTTPS support tests', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock = nock('https://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
+ contextBrokerMock = nock('https://192.168.1.1:1026');
const nockBody = utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json'
diff --git a/test/unit/ngsiv2/general/iotam-autoregistration-test.js b/test/unit/ngsiv2/general/iotam-autoregistration-test.js
index 6c38e5771..8a8d0149c 100644
--- a/test/unit/ngsiv2/general/iotam-autoregistration-test.js
+++ b/test/unit/ngsiv2/general/iotam-autoregistration-test.js
@@ -134,6 +134,43 @@ const optionsCreation = {
'fiware-servicepath': 'theSubService'
}
};
+const configGroupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'SensorMachine',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://unexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'theservice',
+ 'fiware-servicepath': 'theSubService'
+ }
+};
const optionsCreationStatic = {
url: 'http://localhost:4041/iot/services',
method: 'POST',
@@ -172,6 +209,44 @@ const optionsCreationStatic = {
'fiware-servicepath': 'theSubService'
}
};
+const configGroupCreationStatic = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ entity_type: 'SensorMachine',
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://unexistentHost:1026',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'position',
+ type: 'location',
+ values: '123,12'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'theservice',
+ 'fiware-servicepath': 'theSubService'
+ }
+};
const optionsDelete = {
url: 'http://localhost:4041/iot/services',
method: 'DELETE',
@@ -185,6 +260,19 @@ const optionsDelete = {
apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
}
};
+const configGroupDelete = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'DELETE',
+ json: {},
+ headers: {
+ 'fiware-service': 'theservice',
+ 'fiware-servicepath': 'theSubService'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
let iotamMock;
describe('NGSI-v2 - IoT Manager autoregistration', function () {
@@ -263,6 +351,7 @@ describe('NGSI-v2 - IoT Manager autoregistration', function () {
});
});
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When a new service is created in the IoT Agent', function () {
beforeEach(function (done) {
nock.cleanAll();
@@ -298,6 +387,42 @@ describe('NGSI-v2 - IoT Manager autoregistration', function () {
});
});
+ describe('When a new configGroup is created in the IoT Agent', function () {
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ iotamMock = nock('http://mockediotam.com:9876')
+ .post('/protocols', utils.readExampleFile('./test/unit/examples/iotamRequests/registrationEmpty.json'))
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ iotamMock
+ .post(
+ '/protocols',
+ utils.readExampleFile('./test/unit/examples/iotamRequests/registrationWithGroups.json')
+ )
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ iotAgentLib.activate(iotAgentConfig, function (error) {
+ done();
+ });
+ });
+
+ afterEach(function (done) {
+ groupRegistryMemory.clear(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ it('should update the registration in the IoT Manager', function (done) {
+ request(configGroupCreation, function (error, result, body) {
+ should.not.exist(error);
+ iotamMock.done();
+ done();
+ });
+ });
+ });
+
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When a service is removed from the IoT Agent', function () {
beforeEach(function (done) {
nock.cleanAll();
@@ -333,6 +458,42 @@ describe('NGSI-v2 - IoT Manager autoregistration', function () {
});
});
+ describe('When a configGroup is removed from the IoT Agent', function () {
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ iotamMock = nock('http://mockediotam.com:9876')
+ .post(
+ '/protocols',
+ utils.readExampleFile('./test/unit/examples/iotamRequests/registrationWithGroups.json')
+ )
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ iotamMock
+ .post('/protocols', utils.readExampleFile('./test/unit/examples/iotamRequests/registrationEmpty.json'))
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ groupRegistryMemory.create(groupCreation, function () {
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function (done) {
+ groupRegistryMemory.clear(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ it('should update the registration in the IoT Manager', function (done) {
+ request(configGroupDelete, function (error, result, body) {
+ should.not.exist(error);
+ iotamMock.done();
+ done();
+ });
+ });
+ });
+
+ // #FIXME1649: this test will be removed if at the end /iot/services API (now Deprecated) is removed
describe('When a new service with static attributes is created in the IoT Agent', function () {
beforeEach(function (done) {
nock.cleanAll();
@@ -367,4 +528,38 @@ describe('NGSI-v2 - IoT Manager autoregistration', function () {
});
});
});
+ describe('When a new configGroup with static attributes is created in the IoT Agent', function () {
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ iotamMock = nock('http://mockediotam.com:9876')
+ .post('/protocols', utils.readExampleFile('./test/unit/examples/iotamRequests/registrationEmpty.json'))
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ iotamMock
+ .post(
+ '/protocols',
+ utils.readExampleFile('./test/unit/examples/iotamRequests/registrationWithStaticGroups.json')
+ )
+ .reply(200, utils.readExampleFile('./test/unit/examples/iotamResponses/registrationSuccess.json'));
+
+ iotAgentLib.activate(iotAgentConfig, function (error) {
+ done();
+ });
+ });
+
+ afterEach(function (done) {
+ groupRegistryMemory.clear(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ it('should update the registration in the IoT Manager', function (done) {
+ request(configGroupCreationStatic, function (error, result, body) {
+ should.not.exist(error);
+ iotamMock.done();
+ done();
+ });
+ });
+ });
});
diff --git a/test/unit/ngsiv2/general/startup-test.js b/test/unit/ngsiv2/general/startup-test.js
index 808995501..d4a81b88b 100644
--- a/test/unit/ngsiv2/general/startup-test.js
+++ b/test/unit/ngsiv2/general/startup-test.js
@@ -86,6 +86,7 @@ describe('NGSI-v2 - Startup tests', function () {
process.env.IOTA_MONGO_DB = 'themongodb';
process.env.IOTA_MONGO_REPLICASET = 'customReplica';
process.env.IOTA_DEFAULT_RESOURCE = '/iot/custom';
+ process.env.IOTA_HEALTH_CHECK = true;
nock.cleanAll();
@@ -117,6 +118,7 @@ describe('NGSI-v2 - Startup tests', function () {
delete process.env.IOTA_MONGO_DB;
delete process.env.IOTA_MONGO_REPLICASET;
delete process.env.IOTA_DEFAULT_RESOURCE;
+ delete process.env.IOTA_HEALTH_CHECK;
});
afterEach(function (done) {
@@ -158,6 +160,7 @@ describe('NGSI-v2 - Startup tests', function () {
process.env.IOTA_MONGO_AUTH_SOURCE = 'customAuthSource';
process.env.IOTA_MONGO_RETRIES = '10';
process.env.IOTA_MONGO_RETRY_TIME = '5';
+ process.env.IOTA_HEALTH_CHECK = true;
nock.cleanAll();
@@ -181,6 +184,7 @@ describe('NGSI-v2 - Startup tests', function () {
delete process.env.IOTA_MONGO_RETRY_TIME;
delete process.env.IOTA_MONGO_SSL;
delete process.env.IOTA_MONGO_EXTRAARGS;
+ delete process.env.IOTA_HEALTH_CHECK;
});
afterEach(function (done) {
diff --git a/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js b/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js
index a25cf914b..2599cd16e 100644
--- a/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js
+++ b/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js
@@ -58,7 +58,8 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
const device = {
id: 'somelight',
@@ -82,7 +83,7 @@ describe('NGSI-v2 - Update attribute functionalities', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -103,7 +104,7 @@ describe('NGSI-v2 - Update attribute functionalities', function () {
describe('When a attribute update arrives to the IoT Agent as Context Provider', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
diff --git a/test/unit/ngsiv2/lazyAndCommands/command-test.js b/test/unit/ngsiv2/lazyAndCommands/command-test.js
index 7b9fe5d11..ae23f2747 100644
--- a/test/unit/ngsiv2/lazyAndCommands/command-test.js
+++ b/test/unit/ngsiv2/lazyAndCommands/command-test.js
@@ -31,6 +31,7 @@ const request = utils.request;
const should = require('should');
const logger = require('logops');
const nock = require('nock');
+const async = require('async');
const mongoUtils = require('../../mongodb/mongoDBUtils');
const timekeeper = require('timekeeper');
@@ -99,17 +100,98 @@ const iotAgentConfig = {
lazy: [],
staticAttributes: [],
active: []
+ },
+ RobotT: {
+ commands: [
+ {
+ name: 'position',
+ type: 'Array'
+ }
+ ],
+ lazy: [],
+ staticAttributes: [],
+ active: [],
+ cmdMode: 'notification'
}
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true,
+ cmdMode: 'legacy'
};
const device3 = {
id: 'r2d2',
type: 'Robot',
service: 'smartgondor',
- subservice: 'gardens'
+ subservice: 'gardens',
+ cmdMode: 'legacy'
+};
+
+const device4 = {
+ id: 'r2d2',
+ type: 'Robot',
+ service: 'smartgondor',
+ subservice: 'gardens',
+ cmdMode: 'notification',
+ apikey: null
+};
+
+const device5 = {
+ id: 'r2d3',
+ type: 'RobotT',
+ service: 'smartgondor',
+ subservice: 'gardens',
+ apikey: null,
+ cmdMode: 'notification'
+};
+
+const device5old = {
+ id: 'r2d3',
+ name: 'RobotT:r2d3',
+ type: 'RobotT',
+ service: 'smartgondor',
+ subservice: 'gardens',
+ apikey: null,
+ cmdMode: 'notification',
+ commands: [
+ {
+ name: 'position',
+ type: 'Array'
+ }
+ ],
+ subscriptions: [{ id: '6319a7f5254b05844116584d', triggers: ['position'] }],
+ subscriptionId: '6319a7f5254b05844116584d'
+};
+
+const device5updated = {
+ id: 'r2d3',
+ name: 'RobotT:r2d3',
+ type: 'RobotT',
+ service: 'smartgondor',
+ subservice: 'gardens',
+ apikey: null,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Hgmm'
+ }
+ ],
+ commands: [
+ {
+ name: 'reset',
+ type: 'Array'
+ }
+ ],
+ cmdMode: 'notification'
+};
+
+const device6 = {
+ id: 'r2d4',
+ type: 'RobotT',
+ service: 'smartgondor',
+ subservice: 'gardens',
+ apikey: null
};
describe('NGSI-v2 - Command functionalities', function () {
@@ -130,12 +212,6 @@ describe('NGSI-v2 - Command functionalities', function () {
)
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
iotAgentLib.activate(iotAgentConfig, done);
});
@@ -165,7 +241,7 @@ describe('NGSI-v2 - Command functionalities', function () {
});
describe('When a command update arrives to the IoT Agent as Context Provider', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -270,7 +346,7 @@ describe('NGSI-v2 - Command functionalities', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/r2d2/attrs?type=Robot',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json')
)
.reply(204);
@@ -294,7 +370,7 @@ describe('NGSI-v2 - Command functionalities', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/r2d2/attrs?type=Robot',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json')
)
.reply(204);
@@ -315,7 +391,7 @@ describe('NGSI-v2 - Command functionalities', function () {
describe('When a command update with metadata arrives to the IoT Agent as Context Provider', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -419,3 +495,165 @@ describe('NGSI-v2 - Command functionalities', function () {
});
});
});
+
+describe('NGSI-v2 - Command notification functionalities', function () {
+ beforeEach(function (done) {
+ logger.setLevel('FATAL');
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/subscriptions',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateEntity.json')
+ )
+ .reply(204);
+ iotAgentConfig.cmdMode = 'notification';
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ afterEach(function (done) {
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(function () {
+ mongoUtils.cleanDbs(function () {
+ nock.cleanAll();
+ iotAgentLib.setDataUpdateHandler();
+ iotAgentLib.setCommandHandler();
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device with commands by notifications is registered with with commands', function () {
+ it('should subscribe a Context Provider of the commands', function (done) {
+ iotAgentLib.register(device4, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+});
+
+describe('NGSI-v2 - Command update subscription and notification functionalities', function () {
+ afterEach(function (done) {
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When a device with commands by notifications is registered with with commands and its updated', function () {
+ beforeEach(function (done) {
+ logger.setLevel('FATAL');
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .post(
+ '/v2/subscriptions',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateEntity2.json')
+ )
+ .reply(204);
+
+ contextBrokerMock
+ .post(
+ '/v2/subscriptions',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities/RobotT:r2d3/attrs?type=RobotT',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json')
+ )
+ .reply(204);
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .delete('/v2/subscriptions/6319a7f5254b05844116584d')
+ .reply(204, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+
+ iotAgentLib.activate(iotAgentConfig, function (error) {
+ async.series([async.apply(iotAgentLib.clearAll), async.apply(iotAgentLib.register, device5)], done);
+ });
+ });
+
+ it('should update subscription in a Context Provider of the commands', function (done) {
+ iotAgentLib.updateRegister(device5updated, device5old, false, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+});
+
+describe('NGSI-v2 - Command unsubscribe notification functionalities', function () {
+ afterEach(function (done) {
+ iotAgentLib.clearAll(function () {
+ iotAgentLib.deactivate(done);
+ });
+ });
+
+ describe('When a device with commands by notifications is registered with with commands', function () {
+ beforeEach(function (done) {
+ logger.setLevel('FATAL');
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .post(
+ '/v2/subscriptions',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateEntity3.json')
+ )
+ .reply(204);
+ contextBrokerMock
+ .delete('/v2/subscriptions/6319a7f5254b05844116584d')
+ .reply(204, null, { Location: '/v2/subscriptions/6319a7f5254b05844116584d' });
+ iotAgentLib.activate(iotAgentConfig, function (error) {
+ async.series([async.apply(iotAgentLib.clearAll), async.apply(iotAgentLib.register, device6)], done);
+ });
+ });
+
+ it('should unsubscribe a Context Provider of the commands', function (done) {
+ iotAgentLib.unregister(device6.id, null, 'smartgondor', 'gardens', function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js
index 105726366..c16de39e1 100644
--- a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js
+++ b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js
@@ -114,7 +114,8 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
const device1 = {
id: 'light1',
@@ -170,7 +171,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
describe('When the IoT Agent receives an update on the device data in JSON format', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -208,7 +209,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
@@ -279,7 +280,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
@@ -341,7 +342,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series(
@@ -414,7 +415,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
@@ -485,7 +486,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device2)], done);
@@ -517,7 +518,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
'internalAttributes',
function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -555,7 +556,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device3)], done);
@@ -634,7 +635,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(3)
.reply(204);
@@ -717,7 +718,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.times(3)
.reply(204);
@@ -789,7 +790,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
@@ -852,7 +853,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -917,7 +918,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -984,7 +985,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([apply(iotAgentLib.activate, iotAgentConfig), apply(iotAgentLib.register, device1)], done);
@@ -1052,7 +1053,7 @@ describe('NGSI-v2 - IoT Agent Lazy Devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
diff --git a/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js b/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js
index c23e16d4a..495bf00a0 100644
--- a/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js
+++ b/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js
@@ -126,7 +126,9 @@ const iotAgentConfig = {
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
pollingExpiration: 200,
- pollingDaemonFrequency: 20
+ pollingDaemonFrequency: 20,
+ useCBflowControl: true,
+ healthCheck: true
};
const device3 = {
id: 'r2d2',
@@ -158,7 +160,7 @@ describe('NGSI-v2 - Polling commands', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -180,7 +182,7 @@ describe('NGSI-v2 - Polling commands', function () {
describe('When a command update arrives to the IoT Agent for a device with polling', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -202,14 +204,7 @@ describe('NGSI-v2 - Polling commands', function () {
};
beforeEach(function (done) {
- statusAttributeMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/Robot:r2d2/attrs?type=Robot',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json')
- )
- .reply(204);
+ statusAttributeMock = nock('http://192.168.1.1:1026');
iotAgentLib.register(device3, function (error) {
done();
@@ -271,7 +266,7 @@ describe('NGSI-v2 - Polling commands', function () {
describe('When a command arrives with multiple values in the value field', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -300,7 +295,7 @@ describe('NGSI-v2 - Polling commands', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/Robot:r2d2/attrs?type=Robot',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json')
)
.reply(204);
@@ -327,7 +322,7 @@ describe('NGSI-v2 - Polling commands', function () {
describe('When a polling command expires', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -353,16 +348,7 @@ describe('NGSI-v2 - Polling commands', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/Robot:r2d2/attrs?type=Robot',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json')
- )
- .reply(204);
-
- statusAttributeMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/Robot:r2d2/attrs?type=Robot',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json'
)
@@ -422,7 +408,7 @@ describe('NGSI-v2 - Polling commands expressions', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentConfig.pollingExpiration = 0;
@@ -446,7 +432,7 @@ describe('NGSI-v2 - Polling commands expressions', function () {
describe('When a command update arrives to the IoT Agent for a device with polling', function () {
const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update?options=flowControl',
method: 'POST',
json: {
actionType: 'update',
@@ -468,16 +454,7 @@ describe('NGSI-v2 - Polling commands expressions', function () {
};
beforeEach(function (done) {
- statusAttributeMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/RobotExp:r2d4/attrs?type=RobotExp',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json'
- )
- )
- .reply(204);
+ statusAttributeMock = nock('http://192.168.1.1:1026');
iotAgentLib.register(device4, function (error) {
done();
diff --git a/test/unit/ngsiv2/ngsiService/active-devices-test.js b/test/unit/ngsiv2/ngsiService/active-devices-test.js
index 92594925a..ecf954527 100644
--- a/test/unit/ngsiv2/ngsiService/active-devices-test.js
+++ b/test/unit/ngsiv2/ngsiService/active-devices-test.js
@@ -60,6 +60,23 @@ const iotAgentConfig = {
}
]
},
+ LightNoTimestamp: {
+ commands: [],
+ type: 'Light',
+ lazy: [
+ {
+ name: 'temperature',
+ type: 'centigrades'
+ }
+ ],
+ timestamp: false,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Hgmm'
+ }
+ ]
+ },
BrokenLight: {
commands: [],
lazy: [
@@ -139,11 +156,58 @@ const iotAgentConfig = {
}
}
]
+ },
+ StupidDevice: {
+ type: 'StupidDevice',
+ commands: [],
+ lazy: [],
+ staticAttributes: [],
+ active: [
+ {
+ name: 'type',
+ object_id: 't',
+ type: 'text'
+ },
+ {
+ name: 'id',
+ object_id: 'i',
+ type: 'text'
+ },
+ {
+ name: 'meas',
+ object_id: 'm',
+ type: 'String'
+ }
+ ]
+ },
+ StupidDevice2: {
+ type: 'StupidDevice2',
+ commands: [],
+ lazy: [],
+ staticAttributes: [],
+ active: [
+ {
+ name: 'type',
+ object_id: 'type',
+ type: 'text'
+ },
+ {
+ name: 'id',
+ object_id: 'id',
+ type: 'text'
+ },
+ {
+ name: 'meas',
+ object_id: 'meas',
+ type: 'String'
+ }
+ ]
}
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Active attributes test', function () {
@@ -176,10 +240,9 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -192,22 +255,53 @@ describe('NGSI-v2 - Active attributes test', function () {
done();
});
});
+ });
- it('should ignore if wrong type or id are target atributes in the context broker', function (done) {
- const wrongvalues = [
+ describe('When the IoT Agent receives new information and the timestamp flag is on', function () {
+ let modifiedValues;
+
+ beforeEach(function (done) {
+ const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
+
+ modifiedValues = [
{
- name: 'type',
- type: 'string',
- value: 'wrongtype'
+ name: 'state',
+ type: 'boolean',
+ value: true
},
{
- name: 'id',
- type: 'string',
- value: 'wrongid'
+ name: 'dimming',
+ type: 'number',
+ value: 87
}
];
- iotAgentLib.update('light1', 'Light', '', wrongvalues.concat(values), function (error) {
+ timekeeper.freeze(time);
+
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json')
+ )
+ .reply(204);
+
+ iotAgentConfig.timestamp = true;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ afterEach(function (done) {
+ delete iotAgentConfig.timestamp;
+ timekeeper.reset();
+
+ done();
+ });
+
+ it('should add the timestamp to the entity and all the attributes', function (done) {
+ iotAgentLib.update('light1', 'Light', '', modifiedValues, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
@@ -215,12 +309,10 @@ describe('NGSI-v2 - Active attributes test', function () {
});
});
- describe('When the IoT Agent receives new information and the timestamp flag is on', function () {
+ describe('When the IoT Agent receives new information and the timestamp flag is false', function () {
let modifiedValues;
beforeEach(function (done) {
- const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
-
modifiedValues = [
{
name: 'state',
@@ -234,21 +326,72 @@ describe('NGSI-v2 - Active attributes test', function () {
}
];
- timekeeper.freeze(time);
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalse.json'
+ )
+ )
+ .reply(204);
+
+ iotAgentConfig.timestamp = false;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ afterEach(function (done) {
+ delete iotAgentConfig.timestamp;
+
+ done();
+ });
+
+ it('should not add the timestamp to the entity and the attributes', function (done) {
+ iotAgentLib.update('lightNoTimestamp1', 'LightNoTimestamp', '', modifiedValues, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+ describe('When the IoT Agent receives new information and the timestamp flag is false but the measure contains timeInstant', function () {
+ let modifiedValues;
+
+ beforeEach(function (done) {
+ const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
+
+ modifiedValues = [
+ {
+ name: 'state',
+ type: 'boolean',
+ value: true
+ },
+ {
+ name: 'TimeInstant',
+ type: 'DateTime',
+ value: '2015-12-14T08:06:01.468Z'
+ }
+ ];
+
+ timekeeper.freeze(time);
nock.cleanAll();
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json')
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalseTimeInstant.json'
+ )
)
- .query({ type: 'Light' })
.reply(204);
- iotAgentConfig.timestamp = true;
+ iotAgentConfig.timestamp = false;
iotAgentLib.activate(iotAgentConfig, done);
});
@@ -259,8 +402,8 @@ describe('NGSI-v2 - Active attributes test', function () {
done();
});
- it('should add the timestamp to the entity and all the attributes', function (done) {
- iotAgentLib.update('light1', 'Light', '', modifiedValues, function (error) {
+ it('should add the timestamp attribute to the entity but not as attribute metadata', function (done) {
+ iotAgentLib.update('lightNoTimestamp1', 'LightNoTimestamp', '', modifiedValues, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
@@ -333,12 +476,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json'
)
)
- .query({ type: 'Light' })
.reply(204);
iotAgentConfig.timestamp = true;
@@ -361,7 +503,7 @@ describe('NGSI-v2 - Active attributes test', function () {
});
});
- describe('When the IoT Agent receives new information, the timestamp flag is onand timezone is defined', function () {
+ describe('When the IoT Agent receives new information, the timestamp flag is on and timezone is defined', function () {
let modifiedValues;
beforeEach(function (done) {
@@ -388,12 +530,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json'
)
)
- .query({ type: 'Light' })
.reply(204);
iotAgentConfig.timestamp = true;
@@ -445,12 +586,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json'
)
)
- .query({ type: 'Light' })
.reply(204);
iotAgentConfig.timestamp = true;
@@ -500,12 +640,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json'
)
)
- .query({ type: 'Light' })
.reply(204);
iotAgentConfig.timestamp = true;
@@ -555,10 +694,9 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
)
- .query({ type: 'Light' })
.reply(
413,
utils.readExampleFile('./test/unit/ngsiv2/examples/contextResponses/updateContext1Failed.json')
@@ -588,10 +726,9 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
)
- .query({ type: 'Light' })
.reply(
400,
utils.readExampleFile('./test/unit/ngsiv2/examples/contextResponses/updateContext2Failed.json')
@@ -618,10 +755,9 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
)
- .query({ type: 'Light' })
.reply(
500,
utils.readExampleFile('./test/unit/ngsiv2/examples/contextResponses/updateContext2Failed.json')
@@ -651,10 +787,9 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/humSensor/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext5.json')
)
- .query({ type: 'Humidity' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -685,12 +820,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/motion1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json'
)
)
- .query({ type: 'Motion' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -721,12 +855,11 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/lamp1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json'
)
)
- .query({ type: 'Lamp' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -740,7 +873,7 @@ describe('NGSI-v2 - Active attributes test', function () {
});
});
- describe('When the IoT Agent receives new information from a device and the appendMode flag is on', function () {
+ describe('When the IoT Agent receives new information from a device', function () {
beforeEach(function (done) {
nock.cleanAll();
@@ -748,19 +881,15 @@ describe('NGSI-v2 - Active attributes test', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
)
- .query({ type: 'Light' })
.reply(204);
- iotAgentConfig.appendMode = true;
iotAgentLib.activate(iotAgentConfig, done);
});
afterEach(function (done) {
- iotAgentConfig.appendMode = false;
-
done();
});
@@ -773,6 +902,166 @@ describe('NGSI-v2 - Active attributes test', function () {
});
});
+ describe('When the IoT Agent receives autoprovisioned id and type measures', function () {
+ const valuesIdType = [
+ {
+ name: 'id',
+ type: 'aTypeProvidedByIoTACodeCallingUpdateOnLib1',
+ value: 'idIoTA'
+ },
+ {
+ name: 'type',
+ type: 'aTypeProvidedByIoTACodeCallingUpdateOnLib2',
+ value: 'typeIoTA'
+ },
+ {
+ name: 'm',
+ type: 'aTypeProvidedByIoTACodeCallingUpdateOnLib3',
+ value: 'measIoTA'
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ // Note that in the case of measure_id and measure_type the type provided by the IOTA when calling iotAgentLib.update()
+ // is used (thus ignoring the one of the StupidDevice group for id or type, which is 'text') but in the case of measIoTA the type provided in the
+ // provisioning ('String') is used
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'stupiddevice1',
+ type: 'StupidDevice',
+ meas: {
+ value: 'measIoTA',
+ type: 'String'
+ },
+ measure_id: {
+ type: 'aTypeProvidedByIoTACodeCallingUpdateOnLib1',
+ value: 'idIoTA'
+ },
+ measure_type: {
+ type: 'aTypeProvidedByIoTACodeCallingUpdateOnLib2',
+ value: 'typeIoTA'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should not affect to the real ID and Type to store in the context broker', function (done) {
+ iotAgentLib.update('stupiddevice1', 'StupidDevice', '', valuesIdType, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When the IoT Agent receives provisioned id and type measures with different object_id names', function () {
+ const valuesIdType2 = [
+ {
+ name: 'i',
+ type: 'text',
+ value: 'idIoTA2'
+ },
+ {
+ name: 't',
+ type: 'text',
+ value: 'typeIoTA2'
+ },
+ {
+ name: 'm',
+ type: 'text',
+ value: 'measIoTA2'
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'stupiddevice2',
+ type: 'StupidDevice',
+ meas: {
+ value: 'measIoTA2',
+ type: 'String'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should not affect to the real ID and Type to store in the context broker', function (done) {
+ iotAgentLib.update('stupiddevice2', 'StupidDevice', '', valuesIdType2, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When the IoT Agent receives provisioned id and type measures with the same object_id name', function () {
+ const valuesIdType3 = [
+ {
+ name: 'id',
+ type: 'text',
+ value: 'idIoTA'
+ },
+ {
+ name: 'type',
+ type: 'text',
+ value: 'typeIoTA'
+ },
+ {
+ name: 'meas',
+ type: 'text',
+ value: 'measIoTA'
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'stupiddevice3',
+ type: 'StupidDevice2',
+ meas: {
+ value: 'measIoTA',
+ type: 'String'
+ },
+ measure_id: {
+ value: 'idIoTA',
+ type: 'text'
+ },
+ measure_type: {
+ value: 'typeIoTA',
+ type: 'text'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should not affect to the real ID and Type to store in the context broker', function (done) {
+ iotAgentLib.update('stupiddevice3', 'StupidDevice2', '', valuesIdType3, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
describe('When the IoT Agent receives new information from a device and CBis defined using environment variables', function () {
beforeEach(function (done) {
process.env.IOTA_CB_HOST = 'cbhost';
@@ -782,11 +1071,10 @@ describe('NGSI-v2 - Active attributes test', function () {
contextBrokerMock = nock('http://cbhost:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .patch(
- '/v2/entities/light1/attrs',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext.json')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext6.json')
)
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
diff --git a/test/unit/ngsiv2/ngsiService/geoproperties-test.js b/test/unit/ngsiv2/ngsiService/geoproperties-test.js
deleted file mode 100644
index 116f29ba1..000000000
--- a/test/unit/ngsiv2/ngsiService/geoproperties-test.js
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-// FIXME: #1012
-//const utils = require('../../../tools/utils');
-//const should = require('should');
-const logger = require('logops');
-//const nock = require('nock');
-//let contextBrokerMock;
-//const iotAgentConfig = {
-// autocast: true,
-// contextBroker: {
-// host: '192.168.1.1',
-// port: '1026',
-// ngsiVersion: 'v2'
-// },
-// server: {
-// port: 4041
-// },
-// types: {
-// Light: {
-// commands: [],
-// type: 'Light',
-// active: [
-// {
-// name: 'location',
-// type: 'geo:json'
-// }
-// ]
-// }
-// },
-// service: 'smartgondor',
-// subservice: 'gardens',
-// providerUrl: 'http://smartgondor.com'
-//};
-
-describe('NGSI-v2 - Geo-JSON types autocast test', function () {
- beforeEach(function () {
- logger.setLevel('FATAL');
- });
-
- afterEach(function (done) {
- iotAgentLib.deactivate(done);
- });
-
- // FIXME: #1012
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with geo:json type and String value',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'geo:json',
- // value: '23,12.5'
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with geo:json type and GeoJSON object value',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'geo:json',
- // value: {
- // type: 'Point',
- // coordinates: [23, 12.5]
- // }
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe('When the IoT Agent receives new geo-information from a device. Location with Point type and Array value', function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'Point',
- // value: [23, 12.5]
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties1.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // });
-
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with LineString type and Array value',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'LineString',
- // value: [
- // [23, 12.5],
- // [22, 12.5]
- // ]
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with LineString type and Array of Strings',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'LineString',
- // value: ['23,12.5', '22,12.5']
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties2.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe('When the IoT Agent receives new geo-information from a device. Location with None type', function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'None',
- // value: 'null'
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties3.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // });
-
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with Polygon type - Array of coordinates',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'Polygon',
- // value: [
- // [23, 12.5],
- // [22, 13.5],
- // [22, 13.5]
- // ]
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe(
- // 'When the IoT Agent receives new geo-information from a device.' +
- // 'Location with Polygon type - list of coordinates',
- // function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'Polygon',
- // value: '23,12.5,22,13.5,22,13.5'
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
-
- // contextBrokerMock = nock('http://192.168.1.1:1026')
- // .matchHeader('fiware-service', 'smartgondor')
- // .post(
- // '/v2/entities/light1/attrs',
- // utils.readExampleFile(
- // './test/unit/ngsiv2/examples/contextRequests/updateContextGeoproperties4.json'
- // )
- // )
- // .query({ type: 'Light' })
- // .reply(204);
-
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should change the value of the corresponding attribute in the context broker', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.not.exist(error);
- // contextBrokerMock.done();
- // done();
- // });
- // });
- // }
- // );
-
- // describe('When the IoT Agent receives new geo-information from a device. Location with a missing latitude', function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'Point',
- // value: '23,12.5,22,13.5,22'
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should throw a BadGeocoordinates Error', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.exist(error);
- // done();
- // });
- // });
- // });
-
- // describe('When the IoT Agent receives new geo-information from a device. Location invalid coordinates', function () {
- // const values = [
- // {
- // name: 'location',
- // type: 'Point',
- // value: '2016-04-30Z'
- // }
- // ];
-
- // beforeEach(function (done) {
- // nock.cleanAll();
- // iotAgentLib.activate(iotAgentConfig, done);
- // });
-
- // it('should throw a BadGeocoordinates Error', function (done) {
- // iotAgentLib.update('light1', 'Light', '', values, function (error) {
- // should.exist(error);
- // done();
- // });
- // });
- // });
-});
diff --git a/test/unit/ngsiv2/ngsiService/staticAttributes-test.js b/test/unit/ngsiv2/ngsiService/staticAttributes-test.js
index fb1cf84e2..bcd434b82 100644
--- a/test/unit/ngsiv2/ngsiService/staticAttributes-test.js
+++ b/test/unit/ngsiv2/ngsiService/staticAttributes-test.js
@@ -75,12 +75,109 @@ const iotAgentConfig = {
type: 'type4'
}
]
+ },
+ Light_Explicit_True: {
+ commands: [],
+ type: 'Light_Explicit_True',
+ explicitAttrs: true,
+ timestamp: false,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Number'
+ }
+ ],
+ staticAttributes: [
+ {
+ name: 'attr1',
+ type: 'Text',
+ value: 'Static Text'
+ },
+ {
+ name: 'attr2',
+ type: 'Number',
+ value: 123
+ }
+ ]
+ },
+ Light_Explicit_False: {
+ commands: [],
+ type: 'Light_Explicit_False',
+ explicitAttrs: false,
+ timestamp: false,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Number'
+ }
+ ],
+ staticAttributes: [
+ {
+ name: 'attr1',
+ type: 'Text',
+ value: 'Static Text'
+ },
+ {
+ name: 'attr2',
+ type: 'Number',
+ value: 123
+ }
+ ]
+ },
+ Light_Explicit_Array: {
+ commands: [],
+ type: 'Light_Explicit_Array',
+ explicitAttrs: '[ "pressure", "attr1" ]',
+ timestamp: false,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Number'
+ }
+ ],
+ staticAttributes: [
+ {
+ name: 'attr1',
+ type: 'Text',
+ value: 'Static Text'
+ },
+ {
+ name: 'attr2',
+ type: 'Number',
+ value: 123
+ }
+ ]
+ },
+ Light_Explicit_Expression: {
+ commands: [],
+ type: 'Light_Explicit_Expression',
+ explicitAttrs: ' pressure ? [ "pressure", "attr1" ] : [ "attr2" ] ',
+ timestamp: false,
+ active: [
+ {
+ name: 'pressure',
+ type: 'Number'
+ }
+ ],
+ staticAttributes: [
+ {
+ name: 'attr1',
+ type: 'Text',
+ value: 'Static Text'
+ },
+ {
+ name: 'attr2',
+ type: 'Number',
+ value: 123
+ }
+ ]
}
},
timestamp: true,
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Static attributes test', function () {
@@ -112,20 +209,18 @@ describe('NGSI-v2 - Static attributes test', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Light' })
+ .post('/v2/entities?options=upsert,flowControl')
.times(4)
.reply(204)
- .post('/v2/entities/light1/attrs', function (body) {
+ .post('/v2/entities?options=upsert,flowControl', function (body) {
let metadatas = 0;
for (const i in body) {
if (body[i].metadata) {
metadatas += Object.keys(body[i].metadata).length;
}
}
- return metadatas === Object.keys(body).length - 1;
+ return metadatas === Object.keys(body).length - 1 - 2;
})
- .query({ type: 'Light' })
.reply(204);
iotAgentLib.activate(iotAgentConfig, done);
@@ -148,4 +243,175 @@ describe('NGSI-v2 - Static attributes test', function () {
);
});
});
+
+ describe('When using explicitAttrs true', function () {
+ const newValues = [
+ {
+ name: 'pressure',
+ type: 'Number',
+ value: 321
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'light2',
+ type: 'Light_Explicit_True',
+ pressure: {
+ value: 321,
+ type: 'Number'
+ },
+ attr1: {
+ value: 'Static Text',
+ type: 'Text'
+ },
+ attr2: {
+ value: 123,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should include all the statics', function (done) {
+ iotAgentLib.update('light2', 'Light_Explicit_True', '', newValues, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using explicitAttrs false', function () {
+ const newValues = [
+ {
+ name: 'pressure',
+ type: 'Number',
+ value: 321
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'light2',
+ type: 'Light_Explicit_True',
+ pressure: {
+ value: 321,
+ type: 'Number'
+ },
+ attr1: {
+ value: 'Static Text',
+ type: 'Text'
+ },
+ attr2: {
+ value: 123,
+ type: 'Number'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should include all the statics', function (done) {
+ iotAgentLib.update('light2', 'Light_Explicit_True', '', newValues, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using explicitAttrs as array', function () {
+ const newValues = [
+ {
+ name: 'pressure',
+ type: 'Number',
+ value: 321
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'light2',
+ type: 'Light_Explicit_Array',
+ pressure: {
+ value: 321,
+ type: 'Number'
+ },
+ attr1: {
+ value: 'Static Text',
+ type: 'Text'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should include only statics defined into the array', function (done) {
+ iotAgentLib.update('light2', 'Light_Explicit_Array', '', newValues, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When using explicitAttrs as expression', function () {
+ const newValues = [
+ {
+ name: 'pressure',
+ type: 'Number',
+ value: 321
+ }
+ ];
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', 'gardens')
+ .post('/v2/entities?options=upsert,flowControl', {
+ id: 'light2',
+ type: 'Light_Explicit_Expression',
+ pressure: {
+ value: 321,
+ type: 'Number'
+ },
+ attr1: {
+ value: 'Static Text',
+ type: 'Text'
+ }
+ })
+ .reply(204);
+
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+
+ it('should include statics as the result on the expression', function (done) {
+ iotAgentLib.update('light2', 'Light_Explicit_Expression', '', newValues, function (error) {
+ should.not.exist(error);
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
});
diff --git a/test/unit/ngsiv2/ngsiService/subscriptions-test.js b/test/unit/ngsiv2/ngsiService/subscriptions-test.js
deleted file mode 100644
index 413fb5d3a..000000000
--- a/test/unit/ngsiv2/ngsiService/subscriptions-test.js
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- logLevel: 'FATAL',
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'v2'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {},
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-v2 - Subscription tests', function () {
- beforeEach(function (done) {
- const optionsProvision = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- nock.cleanAll();
-
- iotAgentLib.activate(iotAgentConfig, function () {
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(204);
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- iotAgentLib.clearAll(function () {
- request(optionsProvision, function (error, result, body) {
- done();
- });
- });
- });
- });
-
- afterEach(function (done) {
- nock.cleanAll();
- iotAgentLib.setNotificationHandler();
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
-
- describe('When a client invokes the subscribe() function for device', function () {
- it('should send the appropriate request to the Context Broker', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- should.not.exist(error);
-
- contextBrokerMock.done();
-
- done();
- });
- });
- });
- it('should store the subscription ID in the Device Registry', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- should.not.exist(error);
- should.exist(device);
- should.exist(device.subscriptions);
- device.subscriptions.length.should.equal(1);
- device.subscriptions[0].id.should.equal('51c0ac9ed714fb3b37d7d5a8');
- device.subscriptions[0].triggers[0].should.equal('attr_name');
- done();
- });
- });
- });
- });
- });
- describe('When a client invokes the unsubscribe() function for an entity', function () {
- beforeEach(function (done) {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .delete('/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8', '')
- .reply(204);
-
- done();
- });
- it('should delete the subscription from the CB', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
- });
- it('should remove the id from the subscriptions array', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.unsubscribe(device, '51c0ac9ed714fb3b37d7d5a8', function (error) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- should.not.exist(error);
- should.exist(device);
- should.exist(device.subscriptions);
- device.subscriptions.length.should.equal(0);
- done();
- });
- });
- });
- });
- });
- });
- describe('When a client removes a device from the registry', function () {
- beforeEach(function (done) {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .delete('/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8', '')
- .reply(204);
-
- done();
- });
-
- it('should delete the subscription from the CB', function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- iotAgentLib.unregister(device.id, 'smartgondor', '/gardens', function (error) {
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
- });
- describe('When a new notification comes to the IoTAgent', function () {
- beforeEach(function (done) {
- iotAgentLib.getDevice('MicroLight1', 'smartgondor', '/gardens', function (error, device) {
- iotAgentLib.subscribe(device, ['attr_name'], null, function (error) {
- done();
- });
- });
- });
-
- it('should invoke the user defined callback', function (done) {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/ngsiv2/examples/subscriptionRequests/simpleNotification.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(notificationOptions, function (error, response, body) {
- should.not.exist(error);
- executedHandler.should.equal(true);
-
- done();
- });
- });
- it('should invoke all the notification middlewares before the user defined callback', function (done) {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/ngsiv2/examples/subscriptionRequests/simpleNotification.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedMiddlewares = false;
- let executedHandler = false;
- let modifiedData = false;
-
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- modifiedData = notification.length === 2;
- callback();
- }
-
- function mockedMiddleware(device, notification, callback) {
- executedMiddlewares = true;
- notification.push({
- name: 'middlewareAttribute',
- type: 'middlewareType',
- value: 'middlewareValue'
- });
-
- callback(null, device, notification);
- }
-
- iotAgentLib.addNotificationMiddleware(mockedMiddleware);
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(notificationOptions, function (error, response, body) {
- should.not.exist(error);
- executedHandler.should.equal(true);
- executedMiddlewares.should.equal(true);
- modifiedData.should.equal(true);
- done();
- });
- });
- it('should get the correspondent device information', function (done) {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/ngsiv2/examples/subscriptionRequests/simpleNotification.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let rightFields = false;
-
- function mockedHandler(device, data, callback) {
- if (
- device &&
- device.id === 'MicroLight1' &&
- device.name === 'FirstMicroLight' &&
- data &&
- data.length === 1 &&
- data[0].name === 'attr_name'
- ) {
- rightFields = true;
- }
-
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(notificationOptions, function (error, response, body) {
- should.not.exist(error);
- rightFields.should.equal(true);
-
- done();
- });
- });
- });
- describe('When a wrong notification arrives at the IOTA', function () {
- it('should not call the handler', function (done) {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/ngsiv2/examples/subscriptionRequests/errorNotification.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(notificationOptions, function (error, response, body) {
- should.not.exist(error);
- executedHandler.should.equal(false);
-
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsiv2/plugins/alias-plugin_test.js b/test/unit/ngsiv2/plugins/alias-plugin_test.js
index 95cb6afcf..7bf354695 100644
--- a/test/unit/ngsiv2/plugins/alias-plugin_test.js
+++ b/test/unit/ngsiv2/plugins/alias-plugin_test.js
@@ -32,7 +32,6 @@ const logger = require('logops');
const nock = require('nock');
let contextBrokerMock;
const iotAgentConfig = {
- autocast: true,
contextBroker: {
host: '192.168.1.1',
port: '1026',
@@ -90,7 +89,8 @@ const iotAgentConfig = {
{
object_id: 'al',
name: 'keep_alive',
- type: 'None'
+ type: 'None',
+ skipValue: 'null passes'
},
{
object_id: 'ta',
@@ -107,13 +107,13 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Attribute alias plugin', function () {
beforeEach(function (done) {
logger.setLevel('FATAL');
-
iotAgentLib.activate(iotAgentConfig, function () {
iotAgentLib.clearAll(function () {
done();
@@ -131,7 +131,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 't',
type: 'centigrades',
- value: '52'
+ value: 52
},
{
name: 'p',
@@ -147,14 +147,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -167,7 +166,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'l',
type: 'lums',
- value: '9'
+ value: 9
}
];
@@ -178,14 +177,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -198,7 +196,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'ut',
type: 'Number',
- value: '99823423'
+ value: 99823423
}
];
@@ -209,10 +207,9 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json')
)
- .query({ type: 'Light' })
.reply(204);
});
@@ -230,7 +227,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'ut',
type: 'Number',
- value: '99823423'
+ value: 99823423
}
];
@@ -241,14 +238,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -262,7 +258,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'ap',
type: 'Number',
- value: '0.45'
+ value: 0.45
}
];
@@ -273,14 +269,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -305,14 +300,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -326,25 +320,23 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'al',
type: 'None',
- value: 'null'
+ value: null
}
];
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -358,7 +350,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'ta',
type: 'Array',
- value: '["iot","device"]'
+ value: ['iot', 'device']
}
];
@@ -369,14 +361,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -390,7 +381,7 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
{
name: 'c',
type: 'Object',
- value: '{"firmware": {"version": "1.1.0","hash": "cf23df2207d99a74fbe169e3eba035e633b65d94"}}'
+ value: { firmware: { version: '1.1.0', hash: 'cf23df2207d99a74fbe169e3eba035e633b65d94' } }
}
];
@@ -401,14 +392,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
@@ -433,14 +423,13 @@ describe('NGSI-v2 - Attribute alias plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/entities/light1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json')
)
- .query({ type: 'Light' })
.reply(204);
});
- it('should rename the attributes as expected by the alias mappings and cast values to JSON native types', function (done) {
+ it('should rename the attributes as expected by the alias mappings', function (done) {
iotAgentLib.update('light1', 'Light', '', values, function (error) {
should.not.exist(error);
contextBrokerMock.done();
diff --git a/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js b/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js
deleted file mode 100644
index 30015de6d..000000000
--- a/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-
-let contextBrokerMock;
-const iotAgentConfig = {
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'v2'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {},
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-v2 - Bidirectional data plugin', function () {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function (done) {
- logger.setLevel('FATAL');
-
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- iotAgentLib.addDeviceProvisionMiddleware(iotAgentLib.dataPlugins.bidirectionalData.deviceProvision);
- iotAgentLib.addConfigurationProvisionMiddleware(
- iotAgentLib.dataPlugins.bidirectionalData.groupProvision
- );
- iotAgentLib.addNotificationMiddleware(iotAgentLib.dataPlugins.bidirectionalData.notification);
- done();
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
-
- describe('When a new provisioning request arrives to the IoTA with bidirectionality', function () {
- beforeEach(function () {
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(options, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When a device with bidirectionality subscriptions is removed', function () {
- const deleteRequest = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
- method: 'DELETE',
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .delete('/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8', '')
- .reply(204);
- });
-
- it('should remove its subscriptions from the Context Broker', function (done) {
- request(options, function (error, response, body) {
- request(deleteRequest, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
-
- describe('When a notification arrives for a bidirectional attribute', function () {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should execute the original handler', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- executedHandler.should.equal(true);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return a 200 OK', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- response.statusCode.should.equal(200);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- contextBrokerMock.done();
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
-
- describe('When a notification with metadata arrives for a bidirectional attribute', function () {
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- let executedHandler = false;
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should execute the original handler', function (done) {
- function mockedHandler(device, notification, callback) {
- notification[0].name.should.equal('location');
- notification[0].value.should.equal('12.4, -9.6');
- notification[0].metadata.qos.value.should.equal(1);
-
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- executedHandler.should.equal(true);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return a 200 OK', function (done) {
- function mockedHandler(device, notification, callback) {
- executedHandler = true;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- response.statusCode.should.equal(200);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(options, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- contextBrokerMock.done();
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
-
- describe('When a new Group provisioning request arrives with bidirectional attributes', function () {
- const provisionGroup = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const provisionDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(provisionGroup, function (error, response, body) {
- request(provisionDevice, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
- });
-
- describe('When a notification arrives for a bidirectional attribute in a Configuration Group', function () {
- const provisionGroup = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json'),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const notificationOptions = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
- const provisionDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function () {
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- afterEach(function () {
- iotAgentLib.setNotificationHandler();
- });
-
- it('should return the transformed values', function (done) {
- let transformedHandler = false;
-
- function mockedHandler(device, values, callback) {
- let latitudeFound = false;
- let longitudeFound = false;
-
- for (let i = 0; i < values.length; i++) {
- if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
- latitudeFound = true;
- }
-
- if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
- longitudeFound = true;
- }
- }
-
- transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
- callback();
- }
-
- iotAgentLib.setNotificationHandler(mockedHandler);
-
- request(provisionGroup, function (error, response, body) {
- request(provisionDevice, function (error, response, body) {
- request(notificationOptions, function (error, response, body) {
- transformedHandler.should.equal(true);
- done();
- });
- });
- });
- });
- });
-});
-
-describe('NGSI-v2 - Bidirectional data plugin and CB is defined using environment variables', function () {
- const options = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile(
- './test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json'
- ),
- headers: {
- 'fiware-service': 'smartgondor',
- 'fiware-servicepath': '/gardens'
- }
- };
-
- beforeEach(function (done) {
- logger.setLevel('FATAL');
- process.env.IOTA_CB_HOST = 'cbhost';
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- iotAgentLib.addDeviceProvisionMiddleware(iotAgentLib.dataPlugins.bidirectionalData.deviceProvision);
- iotAgentLib.addConfigurationProvisionMiddleware(
- iotAgentLib.dataPlugins.bidirectionalData.groupProvision
- );
- iotAgentLib.addNotificationMiddleware(iotAgentLib.dataPlugins.bidirectionalData.notification);
- done();
- });
- });
- });
-
- afterEach(function (done) {
- process.env.IOTA_CB_HOST = '';
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
-
- describe('When a new provisioning request arrives to the IoTA with bidirectionality', function () {
- beforeEach(function () {
- contextBrokerMock = nock('http://cbhost:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/subscriptions',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
- )
- )
- .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
- )
- .reply(204);
- });
-
- it('should subscribe to the modification of the combined attribute with all the variables', function (done) {
- request(options, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js b/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js
deleted file mode 100644
index 93daab806..000000000
--- a/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'v2'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- lazy: [
- {
- name: 'temperature',
- type: 'centigrades'
- }
- ],
- active: [
- {
- name: 'pressure',
- type: 'Hgmm'
- }
- ]
- },
- BrokenLight: {
- commands: [],
- lazy: [
- {
- name: 'temperature',
- type: 'centigrades'
- }
- ],
- active: [
- {
- name: 'pressure',
- type: 'Hgmm'
- }
- ]
- },
- Termometer: {
- type: 'Termometer',
- commands: [],
- lazy: [
- {
- name: 'temp',
- type: 'kelvin'
- }
- ],
- active: []
- },
- Humidity: {
- type: 'Humidity',
- cbHost: 'http://192.168.1.1:3024',
- commands: [],
- lazy: [],
- active: [
- {
- name: 'humidity',
- type: 'percentage'
- }
- ]
- },
- Motion: {
- type: 'Motion',
- commands: [],
- lazy: [],
- staticAttributes: [
- {
- name: 'location',
- type: 'Vector',
- value: '(123,523)'
- }
- ],
- active: [
- {
- name: 'humidity',
- type: 'percentage'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-v2 - Timestamp compression plugin', function () {
- beforeEach(function (done) {
- logger.setLevel('FATAL');
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- done();
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
- describe('When an update comes with a timestamp through the plugin', function () {
- const values = [
- {
- name: 'state',
- type: 'Boolean',
- value: 'true'
- },
- {
- name: 'TheTargetValue',
- type: 'DateTime',
- value: '20071103T131805'
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json'
- )
- )
- .query({ type: 'Light' })
- .reply(204);
- });
-
- it('should return an entity with all its timestamps expanded to have separators', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-
- describe('When an update comes with a timestamp through the plugin with metadata.', function () {
- const values = [
- {
- name: 'state',
- type: 'Boolean',
- value: true,
- metadata: {
- TimeInstant: {
- type: 'DateTime',
- value: '20071103T131805'
- }
- }
- },
- {
- name: 'TheTargetValue',
- type: 'DateTime',
- value: '20071103T131805'
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/light1/attrs',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json'
- )
- )
- .query({ type: 'Light' })
- .reply(204);
- });
-
- it('should return an entity with all its timestamps expanded to have separators', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsiv2/plugins/custom-plugin_test.js b/test/unit/ngsiv2/plugins/custom-plugin_test.js
index 7cbe03dbe..f5d6bb8c2 100644
--- a/test/unit/ngsiv2/plugins/custom-plugin_test.js
+++ b/test/unit/ngsiv2/plugins/custom-plugin_test.js
@@ -57,7 +57,8 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Custom plugin', function () {
@@ -111,8 +112,7 @@ describe('NGSI-v2 - Custom plugin', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities/light1/attrs')
- .query({ type: 'Light' })
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
});
diff --git a/test/unit/ngsiv2/plugins/multientity-plugin_test.js b/test/unit/ngsiv2/plugins/multientity-plugin_test.js
index a1f640d9c..246ca6dac 100644
--- a/test/unit/ngsiv2/plugins/multientity-plugin_test.js
+++ b/test/unit/ngsiv2/plugins/multientity-plugin_test.js
@@ -272,7 +272,7 @@ const iotAgentConfig = {
commands: [],
type: 'WeatherStation',
lazy: [],
- static: [
+ staticAttributes: [
{
name: 'st1',
type: 'Number',
@@ -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',
@@ -508,7 +540,6 @@ const iotAgentConfig = {
},
GPS1: {
commands: [],
- expressionLanguage: 'jexl',
type: 'GPS',
lazy: [],
active: [
@@ -523,7 +554,13 @@ const iotAgentConfig = {
expression: 'z+1'
},
{
- name: 'nonexpectedAtt',
+ name: 'alsoexpectedAtt',
+ type: 'number',
+ expression: 'w+1',
+ skipValue: 'loquesea'
+ },
+ {
+ name: 'nonexpectedAttByDefaultSkipValue',
type: 'number',
expression: 'w+1'
},
@@ -565,7 +602,7 @@ const iotAgentConfig = {
object_id: 'y'
}
],
- static: [
+ staticAttributes: [
{
name: 'bar',
type: 'text',
@@ -596,12 +633,13 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function (done) {
- logger.setLevel('DEBUG');
+ logger.setLevel('FATAL');
iotAgentLib.activate(iotAgentConfig, function () {
iotAgentLib.clearAll(function () {
@@ -637,7 +675,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json'
)
@@ -675,7 +713,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin17.json'
)
@@ -703,12 +741,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json'
)
@@ -741,12 +778,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json'
)
@@ -780,12 +816,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json'
)
@@ -828,7 +863,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json'
)
@@ -871,7 +906,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin9.json'
)
@@ -914,7 +949,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10.json'
)
@@ -962,7 +997,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin12.json'
)
@@ -1010,7 +1045,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin13.json'
)
@@ -1043,7 +1078,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin14.json'
)
@@ -1086,7 +1121,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10.json'
)
@@ -1119,9 +1154,9 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10.json'
+ './test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10b.json'
)
)
.reply(204);
@@ -1143,7 +1178,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
// Updated test same case that updateContextMultientityPlugin4.json
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json'
@@ -1152,7 +1187,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.reply(204);
});
- describe('When an update comes for a multientity whith a wrong mapping)', function () {
+ describe('When an update comes for a multientity whith a wrong mapping', function () {
const values = [
{
name: 'v',
@@ -1173,12 +1208,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json'
)
@@ -1216,7 +1250,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json'
)
@@ -1258,7 +1292,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json'
)
@@ -1300,7 +1334,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json'
)
@@ -1342,7 +1376,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json'
)
@@ -1373,12 +1407,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json'
)
@@ -1425,12 +1458,11 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
beforeEach(function () {
nock.cleanAll();
-
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json'
)
@@ -1469,7 +1501,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json'
)
@@ -1489,8 +1521,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 () {
@@ -1540,7 +1571,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/op/update', function (body) {
+ .post('/v2/op/update?options=flowControl', function (body) {
const expectedBody = utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json'
);
@@ -1561,7 +1592,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
delete expectedBody.entities[1].TimeInstant;
delete expectedBody.entities[1].humidity.metadata.TimeInstant;
- return JSON.stringify(body) === JSON.stringify(expectedBody);
+ return utils.deepEqual(body, expectedBody);
}
return false;
})
@@ -1578,28 +1609,28 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/op/update', function (body) {
+ .post('/v2/op/update?options=flowControl', function (body) {
const expectedBody = utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json'
);
// Note that TimeInstant fields are not included in the json used by this mock as they are dynamic
// fields. The following code just checks that TimeInstant fields are present.
- if (!body.entities[1].TimeInstant || !body.entities[1].humidity.metadata.TimeInstant) {
+ if (!body.entities[0].TimeInstant || !body.entities[0].humidity.metadata.TimeInstant) {
return false;
}
- const timeInstantEntity2 = body.entities[1].TimeInstant;
- const timeInstantAtt = body.entities[1].humidity.metadata.TimeInstant;
+ const timeInstantEntity2 = body.entities[0].TimeInstant;
+ const timeInstantAtt = body.entities[0].humidity.metadata.TimeInstant;
if (
moment(timeInstantEntity2, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid &&
moment(timeInstantAtt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid
) {
- delete body.entities[1].TimeInstant;
- delete body.entities[1].humidity.metadata.TimeInstant;
+ delete body.entities[0].TimeInstant;
+ delete body.entities[0].humidity.metadata.TimeInstant;
- delete expectedBody.entities[1].TimeInstant;
- delete expectedBody.entities[1].humidity.metadata.TimeInstant;
- return JSON.stringify(body) === JSON.stringify(expectedBody);
+ delete expectedBody.entities[0].TimeInstant;
+ delete expectedBody.entities[0].humidity.metadata.TimeInstant;
+ return utils.deepEqual(body, expectedBody);
}
return false;
})
@@ -1617,7 +1648,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- '/v2/op/update',
+ '/v2/op/update?options=flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json'
)
@@ -1637,8 +1668,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();
@@ -1650,7 +1680,6 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
describe('NGSI-v2 - Multi-entity plugin is executed for a command update for a regular entity ', function () {
beforeEach(function (done) {
logger.setLevel('FATAL');
-
iotAgentConfig.timestamp = true;
const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
timekeeper.freeze(time);
@@ -1673,8 +1702,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed for a command update for a r
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
.post(
- //'/v2/op/update',
- '/v2/entities/sensorCommand/attrs?type=SensorCommand',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json'
)
diff --git a/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js b/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js
deleted file mode 100644
index d069d53ba..000000000
--- a/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::daniel.moranjimenez@telefonica.com
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const should = require('should');
-const logger = require('logops');
-const nock = require('nock');
-let contextBrokerMock;
-const iotAgentConfig = {
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'v2'
- },
- server: {
- port: 4041,
- host: 'localhost'
- },
- types: {
- Light: {
- commands: [],
- type: 'Light',
- lazy: [
- {
- name: 'temperature',
- type: 'centigrades'
- }
- ],
- active: [
- {
- name: 'pressure',
- type: 'Hgmm'
- }
- ]
- }
- },
- service: 'smartgondor',
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-
-describe('NGSI-v2 - Timestamp processing plugin', function () {
- beforeEach(function (done) {
- logger.setLevel('FATAL');
-
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(function () {
- done();
- });
- });
- });
-
- afterEach(function (done) {
- iotAgentLib.clearAll(function () {
- iotAgentLib.deactivate(done);
- });
- });
- describe('When an update comes with a timestamp through the plugin', function () {
- const values = [
- {
- name: 'state',
- type: 'Boolean',
- value: true
- },
- {
- name: 'TimeInstant',
- type: 'DateTime',
- value: '2016-05-30T16:25:22.304Z'
- }
- ];
-
- beforeEach(function () {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post(
- '/v2/entities/light1/attrs',
- // this tests breaks jexlBasedTransformation-test with uses updateContextExpressionPlugin32 which do not includes Timestamp in metadata attributes
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json'
- )
- )
- .query({ type: 'Light' })
- .reply(204);
- });
-
- it('should return an entity with all its timestamps expanded to have separators', function (done) {
- iotAgentLib.update('light1', 'Light', '', values, function (error) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsiv2/provisioning/device-group-api-test.js b/test/unit/ngsiv2/provisioning/device-group-api-test.js
index ff5ee9564..e32ac35bd 100644
--- a/test/unit/ngsiv2/provisioning/device-group-api-test.js
+++ b/test/unit/ngsiv2/provisioning/device-group-api-test.js
@@ -23,6 +23,8 @@
/* eslint-disable no-unused-vars */
+// #FIXME1649: parallel tests in device-provisioning-configGroup-api_test.js.
+
const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
const _ = require('underscore');
const async = require('async');
@@ -48,7 +50,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- deviceRegistrationDuration: 'P1M'
+ deviceRegistrationDuration: 'P1M',
+ useCBflowControl: true
};
const optionsCreation = {
url: 'http://localhost:4041/iot/services',
@@ -61,6 +64,9 @@ const optionsCreation = {
entity_type: 'SensorMachine',
trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
cbHost: 'http://unexistentHost:1026',
+ transport: 'HTTP',
+ endpoint: 'http://myendpoint.com',
+ useCBflowControl: true,
commands: [
{
name: 'wheel1',
@@ -116,6 +122,19 @@ const optionsDelete = {
apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
}
};
+const optionsDeleteGroup = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'DELETE',
+ json: {},
+ headers: {
+ 'fiware-service': 'Testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
const optionsDeleteDevice = {
url: 'http://localhost:4041/iot/services',
method: 'DELETE',
@@ -136,6 +155,8 @@ const optionsUpdate = {
json: {
trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
cbHost: 'http://anotherUnexistentHost:1026',
+ transport: 'MQTT',
+ endpoint: 'http://yourendpoint.com',
commands: [
{
name: 'wheel1',
@@ -171,6 +192,49 @@ const optionsUpdate = {
apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
}
};
+const optionsUpdateGroup = {
+ url: 'http://localhost:4041/iot/services',
+ method: 'PUT',
+ json: {
+ trust: '8970A9078A803H3BL98PINEQRW8342HBAMS',
+ cbHost: 'http://anotherUnexistentHost:1026',
+ transport: 'MQTT',
+ endpoint: 'http://yourendpoint.com',
+ commands: [
+ {
+ name: 'wheel1',
+ type: 'Wheel'
+ }
+ ],
+ lazy: [
+ {
+ name: 'luminescence',
+ type: 'Lumens'
+ }
+ ],
+ attributes: [
+ {
+ name: 'status',
+ type: 'Boolean'
+ }
+ ],
+ static_attributes: [
+ {
+ name: 'identifier',
+ type: 'UUID',
+ value: 'WERTYUIOP234567890'
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'Testservice',
+ 'fiware-servicepath': '/testingPath'
+ },
+ qs: {
+ resource: '/deviceTest',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732'
+ }
+};
const optionsList = {
url: 'http://localhost:4041/iot/services',
method: 'GET',
@@ -190,6 +254,20 @@ const optionsGet = {
}
};
+// Add new options using the literal groups instead of services
+const configGroupTerm = 'groups';
+
+const newOptionsCreation = JSON.parse(JSON.stringify(optionsCreation));
+newOptionsCreation.url = newOptionsCreation.url.replace('services', configGroupTerm);
+newOptionsCreation.json[configGroupTerm] = newOptionsCreation.json.services;
+delete newOptionsCreation.json.services;
+
+const newOptionsList = JSON.parse(JSON.stringify(optionsList));
+newOptionsList.url = newOptionsList.url.replace('services', configGroupTerm);
+
+const newOptionsGet = JSON.parse(JSON.stringify(optionsGet));
+newOptionsGet.url = newOptionsGet.url.replace('services', configGroupTerm);
+
describe('NGSI-v2 - Device Group Configuration API', function () {
beforeEach(function (done) {
iotAgentLib.activate(iotAgentConfig, function () {
@@ -217,6 +295,8 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
request(optionsList, function (error, response, body) {
body.count.should.equal(1);
body.services[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ body.services[0].transport.should.equal('HTTP');
+ body.services[0].endpoint.should.equal('http://myendpoint.com');
done();
});
});
@@ -405,6 +485,33 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
});
});
});
+ describe('When a device group removal request arrives with service-header in uppercase', function () {
+ beforeEach(function (done) {
+ request(optionsCreation, done);
+ });
+
+ it('should return a 204 OK', function (done) {
+ request(optionsDeleteGroup, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ it('should remove it from the database', function (done) {
+ request(optionsDeleteGroup, function (error, response, body) {
+ request(optionsList, function (error, response, body) {
+ body.count.should.equal(0);
+ done();
+ });
+ });
+ });
+ it('should remove it from the configuration', function (done) {
+ request(optionsDeleteGroup, function (error, response, body) {
+ should.not.exist(iotAgentConfig.types.SensorMachine);
+ done();
+ });
+ });
+ });
describe('When a device group removal request arrives with device=true option', function () {
let contextBrokerMock;
@@ -414,7 +521,7 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
contextBrokerMock
@@ -432,7 +539,7 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
contextBrokerMock
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series(
@@ -664,6 +771,8 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
) {
body.services[i].cbHost.should.equal('http://anotherUnexistentHost:1026');
body.services[i].static_attributes.length.should.equal(1);
+ body.services[i].endpoint = 'http://yourendpoint.com';
+ body.services[i].transport = 'MQTT';
found = true;
}
}
@@ -696,6 +805,83 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
});
});
+ describe('When a device group update request arrives with service-header in uppercase', function () {
+ beforeEach(function (done) {
+ const optionsCreation1 = _.clone(optionsCreation);
+ const optionsCreation2 = _.clone(optionsCreation);
+ const optionsCreation3 = _.clone(optionsCreation);
+
+ optionsCreation1.json = { services: [] };
+ optionsCreation3.json = { services: [] };
+
+ optionsCreation1.json.services[0] = _.clone(optionsCreation.json.services[0]);
+ optionsCreation3.json.services[0] = _.clone(optionsCreation.json.services[0]);
+
+ optionsCreation1.json.services[0].apikey = 'qwertyuiop';
+ optionsCreation3.json.services[0].apikey = 'lkjhgfds';
+
+ async.series(
+ [
+ async.apply(request, optionsCreation1),
+ async.apply(request, optionsCreation2),
+ async.apply(request, optionsCreation3)
+ ],
+ done
+ );
+ });
+
+ it('should return a 204 OK', function (done) {
+ request(optionsUpdateGroup, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ it('should update the appropriate values in the database', function (done) {
+ request(optionsUpdateGroup, function (error, response, body) {
+ request(optionsList, function (error, response, body) {
+ let found = false;
+ body.count.should.equal(3);
+
+ for (let i = 0; i < body.services.length; i++) {
+ if (
+ body.services[i].apikey === '801230BJKL23Y9090DSFL123HJK09H324HV8732' &&
+ body.services[i].resource === '/deviceTest'
+ ) {
+ body.services[i].cbHost.should.equal('http://anotherUnexistentHost:1026');
+ body.services[i].static_attributes.length.should.equal(1);
+ found = true;
+ }
+ }
+
+ found.should.equal(true);
+ done();
+ });
+ });
+ });
+ it('should call the configuration creation handler', function (done) {
+ let handlerCalled = false;
+
+ iotAgentLib.setConfigurationHandler(function (newConfiguration, callback) {
+ should.exist(newConfiguration);
+ should.exist(callback);
+ newConfiguration.cbHost.should.equal('http://anotherUnexistentHost:1026');
+ newConfiguration.trust.should.equal('8970A9078A803H3BL98PINEQRW8342HBAMS');
+ newConfiguration.service.should.equal('Testservice');
+ newConfiguration.subservice.should.equal('/testingPath');
+ newConfiguration.resource.should.equal('/deviceTest');
+ newConfiguration.apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ handlerCalled = true;
+ callback();
+ });
+
+ request(optionsUpdateGroup, function (error, response, body) {
+ handlerCalled.should.equal(true);
+ done();
+ });
+ });
+ });
+
describe('When a device group update request arrives declaring a different service', function () {
beforeEach(function (done) {
optionsUpdate.headers['fiware-service'] = 'UnexistentService';
@@ -907,10 +1093,9 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
.matchHeader('fiware-service', 'testservice')
.matchHeader('fiware-servicepath', '/testingPath')
.post(
- '/v2/entities/machine1/attrs',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json')
)
- .query({ type: 'SensorMachine' })
.reply(204, {});
async.series([async.apply(request, optionsCreation)], done);
});
@@ -989,4 +1174,87 @@ describe('NGSI-v2 - Device Group Configuration API', function () {
});
});
});
+
+ describe('When a new device group creation request arrives with the NEW API endpoint ', function () {
+ it('should return a 200 OK', function (done) {
+ request(newOptionsCreation, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+ done();
+ });
+ });
+ it('should be recovered using the OLD API endpoint', function (done) {
+ request(newOptionsCreation, function (error, response, body) {
+ request(optionsList, function (error, response, body) {
+ body.count.should.equal(1);
+ body.services[0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ body.services[0].transport.should.equal('HTTP');
+ body.services[0].endpoint.should.equal('http://myendpoint.com');
+
+ body.count.should.equal(1);
+ should.exist(body.services[0].attributes);
+ body.services[0].attributes.length.should.equal(1);
+ body.services[0].attributes[0].name.should.equal('status');
+
+ should.exist(body.services[0].lazy);
+ body.services[0].lazy.length.should.equal(1);
+ body.services[0].lazy[0].name.should.equal('luminescence');
+
+ should.exist(body.services[0].commands);
+ body.services[0].commands.length.should.equal(1);
+ body.services[0].commands[0].name.should.equal('wheel1');
+
+ should.exist(body.services[0].static_attributes);
+ body.services[0].static_attributes.length.should.equal(1);
+ body.services[0].static_attributes[0].name.should.equal('bootstrapServer');
+
+ body.count.should.equal(1);
+ body.services[0].service.should.equal('testservice');
+ body.services[0].subservice.should.equal('/testingPath');
+ done();
+ });
+ });
+ });
+ });
+ describe('When a new device group creation request arrives with the NEW OLD endpoint ', function () {
+ it('should return a 200 OK', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+ done();
+ });
+ });
+ it('should be recovered using the NEW API endpoint', function (done) {
+ request(optionsCreation, function (error, response, body) {
+ request(newOptionsList, function (error, response, body) {
+ body.count.should.equal(1);
+ body[configGroupTerm][0].apikey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
+ body[configGroupTerm][0].transport.should.equal('HTTP');
+ body[configGroupTerm][0].endpoint.should.equal('http://myendpoint.com');
+
+ body.count.should.equal(1);
+ should.exist(body[configGroupTerm][0].attributes);
+ body[configGroupTerm][0].attributes.length.should.equal(1);
+ body[configGroupTerm][0].attributes[0].name.should.equal('status');
+
+ should.exist(body[configGroupTerm][0].lazy);
+ body[configGroupTerm][0].lazy.length.should.equal(1);
+ body[configGroupTerm][0].lazy[0].name.should.equal('luminescence');
+
+ should.exist(body[configGroupTerm][0].commands);
+ body[configGroupTerm][0].commands.length.should.equal(1);
+ body[configGroupTerm][0].commands[0].name.should.equal('wheel1');
+
+ should.exist(body[configGroupTerm][0].static_attributes);
+ body[configGroupTerm][0].static_attributes.length.should.equal(1);
+ body[configGroupTerm][0].static_attributes[0].name.should.equal('bootstrapServer');
+
+ body.count.should.equal(1);
+ body[configGroupTerm][0].service.should.equal('testservice');
+ body[configGroupTerm][0].subservice.should.equal('/testingPath');
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/test/unit/ngsiv2/provisioning/device-group-utils-test.js b/test/unit/ngsiv2/provisioning/device-group-utils-test.js
index d0bbdcae1..76f7b0e92 100644
--- a/test/unit/ngsiv2/provisioning/device-group-utils-test.js
+++ b/test/unit/ngsiv2/provisioning/device-group-utils-test.js
@@ -88,7 +88,7 @@ describe('Device Group utils', function () {
});
});
- describe('When an API Key is requested for a device in a group without the SingleConfiguration mode', function () {
+ describe('When an API Key is requested for a device in a group', function () {
beforeEach(function (done) {
async.series(
[
@@ -107,26 +107,6 @@ describe('Device Group utils', function () {
});
});
});
- describe('When an API Key is requested for a device in a subservice with the SingleConfiguration mode', function () {
- beforeEach(function (done) {
- iotAgentConfig.singleConfigurationMode = true;
- iotAgentLib.activate(iotAgentConfig, function () {
- request(groupCreation, function (error, response, body) {
- done();
- });
- });
- });
- afterEach(function () {
- iotAgentConfig.singleConfigurationMode = false;
- });
- it('should return the API Key of the related subservice', function (done) {
- iotAgentLib.getEffectiveApiKey('testservice', '/testingPath', null, function (error, apiKey) {
- should.not.exist(error);
- apiKey.should.equal('801230BJKL23Y9090DSFL123HJK09H324HV8732');
- done();
- });
- });
- });
describe('When an API Key is requested without a provisioned group but with a configured type', function () {
beforeEach(function (done) {
iotAgentLib.activate(iotAgentConfig, done);
diff --git a/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js b/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js
index 48fd1e46a..4b382c03b 100644
--- a/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js
+++ b/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js
@@ -49,7 +49,8 @@ const iotAgentConfig = {
service: 'smartgondor',
subservice: 'gardens',
providerUrl: 'http://smartgondor.com',
- explicitAttrs: false
+ explicitAttrs: false,
+ useCBflowControl: true
};
describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
@@ -74,7 +75,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.clearAll(done);
@@ -100,15 +101,6 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
)
)
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createProvisionedDevice.json')
- )
- .reply(204);
});
const options = {
@@ -214,7 +206,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
});
});
- it('should create the initial entity in the Context Broker', function (done) {
+ it('should not create the initial entity in the Context Broker', function (done) {
request(options, function (error, response, body) {
response.statusCode.should.equal(201);
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
@@ -248,15 +240,6 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createTimeinstantDevice.json')
- )
- .reply(204);
-
done();
});
@@ -292,15 +275,6 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createTimeinstantDevice.json')
- )
- .reply(204);
-
done();
});
@@ -336,18 +310,8 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createExplicitAttrsDevice.json')
- )
- .reply(204);
-
done();
});
-
it('should send the appropriate requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
contextBrokerMock.done();
@@ -394,17 +358,6 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
@@ -414,11 +367,10 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
done();
});
});
- it('should store the device with explicitAttrs value provided in configuration', function (done) {
+ it('should store the device without explicitAttrs', function (done) {
request(options, function (error, response, body) {
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
- should.exist(results.devices[0].explicitAttrs);
- results.devices[0].explicitAttrs.should.equal(false);
+ should.not.exist(results.devices[0].explicitAttrs);
done();
});
});
@@ -468,7 +420,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
)
@@ -478,12 +430,11 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
done();
});
- it('should store the device with explicitAttrs value provided in configuration', function (done) {
+ it('should store the device without explicitAttrs value', function (done) {
request(groupCreation, function (error, response, body) {
request(options, function (error, response, body) {
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
- should.exist(results.devices[0].explicitAttrs);
- results.devices[0].explicitAttrs.should.equal(true);
+ should.not.exist(results.devices[0].explicitAttrs);
done();
});
});
@@ -541,7 +492,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/' +
'contextRequests/createStaticAttributesProvisionDevice.json'
@@ -552,12 +503,12 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
done();
});
- it('should store the device with static attributes provided in configuration', function (done) {
+ it('should not store the device with static attributes provided in configuration', function (done) {
request(groupCreation, function (error, response, body) {
request(options, function (error, response, body) {
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
should.exist(results.devices[0].staticAttributes);
- results.devices[0].staticAttributes[0].name.should.equal('bootstrapServer');
+ results.devices[0].staticAttributes.length.should.equal(0);
done();
});
});
@@ -607,7 +558,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/' +
'contextRequests/createStaticAttributesProvisionDevice.json'
@@ -681,7 +632,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/' +
'contextRequests/createStaticAttributesProvisionDevice2.json'
@@ -692,12 +643,12 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
done();
});
- it('should store the device with static attributes provided in configuration as well as device', function (done) {
+ it('should store the device with static attributes provided in device but no in configuration', function (done) {
request(groupCreation, function (error, response, body) {
request(options, function (error, response, body) {
iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
should.exist(results.devices[0].staticAttributes);
- results.devices[0].staticAttributes.length.should.equal(2);
+ results.devices[0].staticAttributes.length.should.equal(1);
done();
});
});
@@ -727,7 +678,6 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
/*jshint camelcase: false */
entity_type: 'MicroLights',
entityNameExp: "id + '__' + suffix_st",
- expressionLanguage: 'jexl',
cbHost: 'http://192.168.1.1:1026',
static_attributes: [
{
@@ -751,7 +701,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json'
)
@@ -823,7 +773,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/' +
'contextRequests/createStaticAttributesProvisionDevice3.json'
@@ -861,31 +811,19 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
iotAgentLib.deactivate(function () {
- iotAgentConfig.appendMode = false;
iotAgentLib.activate(iotAgentConfig, done);
});
});
- afterEach(function () {
- iotAgentConfig.appendMode = false;
- });
+ afterEach(function () {});
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createAutoprovisionDevice.json')
- )
- .reply(204);
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should should not appropriate requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -915,36 +853,11 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert', function (body) {
- const expectedBody = utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createTimeInstantMinimumDevice.json'
- );
- if (!body.TimeInstant.value) {
- return false;
- } else if (moment(body.TimeInstant.value, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid()) {
- const timeInstantDiff = moment().diff(body.TimeInstant.value, 'milliseconds');
- if (timeInstantDiff < 500) {
- delete body.TimeInstant;
-
- return JSON.stringify(body) === JSON.stringify(expectedBody);
- }
-
- return false;
- } else {
- return false;
- }
- })
- .reply(204);
-
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send any requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -963,23 +876,11 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
- it('should send the appropriate requests to the Context Broker', function (done) {
+ it('should not send any requests to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -1022,23 +923,10 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createGeopointProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
-
- it('should send the appropriate initial values to the Context Broker', function (done) {
+ it('should not send any initial values to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -1057,23 +945,10 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
beforeEach(function (done) {
nock.cleanAll();
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createDatetimeProvisionedDevice.json'
- )
- )
- .reply(204);
-
done();
});
-
- it('should send the appropriate initial values to the Context Broker', function (done) {
+ it('should not send any initial values to the Context Broker', function (done) {
request(options, function (error, response, body) {
- contextBrokerMock.done();
done();
});
});
@@ -1103,7 +978,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
nock.cleanAll();
contextBrokerMock = nock('http://192.168.1.1:1026')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
)
@@ -1112,7 +987,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
contextBrokerMock
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
)
@@ -1170,23 +1045,13 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-servicepath', '/gardens')
.post('/v2/registrations')
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
- .replyWithError({ message: 'Description of the error', code: 'STRING_CODE' });
-
done();
});
it('should return a valid return code', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
- response.statusCode.should.equal(500);
+ response.statusCode.should.equal(201);
done();
});
@@ -1213,22 +1078,13 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.post('/v2/registrations')
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
- .replyWithError({ message: 'Description of the error', code: 123456789 });
-
done();
});
it('should return a valid return code (three character number)', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
- response.statusCode.should.equal(500);
+ response.statusCode.should.equal(201);
done();
});
@@ -1282,7 +1138,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
done();
diff --git a/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js b/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js
new file mode 100644
index 000000000..0f8f44d6c
--- /dev/null
+++ b/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js
@@ -0,0 +1,1190 @@
+/*
+ * Copyright 2024 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::[contacto@tid.es]
+ *
+ * Modified by: Daniel Calvo - ATOS Research & Innovation
+ */
+
+/* eslint-disable no-unused-vars */
+
+const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
+const utils = require('../../../tools/utils');
+const should = require('should');
+const nock = require('nock');
+
+const request = utils.request;
+const moment = require('moment');
+let contextBrokerMock;
+const iotAgentConfig = {
+ logLevel: 'FATAL',
+ contextBroker: {
+ host: '192.168.1.1',
+ port: '1026',
+ ngsiVersion: 'v2'
+ },
+ server: {
+ port: 4041,
+ host: 'localhost',
+ baseRoot: '/'
+ },
+ types: {},
+ service: 'smartgondor',
+ subservice: 'gardens',
+ providerUrl: 'http://smartgondor.com',
+ explicitAttrs: false,
+ useCBflowControl: true
+};
+
+describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ iotAgentLib.activate(iotAgentConfig, function () {
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/registrations',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+
+ // This mock does not check the payload since the aim of the test is not to verify
+ // device provisioning functionality. Appropriate verification is done in tests under
+ // provisioning folder
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ iotAgentLib.clearAll(done);
+ });
+ });
+
+ afterEach(function (done) {
+ nock.cleanAll();
+ iotAgentLib.setProvisioningHandler();
+ iotAgentLib.deactivate(done);
+ });
+
+ describe('When a device provisioning request with all the required data arrives to the IoT Agent', function () {
+ beforeEach(function () {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/registrations',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json'
+ )
+ )
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+ });
+
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ it('should add the device to the devices list', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices.length.should.equal(1);
+ done();
+ });
+ });
+ });
+
+ it('should call the device provisioning handler if present', function (done) {
+ let handlerCalled = false;
+
+ iotAgentLib.setProvisioningHandler(function (device, callback) {
+ handlerCalled = true;
+ callback(null, device);
+ });
+
+ request(options, function (error, response, body) {
+ handlerCalled.should.equal(true);
+ done();
+ });
+ });
+
+ it('should store the device with the provided entity id, name and type', function (done) {
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices[0].id.should.equal('Light1');
+ results.devices[0].name.should.equal('TheFirstLight');
+ results.devices[0].type.should.equal('TheLightType');
+ done();
+ });
+ });
+ });
+ it('should store the device with the per device information', function (done) {
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].timezone);
+ results.devices[0].timezone.should.equal('America/Santiago');
+ should.exist(results.devices[0].endpoint);
+ results.devices[0].endpoint.should.equal('http://fakedEndpoint:1234');
+ should.exist(results.devices[0].transport);
+ results.devices[0].transport.should.equal('MQTT');
+ should.exist(results.devices[0].lazy);
+ results.devices[0].lazy.length.should.equal(1);
+ results.devices[0].lazy[0].name.should.equal('luminance');
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].commands.length.should.equal(1);
+ results.devices[0].commands[0].name.should.equal('commandAttr');
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].staticAttributes.length.should.equal(1);
+ results.devices[0].staticAttributes[0].name.should.equal('hardcodedAttr');
+ should.exist(results.devices[0].active);
+ results.devices[0].active.length.should.equal(1);
+ results.devices[0].active[0].name.should.equal('attr_name');
+ should.exist(results.devices[0].internalAttributes);
+ results.devices[0].internalAttributes.length.should.equal(1);
+ results.devices[0].internalAttributes[0].customField.should.equal('customValue');
+ done();
+ });
+ });
+ });
+
+ it('should store fill the device ID in case only the name is provided', function (done) {
+ /* jshint camelcase:false */
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices[0].lazy[0].object_id.should.equal('luminance');
+ results.devices[0].commands[0].object_id.should.equal('commandAttr');
+ results.devices[0].active[0].object_id.should.equal('attr_name');
+ done();
+ });
+ });
+ });
+
+ it('should store service and subservice info from the headers along with the device data', function (done) {
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].service);
+ results.devices[0].service.should.equal('smartgondor');
+ should.exist(results.devices[0].subservice);
+ results.devices[0].subservice.should.equal('/gardens');
+ done();
+ });
+ });
+ });
+
+ it('should not create the initial entity in the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+ });
+ describe('When a device provisioning request with a TimeInstant attribute arrives to the IoTA', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionTimeInstant.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.timestamp = true;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.timestamp = false;
+ });
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the appropriate requests to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request with a timestamp provision attribute arrives to the IoTA', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionTimeInstant2.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.timestamp = false;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.timestamp = false;
+ });
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the appropriate requests to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request with explicitAttrs provision attribute arrives to the IoTA', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionExplicitAttrs.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.explicitAttrs = false;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.explicitAttrs = false;
+ });
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the appropriate requests to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ it('should store the device with explicitAttrs:true device information', function (done) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].explicitAttrs);
+ results.devices[0].explicitAttrs.should.equal(true);
+ done();
+ });
+ });
+ });
+ });
+
+ describe(
+ 'When a device provisioning request without explicitAttrs provision attribute arrives to the IoTA' +
+ ' and explicitAttrs is not configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.explicitAttrs = false;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.explicitAttrs = false;
+ });
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the appropriate requests to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ contextBrokerMock.done();
+ done();
+ });
+ });
+ it('should store the device without explicitAttrs', function (done) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.not.exist(results.devices[0].explicitAttrs);
+ done();
+ });
+ });
+ });
+ }
+ );
+
+ describe(
+ 'When a device provisioning request without explicitAttrs provision attribute arrives to the IoTA' +
+ ' and explicitAttrs is configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const groupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/Thing',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ /*jshint camelcase: false */
+ entity_type: 'MicroLights',
+ cbroker: 'http://192.168.1.1:1026',
+ explicitAttrs: true
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should store the device without explicitAttrs value', function (done) {
+ request(groupCreation, function (error, response, body) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.not.exist(results.devices[0].explicitAttrs);
+ done();
+ });
+ });
+ });
+ });
+ }
+ );
+
+ describe(
+ 'When a device provisioning request without static attributes arrives to the IoTA' +
+ ' and static attribute is configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const groupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/Thing',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ /*jshint camelcase: false */
+ entity_type: 'MicroLights',
+ cbroker: 'http://192.168.1.1:1026',
+ explicitAttrs: true,
+ static_attributes: [
+ {
+ name: 'bootstrapServer',
+ type: 'Address',
+ value: '127.0.0.1'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/' +
+ 'contextRequests/createStaticAttributesProvisionDevice.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should not store the device with static attributes provided in configuration', function (done) {
+ request(groupCreation, function (error, response, body) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].staticAttributes.length.should.equal(0);
+ done();
+ });
+ });
+ });
+ });
+ }
+ );
+ describe(
+ 'When a device provisioning request with static attributes arrives to the IoTA' +
+ ' and static attribute is not configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionStaticAttrsDevice.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const groupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/Thing',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ /*jshint camelcase: false */
+ entity_type: 'MicroLights',
+ cbroker: 'http://192.168.1.1:1026',
+ explicitAttrs: true
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/' +
+ 'contextRequests/createStaticAttributesProvisionDevice.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should store the device with static attributes provided in device', function (done) {
+ request(groupCreation, function (error, response, body) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].staticAttributes[0].name.should.equal('bootstrapServer');
+ done();
+ });
+ });
+ });
+ });
+ }
+ );
+
+ describe(
+ 'When a device provisioning request with static attributes arrives to the IoTA' +
+ ' and static attribute is also configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionStaticAttrsDevice2.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const groupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/Thing',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ /*jshint camelcase: false */
+ entity_type: 'MicroLights',
+ cbroker: 'http://192.168.1.1:1026',
+ explicitAttrs: true,
+ static_attributes: [
+ {
+ name: 'bootstrapServer',
+ type: 'Address',
+ value: '127.0.0.1'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/' +
+ 'contextRequests/createStaticAttributesProvisionDevice2.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should store the device with static attributes provided in configuration as well as device', function (done) {
+ request(groupCreation, function (error, response, body) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].staticAttributes.length.should.equal(1);
+ done();
+ });
+ });
+ });
+ });
+ }
+ );
+
+ describe(
+ 'When a device provisioning request with static attributes arrives to the IoTA' +
+ ' and same static attribute is also configured at group level',
+ function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionStaticAttrsDevice3.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const groupCreation = {
+ url: 'http://localhost:4041/iot/groups',
+ method: 'POST',
+ json: {
+ groups: [
+ {
+ resource: '/Thing',
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
+ /*jshint camelcase: false */
+ entity_type: 'MicroLights',
+ cbroker: 'http://192.168.1.1:1026',
+ explicitAttrs: true,
+ static_attributes: [
+ {
+ name: 'bootstrapServer',
+ type: 'Address',
+ value: '127.0.0.1'
+ }
+ ]
+ }
+ ]
+ },
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/' +
+ 'contextRequests/createStaticAttributesProvisionDevice3.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should store the device with static attributes provided in device', function (done) {
+ request(groupCreation, function (error, response, body) {
+ request(options, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ should.exist(results.devices[0].staticAttributes);
+ results.devices[0].staticAttributes[0].value.should.equal('127.0.0.2');
+ done();
+ });
+ });
+ });
+ });
+ }
+ );
+
+ describe('When a device provisioning request with a autoprovision attribute arrives to the IoTA', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionAutoprovision.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {});
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the appropriate requests to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request arrives to the IoTAand timestamp is enabled in configuration', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.timestamp = true;
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.timestamp = false;
+ });
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ done();
+ });
+
+ it('should send the any request to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request with the minimum required data arrives to the IoT Agent', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send the any request to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ done();
+ });
+ });
+
+ it('should add the device to the devices list', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices.length.should.equal(1);
+ done();
+ });
+ });
+ });
+
+ it('should store the device with the provided entity id, name and type', function (done) {
+ request(options, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices[0].id.should.equal('MicroLight1');
+ results.devices[0].name.should.equal('FirstMicroLight');
+ results.devices[0].type.should.equal('MicroLights');
+ done();
+ });
+ });
+ });
+ });
+
+ describe('When a device provisioning request with geo:point attributes arrives', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionGeopointDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send any initial values to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request with DateTime attributes arrives', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionDatetimeDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ done();
+ });
+
+ it('should send any initial values to the Context Broker', function (done) {
+ request(options, function (error, response, body) {
+ done();
+ });
+ });
+ });
+
+ describe('When two devices with the same ID but different services arrive to the agent', function () {
+ const options1 = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+ const options2 = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartMordor',
+ 'fiware-servicepath': '/electricity'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
+ )
+ )
+ .reply(204);
+
+ contextBrokerMock
+ .post(
+ '/v2/entities?options=upsert,flowControl',
+ utils.readExampleFile(
+ './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice.json'
+ )
+ )
+ .reply(204);
+
+ done();
+ });
+
+ it('should accept both creations', function (done) {
+ request(options1, function (error, response, body) {
+ response.statusCode.should.equal(201);
+
+ request(options2, function (error, response, body) {
+ response.statusCode.should.equal(201);
+ done();
+ });
+ });
+ });
+
+ it('should show the new device in each list', function (done) {
+ request(options1, function (error, response, body) {
+ request(options2, function (error, response, body) {
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices.length.should.equal(1);
+ results.devices[0].id.should.equal('MicroLight1');
+
+ iotAgentLib.listDevices('smartMordor', '/electricity', function (error, results) {
+ results.devices.length.should.equal(1);
+ results.devices[0].id.should.equal('MicroLight1');
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+
+ describe('When there is a connection error with a String code connecting the CB', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+ done();
+ });
+
+ it('should return a valid return code', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+
+ done();
+ });
+ });
+ });
+
+ describe('When there is a connection error with a Number code connecting the CB', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ nock.cleanAll();
+
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+
+ done();
+ });
+
+ it('should return a valid return code (three character number)', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+
+ done();
+ });
+ });
+ });
+
+ describe('When a device provisioning request with missing data arrives to the IoT Agent', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ },
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionDeviceMissingParameters.json'
+ )
+ };
+
+ it('should raise a MISSING_ATTRIBUTES error, indicating the missing attributes', function (done) {
+ request(options, function (error, response, body) {
+ should.exist(body);
+ response.statusCode.should.equal(400);
+ body.name.should.equal('MISSING_ATTRIBUTES');
+ body.message.should.match(/.*device_id.*/);
+ done();
+ });
+ });
+ });
+ describe('When two device provisioning requests with the same service and Device ID arrive', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ beforeEach(function (done) {
+ contextBrokerMock = nock('http://192.168.1.1:1026')
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
+
+ // This mock does not check the payload since the aim of the test is not to verify
+ // device provisioning functionality. Appropriate verification is done in tests under
+ // provisioning folder
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/entities?options=upsert,flowControl')
+ .reply(204);
+
+ done();
+ });
+
+ it('should raise a DUPLICATE_ID error, indicating the ID was already in use', function (done) {
+ request(options, function (error, response, body) {
+ request(options, function (error, response, body) {
+ should.exist(body);
+ response.statusCode.should.equal(409);
+ body.name.should.equal('DUPLICATE_DEVICE_ID');
+ done();
+ });
+ });
+ });
+ });
+ describe('When a device provisioning request is malformed', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionNewDeviceMalformed1.json'
+ ),
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ }
+ };
+
+ it('should raise a WRONG_SYNTAX exception', function (done) {
+ request(options, function (error, response, body) {
+ request(options, function (error, response, body) {
+ should.exist(body);
+ response.statusCode.should.equal(400);
+ body.name.should.equal('WRONG_SYNTAX');
+ done();
+ });
+ });
+ });
+ });
+ describe('When an agent is activated with a different base root', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/newBaseRoot/iot/devices',
+ method: 'POST',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ },
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json')
+ };
+
+ beforeEach(function (done) {
+ iotAgentLib.deactivate(function () {
+ iotAgentConfig.server.baseRoot = '/newBaseRoot';
+ iotAgentLib.activate(iotAgentConfig, done);
+ });
+ });
+
+ afterEach(function () {
+ iotAgentConfig.server.baseRoot = '/';
+ });
+
+ it('should listen to requests in the new root', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(201);
+
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
+ results.devices.length.should.equal(1);
+ done();
+ });
+ });
+ });
+ });
+ describe('When a device provisioning request without the mandatory headers arrives to the Agent', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
+ method: 'POST',
+ headers: {},
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/provisionDeviceMissingParameters.json'
+ )
+ };
+
+ it('should raise a MISSING_HEADERS error, indicating the missing attributes', function (done) {
+ request(options, function (error, response, body) {
+ should.exist(body);
+ response.statusCode.should.equal(400);
+ body.name.should.equal('MISSING_HEADERS');
+ done();
+ });
+ });
+ });
+ describe('When a device delete request arrives to the Agent for a not existing device', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light84',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ },
+ method: 'DELETE'
+ };
+
+ it('should return a 404 error', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(404);
+ done();
+ });
+ });
+ });
+});
diff --git a/test/unit/ngsiv2/provisioning/device-registration_test.js b/test/unit/ngsiv2/provisioning/device-registration_test.js
index dbf29c15e..49c6df78b 100644
--- a/test/unit/ngsiv2/provisioning/device-registration_test.js
+++ b/test/unit/ngsiv2/provisioning/device-registration_test.js
@@ -76,19 +76,22 @@ const iotAgentConfig = {
},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
const device1 = {
id: 'light1',
type: 'Light',
service: 'smartgondor',
- subservice: 'gardens'
+ subservice: 'gardens',
+ apikey: null
};
const device2 = {
id: 'term2',
type: 'Termometer',
service: 'smartgondor',
- subservice: 'gardens'
+ subservice: 'gardens',
+ apikey: null
};
describe('NGSI-v2 - IoT Agent Device Registration', function () {
@@ -112,11 +115,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
+ contextBrokerMock = nock('http://192.168.1.1:1026');
const nockBody = utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json'
@@ -217,7 +216,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
iotAgentLib.activate(iotAgentConfig, function (error) {
@@ -227,7 +226,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
it("should return all the device's information", function (done) {
iotAgentLib.register(device1, function (error) {
- iotAgentLib.getDevice('light1', 'smartgondor', 'gardens', function (error, data) {
+ iotAgentLib.getDevice('light1', null, 'smartgondor', 'gardens', function (error, data) {
should.not.exist(error);
should.exist(data);
data.type.should.equal('Light');
@@ -258,7 +257,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
it('should return a ENTITY_NOT_FOUND error', function (done) {
iotAgentLib.register(device1, function (error) {
- iotAgentLib.getDevice('lightUnexistent', 'smartgondor', 'gardens', function (error, data) {
+ iotAgentLib.getDevice('lightUnexistent', null, 'smartgondor', 'gardens', function (error, data) {
should.exist(error);
should.not.exist(data);
error.code.should.equal(404);
@@ -279,7 +278,6 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
contextBrokerMock
.post('/v2/registrations')
@@ -288,7 +286,6 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
contextBrokerMock
.delete('/v2/registrations/6319a7f5254b05844116584d', '')
@@ -307,7 +304,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
});
it('should update the devices information in Context Broker', function (done) {
- iotAgentLib.unregister(device1.id, 'smartgondor', 'gardens', function (error) {
+ iotAgentLib.unregister(device1.id, null, 'smartgondor', 'gardens', function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
@@ -325,7 +322,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').reply(204);
contextBrokerMock = nock('http://192.168.1.1:1026')
.post('/v2/registrations')
@@ -334,7 +331,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').reply(204);
contextBrokerMock.delete('/v2/registrations/6319a7f5254b05844116584d', '').reply(500);
@@ -352,7 +349,7 @@ describe('NGSI-v2 - IoT Agent Device Registration', function () {
it('should not remove the device from the internal registry');
it('should return a UNREGISTRATION_ERROR error to the caller', function (done) {
- iotAgentLib.unregister(device1.id, 'smartgondor', 'gardens', function (error) {
+ iotAgentLib.unregister(device1.id, null, 'smartgondor', 'gardens', function (error) {
should.exist(error);
should.exist(error.name);
error.name.should.equal('UNREGISTRATION_ERROR');
diff --git a/test/unit/ngsiv2/provisioning/device-update-registration_test.js b/test/unit/ngsiv2/provisioning/device-update-registration_test.js
index 0c1dd48c9..d97efee81 100644
--- a/test/unit/ngsiv2/provisioning/device-update-registration_test.js
+++ b/test/unit/ngsiv2/provisioning/device-update-registration_test.js
@@ -148,15 +148,6 @@ describe('NGSI-v2 - IoT Agent Device Update Registration', function () {
.post('/v2/registrations')
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', 'gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
iotAgentLib.activate(iotAgentConfig, function (error) {
iotAgentLib.register(device1, function (error) {
done();
@@ -206,15 +197,16 @@ describe('NGSI-v2 - IoT Agent Device Update Registration', function () {
});
it('should register as ContextProvider of its lazy attributes', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
});
});
+
it('should store the new values in the registry', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error, data) {
- iotAgentLib.getDevice(deviceUpdated.id, 'smartgondor', 'gardens', function (error, deviceResult) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error, data) {
+ iotAgentLib.getDevice(deviceUpdated.id, null, 'smartgondor', 'gardens', function (error, deviceResult) {
should.not.exist(error);
should.exist(deviceResult);
deviceResult.internalId.should.equal(deviceUpdated.internalId);
@@ -261,16 +253,18 @@ describe('NGSI-v2 - IoT Agent Device Update Registration', function () {
});
it('should register as ContextProvider of its commands and create the additional attributes', function (done) {
- iotAgentLib.updateRegister(deviceCommandUpdated, false, function (error) {
+ iotAgentLib.updateRegister(deviceCommandUpdated, device1, false, function (error) {
should.not.exist(error);
contextBrokerMock.done();
done();
});
});
+
it('should store the new values in the registry', function (done) {
- iotAgentLib.updateRegister(deviceCommandUpdated, false, function (error, data) {
+ iotAgentLib.updateRegister(deviceCommandUpdated, device1, false, function (error, data) {
iotAgentLib.getDevice(
deviceCommandUpdated.id,
+ null,
'smartgondor',
'gardens',
function (error, deviceResult) {
@@ -288,7 +282,7 @@ describe('NGSI-v2 - IoT Agent Device Update Registration', function () {
describe('When a update action is executed in a non registered device', function () {
it('should return a DEVICE_NOT_FOUND error', function (done) {
- iotAgentLib.updateRegister(unknownDevice, false, function (error) {
+ iotAgentLib.updateRegister(unknownDevice, device1, false, function (error) {
should.exist(error);
error.name.should.equal('DEVICE_NOT_FOUND');
done();
@@ -310,7 +304,7 @@ describe('NGSI-v2 - IoT Agent Device Update Registration', function () {
});
it('should return a REGISTRATION_ERROR error in the update action', function (done) {
- iotAgentLib.updateRegister(deviceUpdated, false, function (error) {
+ iotAgentLib.updateRegister(deviceUpdated, device1, false, function (error) {
should.exist(error);
error.name.should.equal('UNREGISTRATION_ERROR');
done();
diff --git a/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js b/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js
index d51632100..f08930833 100644
--- a/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js
+++ b/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js
@@ -48,7 +48,8 @@ const iotAgentConfig = {
types: {},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Device provisioning API: List provisioned devices', function () {
@@ -96,7 +97,7 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').reply(204);
contextBrokerMock
.post('/v2/registrations')
@@ -105,7 +106,7 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').reply(204);
contextBrokerMock
.post('/v2/registrations')
@@ -114,7 +115,7 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').reply(204);
async.series(
[
@@ -183,11 +184,9 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
should.exist(body.devices[2].attributes[0].entity_name);
should.exist(body.devices[2].attributes[0].entity_type);
should.exist(body.devices[2].attributes[1].expression);
- should.exist(body.devices[2].attributes[2].reverse);
body.devices[2].attributes[0].entity_name.should.equal('Higro2000');
body.devices[2].attributes[0].entity_type.should.equal('Higrometer');
body.devices[2].attributes[1].expression.should.equal('${@humidity * 20}');
- body.devices[2].attributes[2].reverse.length.should.equal(2);
done();
});
});
@@ -242,11 +241,9 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
should.exist(body.attributes[0].entity_name);
should.exist(body.attributes[0].entity_type);
should.exist(body.attributes[1].expression);
- should.exist(body.attributes[2].reverse);
body.attributes[0].entity_name.should.equal('Higro2000');
body.attributes[0].entity_type.should.equal('Higrometer');
body.attributes[1].expression.should.equal('${@humidity * 20}');
- body.attributes[2].reverse.length.should.equal(2);
done();
});
});
@@ -312,7 +309,7 @@ describe('NGSI-v2 - Device provisioning API: List provisioned devices', function
// This mock does not check the payload since the aim of the test is not to verify
// device provisioning functionality. Appropriate verification is done in tests under
// provisioning folder
- contextBrokerMock.post('/v2/entities?options=upsert').times(10).reply(204);
+ contextBrokerMock.post('/v2/entities?options=upsert,flowControl').times(10).reply(204);
iotAgentLib.clearAll(function () {
async.times(10, createDeviceRequest, function (error, results) {
diff --git a/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js b/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js
index 3072f7439..d2398bf15 100644
--- a/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js
+++ b/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js
@@ -84,7 +84,7 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile(
'./test/unit/ngsiv2/examples/contextRequests/createProvisionedDeviceMultientity.json'
)
diff --git a/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js b/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js
index cbed2efce..5051ac85b 100644
--- a/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js
+++ b/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js
@@ -48,7 +48,8 @@ const iotAgentConfig = {
types: {},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', function () {
@@ -97,7 +98,7 @@ describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', functi
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
const nockBody2 = utils.readExampleFile(
@@ -115,7 +116,7 @@ describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', functi
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
contextBrokerMock
@@ -130,7 +131,7 @@ describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', functi
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series(
@@ -161,7 +162,7 @@ describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', functi
method: 'DELETE'
};
- it('should return a 200 OK and no errors', function (done) {
+ it('should return a 204 OK and no errors', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
response.statusCode.should.equal(204);
@@ -231,7 +232,42 @@ describe('NGSI-v2 - Device provisioning API: Remove provisioned devices', functi
method: 'DELETE'
};
- it('should return a 200 OK and no errors', function (done) {
+ it('should return a 204 OK and no errors', function (done) {
+ request(options, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ });
+
+ describe('When a request to remove a provision devices arrives', function () {
+ const options = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/op/delete',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ },
+ method: 'POST',
+ json: {
+ devices: [
+ {
+ deviceId: 'Light1',
+ apikey: ''
+ },
+ {
+ deviceId: 'Light2',
+ apikey: ''
+ },
+ {
+ deviceId: 'Light3',
+ apikey: ''
+ }
+ ]
+ }
+ };
+
+ it('should return a 204 OK and no errors', function (done) {
request(options, function (error, response, body) {
should.not.exist(error);
response.statusCode.should.equal(204);
diff --git a/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js b/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js
deleted file mode 100644
index c38685656..000000000
--- a/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
- *
- * This file is part of fiware-iotagent-lib
- *
- * fiware-iotagent-lib is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the License,
- * or (at your option) any later version.
- *
- * fiware-iotagent-lib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with fiware-iotagent-lib.
- * If not, see http://www.gnu.org/licenses/.
- *
- * For those usages not covered by the GNU Affero General Public License
- * please contact with::[contacto@tid.es]
- *
- * Modified by: Daniel Calvo - ATOS Research & Innovation
- */
-
-/* eslint-disable no-unused-vars */
-
-const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
-const utils = require('../../../tools/utils');
-const request = utils.request;
-const should = require('should');
-const nock = require('nock');
-let contextBrokerMock;
-
-const iotAgentConfig = {
- logLevel: 'FATAL',
- contextBroker: {
- host: '192.168.1.1',
- port: '1026',
- ngsiVersion: 'v2'
- },
- server: {
- port: 4041,
- host: 'localhost',
- baseRoot: '/'
- },
- types: {},
- service: 'smartgondor',
- singleConfigurationMode: true,
- subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
-};
-const groupCreation = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionFullGroup.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
-};
-const deviceCreation = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
-};
-
-describe('NGSI-v2 - Provisioning API: Single service mode', function () {
- beforeEach(function (done) {
- nock.cleanAll();
-
- iotAgentLib.activate(iotAgentConfig, function () {
- iotAgentLib.clearAll(done);
- });
- });
-
- afterEach(function (done) {
- nock.cleanAll();
- iotAgentLib.setProvisioningHandler();
- iotAgentLib.deactivate(done);
- });
-
- describe('When a new configuration arrives to an already configured subservice', function () {
- const groupCreationDuplicated = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionDuplicateGroup.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- request(groupCreation, done);
- });
-
- it('should raise a DUPLICATE_GROUP error', function (done) {
- request(groupCreationDuplicated, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(409);
- should.exist(body.name);
- body.name.should.equal('DUPLICATE_GROUP');
- done();
- });
- });
- });
- describe('When a device is provisioned with an ID that already exists in the configuration', function () {
- const deviceCreationDuplicated = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionDuplicatedDev.json'),
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/registrations')
- .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
- request(groupCreation, function (error) {
- request(deviceCreation, function (error, response, body) {
- done();
- });
- });
- });
-
- it('should raise a DUPLICATE_DEVICE_ID error', function (done) {
- request(deviceCreationDuplicated, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(409);
- should.exist(body.name);
- body.name.should.equal('DUPLICATE_DEVICE_ID');
- done();
- });
- });
- });
- describe('When a device is provisioned with an ID that exists globally but not in the configuration', function () {
- const alternativeDeviceCreation = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDevice.json'),
- headers: {
- 'fiware-service': 'AlternateService',
- 'fiware-servicepath': '/testingPath'
- }
- };
- const alternativeGroupCreation = {
- url: 'http://localhost:4041/iot/services',
- method: 'POST',
- json: utils.readExampleFile('./test/unit/examples/groupProvisioningRequests/provisionFullGroup.json'),
- headers: {
- 'fiware-service': 'AlternateService',
- 'fiware-servicepath': '/testingPath'
- }
- };
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/registrations')
- .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
- contextBrokerMock = nock('http://192.168.1.1:1026')
- .matchHeader('fiware-service', 'AlternateService')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/registrations')
- .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'AlternateService')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
- request(groupCreation, function (error) {
- request(deviceCreation, function (error, response, body) {
- request(alternativeGroupCreation, function (error, response, body) {
- done();
- });
- });
- });
- });
-
- it('should return a 201 OK', function (done) {
- request(alternativeDeviceCreation, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(201);
- done();
- });
- });
- });
- describe('When a device is provisioned without a type and with a default configuration type', function () {
- const getDevice = {
- url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
- method: 'GET',
- headers: {
- 'fiware-service': 'testservice',
- 'fiware-servicepath': '/testingPath'
- }
- };
- let oldType;
-
- beforeEach(function (done) {
- nock.cleanAll();
-
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/registrations')
- .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- // This mock does not check the payload since the aim of the test is not to verify
- // device provisioning functionality. Appropriate verification is done in tests under
- // provisioning folder
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
- oldType = deviceCreation.json.devices[0].entity_type;
- delete deviceCreation.json.devices[0].entity_type;
- request(groupCreation, done);
- });
-
- afterEach(function () {
- deviceCreation.json.devices[0].entity_type = oldType;
- });
-
- it('should be provisioned with the default type', function (done) {
- request(deviceCreation, function (error, response, body) {
- request(getDevice, function (error, response, body) {
- body.entity_type.should.equal('SensorMachine');
-
- done();
- });
- });
- });
- });
- describe('When a device is provisioned for a configuration', function () {
- beforeEach(function (done) {
- nock.cleanAll();
- contextBrokerMock = nock('http://unexistentHost:1026')
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post(
- '/v2/registrations',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples' +
- '/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json'
- )
- )
- .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
-
- contextBrokerMock
- .matchHeader('fiware-service', 'testservice')
- .matchHeader('fiware-servicepath', '/testingPath')
- .post(
- '/v2/entities?options=upsert',
- utils.readExampleFile(
- './test/unit/ngsiv2/examples/contextRequests/createProvisionedDeviceWithGroupAndStatic.json'
- )
- )
- .reply(204);
-
- request(groupCreation, done);
- });
-
- it('should not raise any error', function (done) {
- request(deviceCreation, function (error, response, body) {
- should.not.exist(error);
- response.statusCode.should.equal(201);
- done();
- });
- });
-
- it('should send the mixed data to the Context Broker', function (done) {
- request(deviceCreation, function (error, response, body) {
- contextBrokerMock.done();
- done();
- });
- });
- });
-});
diff --git a/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js b/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js
index 62deae09c..e4cd5730d 100644
--- a/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js
+++ b/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js
@@ -48,7 +48,8 @@ const iotAgentConfig = {
types: {},
service: 'smartgondor',
subservice: 'gardens',
- providerUrl: 'http://smartgondor.com'
+ providerUrl: 'http://smartgondor.com',
+ useCBflowControl: true
};
describe('NGSI-v2 - Device provisioning API: Update provisioned devices', function () {
@@ -107,7 +108,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
const nockBody2 = utils.readExampleFile(
@@ -126,7 +127,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
contextBrokerMock
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
// FIXME: When https://github.com/telefonicaid/fiware-orion/issues/3007 is merged into master branch,
@@ -181,7 +182,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
.post(
- '/v2/entities?options=upsert',
+ '/v2/entities?options=upsert,flowControl',
utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateProvisionDevice.json')
)
.reply(204);
@@ -240,7 +241,6 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
request(options, function (error, response, body) {
/* jshint camelcase:false */
-
body.entity_name.should.equal('ANewLightName');
body.timezone.should.equal('Europe/Madrid');
done();
@@ -290,6 +290,41 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
});
});
});
+ describe('When an update request arrives with a new Apikey', function () {
+ const optionsUpdate = {
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
+ method: 'PUT',
+ headers: {
+ 'fiware-service': 'smartgondor',
+ 'fiware-servicepath': '/gardens'
+ },
+ json: utils.readExampleFile(
+ './test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json'
+ )
+ };
+
+ beforeEach(function () {
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .delete('/v2/registrations/6319a7f5254b05844116584d', '')
+ .reply(204);
+
+ contextBrokerMock
+ .matchHeader('fiware-service', 'smartgondor')
+ .matchHeader('fiware-servicepath', '/gardens')
+ .post('/v2/registrations')
+ .reply(201, null, { Location: '/v2/registrations/4419a7f5254b058441165849' });
+ });
+
+ it('should raise a 204 error', function (done) {
+ request(optionsUpdate, function (error, response, body) {
+ should.not.exist(error);
+ response.statusCode.should.equal(204);
+ done();
+ });
+ });
+ });
describe('When a wrong update request payload arrives', function () {
const optionsUpdate = {
url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices/Light1',
@@ -340,7 +375,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
contextBrokerMock
@@ -376,13 +411,6 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
});
});
});
- it('should create the initial values for the attributes in the Context Broker', function (done) {
- request(optionsUpdate, function (error, response, body) {
- should.not.exist(error);
- contextBrokerMock.done();
- done();
- });
- });
});
describe('When a device is updated to add static attributes', function () {
@@ -415,7 +443,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
contextBrokerMock
@@ -477,7 +505,7 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
contextBrokerMock = nock('http://192.168.1.1:1026')
.matchHeader('fiware-service', 'smartgondor')
.matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
+ .post('/v2/entities?options=upsert,flowControl')
.reply(204);
async.series([iotAgentLib.clearAll, async.apply(request, provisioning4Options)], done);
diff --git a/test/unit/plugins/capture-provision-inPlugins_test.js b/test/unit/plugins/capture-provision-inPlugins_test.js
index ff246481b..7ec5a1e07 100644
--- a/test/unit/plugins/capture-provision-inPlugins_test.js
+++ b/test/unit/plugins/capture-provision-inPlugins_test.js
@@ -89,12 +89,6 @@ describe('NGSI-v2 - Data Mapping Plugins: device provision', function () {
)
.reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' });
- contextBrokerMock
- .matchHeader('fiware-service', 'smartgondor')
- .matchHeader('fiware-servicepath', '/gardens')
- .post('/v2/entities?options=upsert')
- .reply(204);
-
iotAgentLib.activate(iotAgentConfig, function (error) {
iotAgentLib.clearAll(done);
});
diff --git a/test/unit/statsRegistry/openmetrics-test.js b/test/unit/statsRegistry/openmetrics-test.js
new file mode 100644
index 000000000..dc8a0c7e7
--- /dev/null
+++ b/test/unit/statsRegistry/openmetrics-test.js
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2024 Telefonica Investigación y Desarrollo, S.A.U
+ *
+ * This file is part of fiware-iotagent-lib
+ *
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the License,
+ * or (at your option) any later version.
+ *
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with fiware-iotagent-lib.
+ * If not, see http://www.gnu.org/licenses/.
+ *
+ * For those usages not covered by the GNU Affero General Public License
+ * please contact with::daniel.moranjimenez@telefonica.com
+ */
+
+/* eslint-disable no-unused-vars */
+
+const statsRegistry = require('../../../lib/services/stats/statsRegistry');
+const should = require('should');
+
+describe('statsRegistry - openmetrics endpoint', function () {
+
+ const testCases = [
+ {
+ description: 'Should accept standard openmetrics 0.0.1 header',
+ accept: 'application/openmetrics-text; version=0.0.1; charset=utf-8',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '0.0.1',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept standard openmetrics 1.0.0 header',
+ accept: 'application/openmetrics-text; version=1.0.0; charset=utf-8',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '1.0.0',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept openmetrics with no version',
+ accept: 'application/openmetrics-text',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '1.0.0',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept text/plain header with version',
+ accept: 'text/plain; version=0.0.4',
+ contentType: {
+ mediaType: 'text/plain',
+ version: '0.0.4',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept wildcard header',
+ accept: '*/*',
+ contentType: {
+ mediaType: 'text/plain',
+ version: '0.0.4',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept both openmetrics and text/plain, prefer openmetrics',
+ accept: 'application/openmetrics-text; version=0.0.1; charset=utf-8,text/plain;version=0.0.4',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '0.0.1',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept both text/plain and openmetrics, prefer openmetrics',
+ accept: 'text/plain,application/openmetrics-text; version=0.0.1; charset=utf-8',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '0.0.1',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept both openmetrics and text/plain, prefer text if preference set',
+ accept: 'application/openmetrics-text; version=0.0.1; charset=utf-8;q=0.5,text/plain;q=0.7',
+ contentType: {
+ mediaType: 'text/plain',
+ version: '0.0.4',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should match version to content-type',
+ accept: 'application/openmetrics-text; version=0.0.1; charset=utf-8, text/plain;version=1.0.0',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '0.0.1',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should set default q to 1.0',
+ accept: 'application/openmetrics-text; version=0.0.1; q=0.5,text/plain;version=0.0.4',
+ contentType: {
+ mediaType: 'text/plain',
+ version: '0.0.4',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should accept mixture of content-types and q',
+ accept: 'application/openmetrics-text; version=0.0.1,text/plain;version=0.0.4;q=0.5,*/*;q=0.1',
+ contentType: {
+ mediaType: 'application/openmetrics-text',
+ version: '0.0.1',
+ charset: 'utf-8'
+ }
+ },
+ {
+ description: 'Should reject Invalid charset',
+ accept: '*/*; charset=utf-16',
+ contentType: null
+ },
+ {
+ description: 'Should reject Invalid openmetrics version',
+ accept: 'application/openmetrics-text; version=0.0.5',
+ contentType: null
+ },
+ {
+ description: 'Should reject Invalid text/plain',
+ accept: 'text/plain; version=0.0.2',
+ contentType: null
+ }
+ ]
+
+ for (const testCase of testCases) {
+ describe(testCase.description, function () {
+ const result = statsRegistry.matchContentType(testCase.accept);
+ if (testCase.contentType) {
+ it('should match', function (done) {
+ should.exist(result);
+ result.mediaType.should.equal(testCase.contentType.mediaType);
+ result.version.should.equal(testCase.contentType.version);
+ result.charset.should.equal(testCase.contentType.charset);
+ done();
+ });
+ } else {
+ it('should not match', function (done) {
+ should.not.exist(result);
+ done();
+ });
+ }
+ });
+ }
+});