From 81c6dd225f052dea9e800bc078197e027ccfdbc1 Mon Sep 17 00:00:00 2001 From: Fermin Galan Marquez Date: Tue, 24 Sep 2019 21:13:09 +0200 Subject: [PATCH 1/4] ADD rebase PR #762 by @dcalvoalonso to master --- CHANGES_NEXT_RELEASE | 2 + lib/plugins/multiEntity.js | 4 +- lib/services/devices/deviceRegistryMemory.js | 11 +- lib/services/devices/deviceRegistryMongoDB.js | 7 +- lib/services/devices/deviceService.js | 30 +- lib/services/devices/registrationUtils.js | 3 +- lib/services/ngsi/ngsiService.js | 2 +- lib/services/northBound/contextServer.js | 468 +++++++++- ...teContext.json => updateContextNgsi1.json} | 0 lib/templates/updateContextNgsi2.json | 38 + .../registerIoTAgent1.json | 6 +- .../registerIoTAgent2.json | 19 + .../registerIoTAgent4.json | 19 + .../registerIoTAgentCommands.json | 19 + .../registerProvisionedDevice.json | 3 +- .../registerProvisionedDevice2.json | 3 +- .../registerProvisionedDeviceWithGroup.json | 3 +- .../registerProvisionedDeviceWithGroup2.json | 3 +- .../registerProvisionedDeviceWithGroup3.json | 3 +- .../updateCommands1.json | 3 +- .../updateIoTAgent1.json | 3 +- .../updateIoTAgent2.json | 3 +- .../updateIoTAgent3.json | 3 +- .../queryInformationResponse.json | 10 + ...eryInformationResponseEmptyAttributes.json | 10 + .../queryInformationResponseWithoutId.json | 30 + ...ueryInformationResponseWithoutIdArray.json | 10 + ...ryInformationStaticAttributesResponse.json | 14 + .../updateInformationResponse2.json | 8 + .../updateContextCommandError.json | 10 + .../updateContextCommandExpired.json | 10 + .../updateContextCommandFinish.json | 10 + .../updateContextCommandStatus.json | 6 + .../updateContextMultientityPlugin1.json | 2 +- .../updateContextMultientityPlugin2.json | 2 +- .../updateContextMultientityPlugin3.json | 2 +- .../updateContextMultientityPlugin4.json | 2 +- .../updateContextMultientityPlugin5.json | 2 +- .../updateContextMultientityPlugin6.json | 2 +- .../updateContextMultientityPlugin7.json | 2 +- ...ateContextMultientityTimestampPlugin1.json | 2 +- ...ateContextMultientityTimestampPlugin2.json | 2 +- ...ateContextMultientityTimestampPlugin3.json | 2 +- ...ateContextMultientityTimestampPlugin4.json | 31 + .../active-devices-attribute-update-test.js | 162 ++++ .../ngsiv2/lazyAndCommands/command-test.js | 311 +++++++ .../lazyAndCommands/lazy-devices-test.js | 812 ++++++++++++++++++ .../lazyAndCommands/polling-commands-test.js | 383 +++++++++ .../ngsiv2/plugins/multientity-plugin_test.js | 64 ++ 49 files changed, 2488 insertions(+), 68 deletions(-) rename lib/templates/{updateContext.json => updateContextNgsi1.json} (100%) create mode 100644 lib/templates/updateContextNgsi2.json create mode 100644 test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json create mode 100644 test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json create mode 100644 test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgentCommands.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponse.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseEmptyAttributes.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationStaticAttributesResponse.json create mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/updateInformationResponse2.json create mode 100644 test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json create mode 100644 test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json create mode 100644 test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json create mode 100644 test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json create mode 100644 test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json create mode 100644 test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js create mode 100644 test/unit/ngsiv2/lazyAndCommands/command-test.js create mode 100644 test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js create mode 100644 test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index a7e6cd5e7..c2fadbb66 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1 +1,3 @@ - Add autoprovision (appendMode) in device and group provosion (#805) +- Add: basic operations of NGSIv2 context forwarding (#527) +- Fix: using "append" instead of "APPEND" (which is deprecated) in NGSIv2 operations diff --git a/lib/plugins/multiEntity.js b/lib/plugins/multiEntity.js index 284f8be34..987a1903a 100644 --- a/lib/plugins/multiEntity.js +++ b/lib/plugins/multiEntity.js @@ -300,9 +300,9 @@ function updateAttributeNgsi1(entity, typeInformation, callback) { function updateAttributeNgsi2(entity, typeInformation, callback) { var entities = []; + entities.push(entity); if (typeInformation.active) { - entities.push(entity); - + var multiEntityAttributes = typeInformation.active.filter(hasEntityName), newEntities = _.pluck(multiEntityAttributes, 'entity_name'), attributesList = _.pluck(multiEntityAttributes, 'name'), diff --git a/lib/services/devices/deviceRegistryMemory.js b/lib/services/devices/deviceRegistryMemory.js index d3ffb28d1..9c6c280cc 100644 --- a/lib/services/devices/deviceRegistryMemory.js +++ b/lib/services/devices/deviceRegistryMemory.js @@ -111,15 +111,20 @@ function getDevicesByService(service, subservice) { * @param {Number} limit Maximum number of entries to return. * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(service, subservice, limit, offset, callback) { +function listDevices(type, service, subservice, limit, offset, callback) { var result = [], skipped = 0, deviceList = getDevicesByService(service, subservice); - + + var countNumber = deviceList.length; for (var i in deviceList) { if (registeredDevices[service].hasOwnProperty(deviceList[i])) { if (offset && skipped < parseInt(offset, 10)) { skipped++; + } else if (type && registeredDevices[service][deviceList[i]].type === type){ + result.push(registeredDevices[service][deviceList[i]]); + } else if (type) { + countNumber--; } else { result.push(registeredDevices[service][deviceList[i]]); } @@ -131,7 +136,7 @@ function listDevices(service, subservice, limit, offset, callback) { } callback(null, { - count: deviceList.length, + count: countNumber, devices: result }); } diff --git a/lib/services/devices/deviceRegistryMongoDB.js b/lib/services/devices/deviceRegistryMongoDB.js index 9bf8ea72a..51f0d0a40 100644 --- a/lib/services/devices/deviceRegistryMongoDB.js +++ b/lib/services/devices/deviceRegistryMongoDB.js @@ -123,15 +123,20 @@ function removeDevice(id, service, subservice, callback) { /** * Return the list of currently registered devices (via callback). * + * @param {String} tyoe Type for which the devices are requested. * @param {String} service Service for which the devices are requested. * @param {String} subservice Subservice inside the service for which the devices are requested. * @param {Number} limit Maximum number of entries to return. * @param {Number} offset Number of entries to skip for pagination. */ -function listDevices(service, subservice, limit, offset, callback) { +function listDevices(type, service, subservice, limit, offset, callback) { var condition = {}, query; + if (type) { + condition.type = type; + } + if (service) { condition.service = service; } diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index 4eb752015..4770f41ba 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -1003,6 +1003,33 @@ function updateRegisterDevice(deviceObj, callback) { } } +/** + * Return a list of all the devices registered in the system. 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). + * + * @param {String} type Type for which the devices are requested. + * @param {String} service Service for which the devices are requested. + * @param {String} subservice Subservice inside the service for which the devices are requested. + * @param {Number} limit Maximum number of entries to return. + * @param {Number} offset Number of entries to skip for pagination. + */ +function listDevicesWithType(type, service, subservice, limit, offset, callback) { + if (!callback) { + if (service && subservice && limit) { + callback = limit; + } else if (service) { + callback = service; + service = null; + subservice = null; + } else { + logger.fatal(context, 'GENERAL-001: Couldn\'t find callback in listDevices() call.'); + } + } + + config.getRegistry().list(type, service, subservice, limit, offset, callback); +} + /** * Return a list of all the devices registered in the system. 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 @@ -1026,7 +1053,7 @@ function listDevices(service, subservice, limit, offset, callback) { } } - config.getRegistry().list(service, subservice, limit, offset, callback); + config.getRegistry().list(null, service, subservice, limit, offset, callback); } /** @@ -1165,6 +1192,7 @@ function retrieveDevice(deviceId, apiKey, callback) { } exports.listDevices = intoTrans(context, checkRegistry)(listDevices); +exports.listDevicesWithType = intoTrans(context, checkRegistry)(listDevicesWithType); exports.getDevice = intoTrans(context, checkRegistry)(getDevice); exports.getDevicesByAttribute = intoTrans(context, checkRegistry)(getDevicesByAttribute); exports.getDeviceByName = intoTrans(context, checkRegistry)(getDeviceByName); diff --git a/lib/services/devices/registrationUtils.js b/lib/services/devices/registrationUtils.js index 3c5cf2537..ea4899ef9 100644 --- a/lib/services/devices/registrationUtils.js +++ b/lib/services/devices/registrationUtils.js @@ -282,8 +282,7 @@ function sendRegistrationsNgsi2(unregister, deviceData, callback) { provider: { http: { url: config.getConfig().providerUrl - }, - legacyForwarding: true + } } }, headers: { diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index 9eab6d687..635498710 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -490,7 +490,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca } options.json = { - actionType: 'APPEND', + actionType: 'append', entities: result }; } else { diff --git a/lib/services/northBound/contextServer.js b/lib/services/northBound/contextServer.js index 05978d35f..d972109eb 100644 --- a/lib/services/northBound/contextServer.js +++ b/lib/services/northBound/contextServer.js @@ -39,7 +39,8 @@ var async = require('async'), context = { op: 'IoTAgentNGSI.ContextServer' }, - updateContextTemplate = require('../../templates/updateContext.json'), + updateContextTemplateNgsi1 = require('../../templates/updateContextNgsi1.json'), + updateContextTemplateNgsi2 = require('../../templates/updateContextNgsi2.json'), queryContextTemplate = require('../../templates/queryContext.json'), notificationTemplateNgsi1 = require('../../templates/notificationTemplateNgsi1.json'), notificationTemplateNgsi2 = require('../../templates/notificationTemplateNgsi2.json'), @@ -180,13 +181,14 @@ function pushCommandsToQueue(device, id, type, service, subservice, attributes, } /** - * Generate all the update actions corresponding to a update context request. Update actions include updates in - * attributes and execution of commands. This action will be called once per Context Element in the request. + * Generate all the update actions corresponding to a update context request using Ngsi1. + * Update actions include updates in attributes and execution of commands. This action will + * be called once per Context Element in the request. * * @param {Object} req Update request to generate Actions from * @param {Object} contextElement Context Element whose actions will be extracted. */ -function generateUpdateActions(req, contextElement, callback) { +function generateUpdateActionsNgsi1(req, contextElement, callback) { function splitUpdates(device, callback) { var attributes = [], commands = [], @@ -293,10 +295,185 @@ function generateUpdateActions(req, contextElement, callback) { } /** - * Express middleware to manage incoming UpdateContext requests. As NGSI10 requests can affect multiple entities, for - * each one of them a call to the user update handler function is made. + * Generate all the update actions corresponding to a update context request using Ngsi2. + * Update actions include updates in attributes and execution of commands. + * + * @param {Object} req Update request to generate Actions from + * @param {Object} contextElement Context Element whose actions will be extracted. */ -function handleUpdate(req, res, next) { +function generateUpdateActionsNgsi2(req, contextElement, callback) { + var entityId; + var entityType; + + if (contextElement.id && contextElement.type) { + entityId = contextElement.id; + entityType = contextElement.type; + }else if (req.params.entity) { + entityId = req.params.entity; + } + + function splitUpdates(device, callback) { + var attributes = [], + commands = [], + found, + newAtt, + i; + + if (device.commands) { + attributeLoop: for (i in contextElement) { + for (var j in device.commands) { + + if (i === device.commands[j].name) { + newAtt = {}; + newAtt[i] = contextElement[i]; + newAtt[i].name = i; + commands.push(newAtt[i]); + found = true; + continue attributeLoop; + } + } + } + + } + + for (i in contextElement) { + if (i !== 'type' && i !== 'id') { + newAtt = {}; + newAtt = contextElement[i]; + newAtt.name = i; + attributes.push(newAtt); + } + } + + callback(null, attributes, commands, device); + } + + function createActionsArray(attributes, commands, device, callback) { + var updateActions = []; + + if(!entityType) { + entityType = device.type; + } + + if (updateHandler) { + updateActions.push( + async.apply( + updateHandler, + entityId, + entityType, + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + attributes) + ); + } + + if (commandHandler) { + if (device.polling) { + updateActions.push( + async.apply( + pushCommandsToQueue, + device, + entityId, + entityType, + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + attributes) + ); + } else { + updateActions.push( + async.apply( + commandHandler, + entityId, + entityType, + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + commands) + ); + } + } + + updateActions.push( + async.apply( + executeUpdateSideEffects, + device, + entityId, + entityType, + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + attributes) + ); + + callback(null, updateActions); + } + + deviceService.getDeviceByName(entityId, req.headers['fiware-service'], req.headers['fiware-servicepath'], + function(error, deviceObj) { + if (error) { + callback(error); + } else { + async.waterfall([ + apply(deviceService.findConfigurationGroup, deviceObj), + apply(deviceService.mergeDeviceWithConfiguration, + [ + 'lazy', + 'internalAttributes', + 'active', + 'staticAttributes', + 'commands', + 'subscriptions' + ], + [null, null, [], [], [], [], []], + deviceObj + ), + splitUpdates, + createActionsArray + ], callback); + } + }); +} + +/** + * Express middleware to manage incoming update context requests using NGSIv2. + */ +function handleUpdateNgsi2(req, res, next) { + function reduceActions(actions, callback) { + callback(null, _.flatten(actions)); + } + + if (updateHandler || commandHandler) { + logger.debug(context, 'Handling update from [%s]', req.get('host')); + logger.debug(context, req.body); + + async.waterfall([ + apply(async.map, req.body.entities, apply(generateUpdateActionsNgsi2, req)), + reduceActions, + async.series + ], function(error, result) { + if (error) { + logger.debug(context, 'There was an error handling the update action: %s.', error); + + next(error); + } else { + logger.debug(context, 'Update action from [%s] handled successfully.', req.get('host')); + res.status(204).json(); + } + }); + } else { + logger.error(context, 'Tried to handle an update request before the update handler was stablished.'); + + var errorNotFound = new Error({ + message: 'Update handler not found' + }); + next(errorNotFound); + } +} + +/** + * Express middleware to manage incoming UpdateContext requests using NGSIv1. + * As NGSI10 requests can affect multiple entities, for each one of them a call + * to the user update handler function is made. + */ +function handleUpdateNgsi1(req, res, next) { function reduceActions(actions, callback) { callback(null, _.flatten(actions)); @@ -307,7 +484,7 @@ function handleUpdate(req, res, next) { logger.debug(context, req.body); async.waterfall([ - apply(async.map, req.body.contextElements, apply(generateUpdateActions, req)), + apply(async.map, req.body.contextElements, apply(generateUpdateActionsNgsi1, req)), reduceActions, async.series ], function(error, result) { @@ -340,7 +517,7 @@ function handleUpdate(req, res, next) { * @param {String} subservice Division inside the service. * @param {Array} attributes List of attributes to read. */ -function defaultQueryHandler(id, type, service, subservice, attributes, callback) { +function defaultQueryHandlerNgsi1(id, type, service, subservice, attributes, callback) { var contextElement = { type: type, isPattern: false, @@ -378,10 +555,55 @@ function defaultQueryHandler(id, type, service, subservice, attributes, callback } /** - * Express middleware to manage incoming QueryContext requests. As NGSI10 requests can affect multiple entities, for - * each one of them a call to the user query handler function is made. + * Handle queries coming to the IoT Agent via de Context Provider API (as a consequence of a query to a passive + * attribute redirected by the Context Broker). + * + * @param {String} id Entity name of the selected entity in the query. + * @param {String} type Type of the entity. + * @param {String} service Service the device belongs to. + * @param {String} subservice Division inside the service. + * @param {Array} attributes List of attributes to read. + */ +function defaultQueryHandlerNgsi2(id, type, service, subservice, attributes, callback) { + var contextElement = { + type: type, + id: id + }; + + deviceService.getDeviceByName(id, service, subservice, function(error, ngsiDevice) { + if (error) { + callback(error); + } else { + for (var i = 0; i < attributes.length; i++) { + var lazyAttribute = _.findWhere(ngsiDevice.lazy, { name: attributes[i] }), + command = _.findWhere(ngsiDevice.commands, { name: attributes[i] }), + attributeType; + + if (command) { + attributeType = command.type; + } else if (lazyAttribute) { + attributeType = lazyAttribute.type; + } else { + attributeType = 'string'; + } + + contextElement[attributes[i]] = { + type: attributeType, + value: '' + }; + } + + callback(null, contextElement); + } + }); +} + +/** + * Express middleware to manage incoming QueryContext requests using NGSIv1. + * As NGSI10 requests can affect multiple entities, for each one of them a call + * to the user query handler function is made. */ -function handleQuery(req, res, next) { +function handleQueryNgsi1(req, res, next) { function getName(element) { return element.name; } @@ -426,7 +648,7 @@ function handleQuery(req, res, next) { if (queryHandler) { actualHandler = queryHandler; } else { - actualHandler = defaultQueryHandler; + actualHandler = defaultQueryHandlerNgsi1; } async.waterfall([ @@ -482,6 +704,174 @@ function handleQuery(req, res, next) { ], handleQueryContextRequests); } +/** + * Express middleware to manage incoming query context requests using NGSIv2. + */ +function handleQueryNgsi2(req, res, next) { + function getName(element) { + return element.name; + } + + function addStaticAttributes(attributes, device, contextElement, callback) { + + function inAttributes(item) { + return item.name && attributes.indexOf(item.name) >= 0; + } + + if (device.staticAttributes) { + + var selectedAttributes = []; + if (attributes === undefined || attributes.length === 0) { + selectedAttributes = device.staticAttributes; + } + else { + selectedAttributes = device.staticAttributes.filter(inAttributes); + } + + for (var att in selectedAttributes) { + contextElement[selectedAttributes[att].name] = { + 'type' : selectedAttributes[att].type, + 'value' : selectedAttributes[att].value + }; + } + } + + callback(null, contextElement); + } + + function completeAttributes(attributes, device, callback) { + if (attributes && attributes.length !== 0) { + logger.debug(context, 'Handling received set of attributes: %j', attributes); + callback(null, attributes); + } else if (device.lazy) { + logger.debug(context, 'Handling stored set of attributes: %j', attributes); + var results = device.lazy.map(getName); + callback(null, results); + } else { + logger.debug(context, 'Couldn\'t find any attributes. Handling with null reference'); + callback(null, null); + } + } + + function finishQueryForDevice(attributes, contextEntity, actualHandler, device, callback) { + var contextId = contextEntity.id; + var contextType = contextEntity.type; + if(!contextId) { + contextId = device.id; + } + + if(!contextType) { + contextType = device.type; + } + + deviceService.findConfigurationGroup(device, function(error, group) { + var executeCompleteAttributes = apply( + completeAttributes, + attributes, + group + ), + executeQueryHandler = apply( + actualHandler, + contextId, + contextType, + req.headers['fiware-service'], + req.headers['fiware-servicepath'] + ), + executeAddStaticAttributes = apply( + addStaticAttributes, + attributes, + group + ); + + async.waterfall([ + executeCompleteAttributes, + executeQueryHandler, + executeAddStaticAttributes + ], callback); + }); + } + + function createQueryRequest(attributes, contextEntity, callback) { + var actualHandler; + var getFunction; + + if (queryHandler) { + actualHandler = queryHandler; + } else { + actualHandler = defaultQueryHandlerNgsi2; + } + + if (contextEntity.id) { + getFunction = apply( + deviceService.getDeviceByName, + contextEntity.id, + req.headers['fiware-service'], + req.headers['fiware-servicepath']); + } else { + getFunction = apply( + deviceService.listDevicesWithType, + contextEntity.type, + req.headers['fiware-service'], + req.headers['fiware-servicepath'], + null, + null); + } + + getFunction(function handleFindDevice(error, innerDevice) { + let deviceList = []; + if (!innerDevice) { + callback(new errors.DeviceNotFound(contextEntity.id)); + } + + if(innerDevice.count) { + if (innerDevice.count === 0) { + return callback(null, []); + } else { + deviceList = innerDevice.devices; + } + } else { + deviceList = [innerDevice]; + } + + async.map(deviceList, async.apply( + finishQueryForDevice, attributes, contextEntity, actualHandler), function (error, results) { + if (error) { + callback(error); + } + else if(innerDevice.count) { + callback(null,results); + } else if(Array.isArray(results) && results.length > 0){ + callback(null, results); + } else { + callback(null, results); + } + }); + }); + + } + + function handleQueryContextRequests(error, result) { + if (error) { + logger.debug(context, 'There was an error handling the query: %s.', error); + next(error); + } else { + logger.debug(context, 'Query from [%s] handled successfully.', req.get('host')); + res.status(200).json(result); + } + } + + logger.debug(context, 'Handling query from [%s]', req.get('host')); + var contextEntity = {}; + contextEntity.id = req.query.id; + contextEntity.type = req.query.type; + var queryAtts = []; + if(req.query.attrs) { + queryAtts = req.query.attrs.split(','); + } + createQueryRequest(queryAtts, contextEntity, handleQueryContextRequests); + +} + function handleNotificationNgsi1(req, res, next) { function checkStatus(statusCode, callback) { @@ -554,7 +944,6 @@ function handleNotificationNgsi1(req, res, next) { } function handleNotificationNgsi2(req, res, next) { - function extractInformation(dataElement, callback) { var atts = []; for (var key in dataElement) { @@ -733,37 +1122,56 @@ function updateErrorHandling(error, req, res, next) { */ function loadContextRoutes(router) { //TODO: remove '//' paths when the appropriate patch comes to Orion - var updateMiddlewares = [ + var updateMiddlewaresNgsi1 = [ middlewares.ensureType, - middlewares.validateJson(updateContextTemplate), - handleUpdate, + middlewares.validateJson(updateContextTemplateNgsi1), + handleUpdateNgsi1, updateErrorHandling ], - queryMiddlewares = [ + updateMiddlewaresNgsi2 = [ + middlewares.ensureType, + middlewares.validateJson(updateContextTemplateNgsi2), + handleUpdateNgsi2, + updateErrorHandling + ], + queryMiddlewaresNgsi1 = [ middlewares.ensureType, middlewares.validateJson(queryContextTemplate), - handleQuery, + handleQueryNgsi1, + queryErrorHandling + ], + queryMiddlewaresNgsi2 = [ + handleQueryNgsi2, queryErrorHandling ], - updatePaths = [ + updatePathsNgsi1 = [ '/v1/updateContext', '/NGSI10/updateContext', '//updateContext' ], - queryPaths = [ + updatePathsNgsi2 = [ + '/v2/op/update' + ], + queryPathsNgsi1 = [ '/v1/queryContext', '/NGSI10/queryContext', '//queryContext' + ], + queryPathsNgsi2 = [ + '/v2/entities', ]; + // In a more evolved implementation, more endpoints could be added to queryPathsNgsi2 + // according to http://fiware.github.io/specifications/ngsiv2/stable. logger.info(context, 'Loading NGSI Contect server routes'); - - for (var i = 0; i < updatePaths.length; i++) { - router.post(updatePaths[i], updateMiddlewares); - router.post(queryPaths[i], queryMiddlewares); - } - + var i; if (config.checkNgsi2()) { + for (i = 0; i < updatePathsNgsi2.length; i++) { + router.post(updatePathsNgsi2[i], updateMiddlewaresNgsi2); + } + for (i = 0; i < queryPathsNgsi2.length; i++) { + router.get(queryPathsNgsi2[i], queryMiddlewaresNgsi2); + } router.post('/notify', [ middlewares.ensureType, middlewares.validateJson(notificationTemplateNgsi2), @@ -771,6 +1179,12 @@ function loadContextRoutes(router) { queryErrorHandling ]); } else { + for (i = 0; i < updatePathsNgsi1.length; i++) { + router.post(updatePathsNgsi1[i], updateMiddlewaresNgsi1); + } + for (i = 0; i < queryPathsNgsi1.length; i++) { + router.post(queryPathsNgsi1[i], queryMiddlewaresNgsi1); + } router.post('/notify', [ middlewares.ensureType, middlewares.validateJson(notificationTemplateNgsi1), diff --git a/lib/templates/updateContext.json b/lib/templates/updateContextNgsi1.json similarity index 100% rename from lib/templates/updateContext.json rename to lib/templates/updateContextNgsi1.json diff --git a/lib/templates/updateContextNgsi2.json b/lib/templates/updateContextNgsi2.json new file mode 100644 index 000000000..8fca57d21 --- /dev/null +++ b/lib/templates/updateContextNgsi2.json @@ -0,0 +1,38 @@ +{ + "type": "object", + "properties": { + "actionType": { + "type": "string", + "enum": ["update"] + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "required": true + }, + "type": { + "type": "string", + "required": true + }, + "additionalProperties":{ + "type": "object", + "properties": { + "type":{ + "type": "string", + "required": true + }, + "value":{ + "type": "string", + "required": true + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json index 1e576e84e..21066cff0 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json @@ -13,7 +13,7 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true - } + } + }, + "expires":"2015-09-05T07:35:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json new file mode 100644 index 000000000..2ae8e9e5e --- /dev/null +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json @@ -0,0 +1,19 @@ +{ + "dataProvided": { + "entities": [ + { + "type": "Motion", + "id": "Motion:motion1" + } + ], + "attrs": [ + "moving" + ] + }, + "provider": { + "http": { + "url": "http://smartGondor.com" + } + }, + "expires":"2015-09-05T07:35:01.468Z" +} \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json new file mode 100644 index 000000000..5c15e5f8f --- /dev/null +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json @@ -0,0 +1,19 @@ +{ + "dataProvided": { + "entities": [ + { + "type": "RobotPre", + "id": "RobotPre:TestRobotPre" + } + ], + "attrs": [ + "moving" + ] + }, + "provider": { + "http": { + "url": "http://smartGondor.com" + } + }, + "expires":"2015-09-05T07:35:01.468Z" +} \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgentCommands.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgentCommands.json new file mode 100644 index 000000000..8832cfad4 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgentCommands.json @@ -0,0 +1,19 @@ +{ + "dataProvided": { + "entities": [ + { + "type": "Robot", + "id": "Robot:r2d2" + } + ], + "attrs": [ + "position" + ] + }, + "provider": { + "http": { + "url": "http://smartGondor.com" + } + }, + "expires":"2015-09-05T07:35:01.468Z" +} \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json index 25386b6b1..c5eafb01f 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice.json @@ -14,7 +14,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice2.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice2.json index 294b10e8a..b337023dd 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice2.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDevice2.json @@ -13,7 +13,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json index 1ba577632..984bb1f8e 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup.json @@ -16,7 +16,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json index 6f02d154a..47cfd12b8 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup2.json @@ -14,8 +14,7 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } }, "expires": "2015-09-05T07:35:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json index f9866fe5a..cececfc8e 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/registerProvisionedDeviceWithGroup3.json @@ -11,8 +11,7 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } }, "expires": "2015-09-05T07:35:01.468Z" } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateCommands1.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateCommands1.json index fb031c146..4dbf358b4 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateCommands1.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateCommands1.json @@ -13,7 +13,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent1.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent1.json index e13204449..d71f1fef7 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent1.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent1.json @@ -13,7 +13,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent2.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent2.json index a76da31c6..47235fd15 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent2.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent2.json @@ -14,7 +14,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent3.json b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent3.json index 35894ff3e..6c19801cb 100644 --- a/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent3.json +++ b/test/unit/ngsiv2/examples/contextAvailabilityRequests/updateIoTAgent3.json @@ -13,7 +13,6 @@ "provider": { "http": { "url": "http://smartGondor.com" - }, - "legacyForwarding": true + } } } \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponse.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponse.json new file mode 100644 index 000000000..ee50ac83a --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponse.json @@ -0,0 +1,10 @@ +[ + { + "id":"Light:light1", + "type":"Light", + "dimming":{ + "type": "Percentage", + "value": 19 + } + } +] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseEmptyAttributes.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseEmptyAttributes.json new file mode 100644 index 000000000..e128794f0 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseEmptyAttributes.json @@ -0,0 +1,10 @@ +[ + { + "id": "Light:light1", + "type": "Light", + "temperature": { + "type": "centigrades", + "value": 19 + } + } +] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json new file mode 100644 index 000000000..4595f5b15 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json @@ -0,0 +1,30 @@ +[ + { + "id":"Light:light1", + "type":"Light", + "dimming":{ + "type": "Percentage", + "value": 19 + } + }, + { + "id": "Motion:motion1", + "type": "Motion", + "moving": { + "type": "Boolean", + "value": true + }, + "location":{ + "type": "Vector", + "value": "(123,523)" + } + }, + { + "id":"RobotPre:TestRobotPre", + "type":"RobotPre", + "moving":{ + "type": "Boolean", + "value": false + } + } +] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json new file mode 100644 index 000000000..ee50ac83a --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json @@ -0,0 +1,10 @@ +[ + { + "id":"Light:light1", + "type":"Light", + "dimming":{ + "type": "Percentage", + "value": 19 + } + } +] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationStaticAttributesResponse.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationStaticAttributesResponse.json new file mode 100644 index 000000000..b1e004243 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationStaticAttributesResponse.json @@ -0,0 +1,14 @@ +[ + { + "id": "Motion:motion1", + "type": "Motion", + "moving": { + "type": "Boolean", + "value": true + }, + "location": { + "type": "Vector", + "value": "(123,523)" + } + } +] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/updateInformationResponse2.json b/test/unit/ngsiv2/examples/contextProviderResponses/updateInformationResponse2.json new file mode 100644 index 000000000..cb132f110 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextProviderResponses/updateInformationResponse2.json @@ -0,0 +1,8 @@ +{ + "id": "RobotPre:TestRobotPre", + "type": "RobotPre", + "moving": { + "type": "string", + "value": "" + } +} \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json new file mode 100644 index 000000000..052db683e --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json @@ -0,0 +1,10 @@ +{ + "position_status": { + "value": "ERROR", + "type": "commandStatus" + }, + "position_info": { + "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 new file mode 100644 index 000000000..9aa4f0a4c --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json @@ -0,0 +1,10 @@ +{ + "position_status": { + "value": "ERROR", + "type": "commandStatus" + }, + "position_info": { + "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 new file mode 100644 index 000000000..02deadef8 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json @@ -0,0 +1,10 @@ +{ + "position_status": { + "value": "FINISHED", + "type": "commandStatus" + }, + "position_info": { + "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 new file mode 100644 index 000000000..5b197f013 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json @@ -0,0 +1,6 @@ +{ + "position_status": { + "type": "commandStatus", + "value": "PENDING" + } +} diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json index b50117161..127bcdde0 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws4", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json index 0659abae1..0882d7223 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws4", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json index ad6668d49..9fb7514e2 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws4", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json index bc26c6e55..a4ab4da8d 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws5", "type": "WeatherStation" diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json index 07e47c45f..b47903efe 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws6", "type": "WeatherStation" diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json index a434f55db..6499ede29 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "Sensor", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json index c1c930d34..5e6d8947c 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "Sensor", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json index 0c2c2511b..4a02a5a38 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws4", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json index 8245b7247..28fb96cfe 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws4", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json index 9a133d1e5..48e9efdb8 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json @@ -1,5 +1,5 @@ { - "actionType": "APPEND", + "actionType": "append", "entities": [ { "id": "ws5", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json new file mode 100644 index 000000000..486e2f940 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json @@ -0,0 +1,31 @@ +{ + "actionType": "append", + "entities": [{ + "id": "sensorCommand", + "type": "SensorCommand", + "PING_status": { + "type": "commandStatus", + "value": "OK", + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2015-08-05T07:35:01.468Z" + } + } + }, + "PING_info": { + "type": "commandResult", + "value": "1234567890", + "metadata": { + "TimeInstant": { + "type": "DateTime", + "value": "2015-08-05T07:35:01.468Z" + } + } + }, + "TimeInstant": { + "type": "DateTime", + "value": "2015-08-05T07:35:01.468Z" + } + }] +} \ No newline at end of file diff --git a/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js b/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js new file mode 100644 index 000000000..9901a3b1f --- /dev/null +++ b/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js @@ -0,0 +1,162 @@ +/* + * 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] + * + * Modified by: Daniel Calvo - ATOS Research & Innovation + */ +'use strict'; + +var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), + should = require('should'), + logger = require('logops'), + nock = require('nock'), + mongoUtils = require('../../mongodb/mongoDBUtils'), + request = require('request'), + contextBrokerMock, + iotAgentConfig = { + logLevel: 'FATAL', + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + 'Light': { + // commands are not defined + active: [ + { + name: 'pressure', + type: 'Hgmm' + } + ] + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S' + }, + device = { + id: 'somelight', + type: 'Light', + service: 'smartGondor', + subservice: 'gardens' + }; + +describe('Update attribute 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/registrations') + .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); + }); + + afterEach(function(done) { + iotAgentLib.clearAll(function() { + iotAgentLib.deactivate(function() { + mongoUtils.cleanDbs(function() { + nock.cleanAll(); + iotAgentLib.setDataUpdateHandler(); + iotAgentLib.setCommandHandler(); + done(); + }); + }); + }); + }); + + describe('When a attribute update arrives to the IoT Agent as Context Provider', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Light:somelight', + type: 'Light', + pressure: { + type: 'Hgmm', + value: 200 + } + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }; + + beforeEach(function(done) { + iotAgentLib.register(device, function(error) { + if (error) { + done('Device registration failed'); + } + done(); + }); + }); + + it('should call the client handler with correct values, even if commands are not defined', function(done) { + var handlerCalled = false; + + iotAgentLib.setDataUpdateHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal('Light:somelight'); + type.should.equal('Light'); + should.exist(attributes); + attributes.length.should.equal(1); + attributes[0].name.should.equal('pressure'); + attributes[0].value.should.equal(200); + handlerCalled = true; + + callback(null, { + id: id, + type: type, + attributes: attributes + }); + }); + + + request(options, function(error, response, body) { + should.not.exist(error); + handlerCalled.should.equal(true); + done(); + }); + }); + }); +}); diff --git a/test/unit/ngsiv2/lazyAndCommands/command-test.js b/test/unit/ngsiv2/lazyAndCommands/command-test.js new file mode 100644 index 000000000..073b641b5 --- /dev/null +++ b/test/unit/ngsiv2/lazyAndCommands/command-test.js @@ -0,0 +1,311 @@ +/* + * 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] + * + * Modified by: Daniel Calvo - ATOS Research & Innovation + */ +'use strict'; + +var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), + utils = require('../../../tools/utils'), + should = require('should'), + logger = require('logops'), + nock = require('nock'), + mongoUtils = require('../../mongodb/mongoDBUtils'), + request = require('request'), + timekeeper = require('timekeeper'), + contextBrokerMock, + statusAttributeMock, + iotAgentConfig = { + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + 'Light': { + commands: [], + lazy: [ + { + name: 'temperature', + type: 'centigrades' + } + ], + active: [ + { + name: 'pressure', + type: 'Hgmm' + } + ] + }, + 'Termometer': { + commands: [], + lazy: [ + { + name: 'temp', + type: 'kelvin' + } + ], + active: [ + ] + }, + 'Motion': { + commands: [], + lazy: [ + { + name: 'moving', + type: 'Boolean' + } + ], + staticAttributes: [ + { + 'name': 'location', + 'type': 'Vector', + 'value': '(123,523)' + } + ], + active: [] + }, + 'Robot': { + commands: [ + { + name: 'position', + type: 'Array' + } + ], + lazy: [], + staticAttributes: [], + active: [] + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S' + }, + device3 = { + id: 'r2d2', + type: 'Robot', + service: 'smartGondor', + subservice: 'gardens' + }; + +describe('Command functionalities', function() { + beforeEach(function(done) { + logger.setLevel('FATAL'); + var time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00 + timekeeper.freeze(time); + 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/registerIoTAgentCommands.json')) + .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); + }); + + afterEach(function(done) { + timekeeper.reset(); + delete(device3.registrationId); + iotAgentLib.clearAll(function() { + iotAgentLib.deactivate(function() { + mongoUtils.cleanDbs(function() { + nock.cleanAll(); + iotAgentLib.setDataUpdateHandler(); + iotAgentLib.setCommandHandler(); + done(); + }); + }); + }); + }); + + describe('When a device is preregistered with commands', function() { + it('should register as Context Provider of the commands', function(done) { + iotAgentLib.register(device3, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + describe('When a command update arrives to the IoT Agent as Context Provider', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Robot:r2d2', + type: 'Robot', + position: { + type: 'Array', + value:'[28, -104, 23]' + } + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }; + + beforeEach(function(done) { + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should call the client handler', function(done) { + var handlerCalled = false; + + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal(device3.type + ':' + device3.id); + type.should.equal(device3.type); + attributes[0].name.should.equal('position'); + attributes[0].value.should.equal('[28, -104, 23]'); + handlerCalled = true; + callback(null, { + id: id, + type: type, + attributes: [ + { + name: 'position', + type: 'Array', + value: '[28, -104, 23]' + } + ] + }); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + handlerCalled.should.equal(true); + 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, { + id: id, + type: type, + attributes: [ + { + name: 'position', + type: 'Array', + value: '[28, -104, 23]' + } + ] + }); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + done(); + }); + }); + it('should create the attribute with the "_status" prefix in the Context Broker', function(done) { + var serviceAndSubservice = false; + + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + serviceAndSubservice = service === 'smartGondor' && subservice === 'gardens'; + callback(null, { + id: id, + type: type, + attributes: [ + { + name: 'position', + type: 'Array', + value: '[28, -104, 23]' + } + ] + }); + }); + + request(options, function(error, response, body) { + serviceAndSubservice.should.equal(true); + done(); + }); + }); + }); + describe('When an update arrives from the south bound for a registered command', function() { + beforeEach(function(done) { + statusAttributeMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities/r2d2/attrs?type=Robot', + utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json')) + .reply(204); + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should update its value and status in the Context Broker', function(done) { + iotAgentLib.setCommandResult('r2d2', 'Robot', '', 'position', '[72, 368, 1]', 'FINISHED', + function(error) { + should.not.exist(error); + statusAttributeMock.done(); + done(); + }); + }); + }); + describe('When an error command arrives from the south bound for a registered command', function() { + beforeEach(function(done) { + statusAttributeMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities/r2d2/attrs?type=Robot', + utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json')) + .reply(204); + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should update its status in the Context Broker', function(done) { + iotAgentLib.setCommandResult('r2d2', 'Robot', '', 'position', 'Stalled', 'ERROR', + function(error) { + should.not.exist(error); + statusAttributeMock.done(); + done(); + }); + }); + }); +}); diff --git a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js new file mode 100644 index 000000000..2f28741d3 --- /dev/null +++ b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js @@ -0,0 +1,812 @@ +/* + * 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] + * + * Modified by: Daniel Calvo - ATOS Research & Innovation + */ +'use strict'; + +var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), + utils = require('../../../tools/utils'), + async = require('async'), + apply = async.apply, + should = require('should'), + logger = require('logops'), + nock = require('nock'), + mongoUtils = require('../../mongodb/mongoDBUtils'), + request = require('request'), + timekeeper = require('timekeeper'), + contextBrokerMock, + iotAgentConfig = { + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + 'Light': { + commands: [], + lazy: [ + { + name: 'temperature', + type: 'centigrades' + } + ], + active: [ + { + name: 'pressure', + type: 'Hgmm' + } + ] + }, + 'Termometer': { + commands: [], + lazy: [ + { + name: 'temp', + type: 'kelvin' + } + ], + active: [ + ] + }, + 'Motion': { + commands: [], + lazy: [ + { + name: 'moving', + type: 'Boolean' + } + ], + staticAttributes: [ + { + 'name': 'location', + 'type': 'Vector', + 'value': '(123,523)' + } + ], + active: [] + }, + 'RobotPre': { + commands: [], + lazy: [ + { + name: 'moving', + type: 'Boolean' + } + ], + staticAttributes: [], + attributes: [], + internalAttributes: { + lwm2mResourceMapping: { + position: { + objectType: 9090, + objectInstance: 0, + objectResource: 0 + } + } + } + } + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S' + }, + device1 = { + id: 'light1', + type: 'Light', + service: 'smartGondor', + subservice: 'gardens' + }, + device2 = { + id: 'motion1', + type: 'Motion', + service: 'smartGondor', + subservice: 'gardens' + }, + device3 = { + id: 'TestRobotPre', + type: 'RobotPre', + service: 'smartGondor', + subservice: 'gardens', + internalAttributes: { + lwm2mResourceMapping: { + position: { + objectType: 6789, + objectInstance: 0, + objectResource: 17 + } + } + } + }; + +describe('IoT Agent Lazy Devices', function() { + beforeEach(function(done) { + logger.setLevel('FATAL'); + + var time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00 + timekeeper.freeze(time); + mongoUtils.cleanDbs(done); + + iotAgentLib.setDataQueryHandler(null); + }); + + afterEach(function(done) { + timekeeper.reset(); + delete(device1.registrationId); + delete(device2.registrationId); + delete(device3.registrationId); + + iotAgentLib.clearAll(function() { + iotAgentLib.deactivate(function() { + mongoUtils.cleanDbs(done); + }); + }); + }); + + describe('When the IoT Agent receives an update on the device data in JSON format', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Light:light1', + type: 'Light', + dimming: { + type: 'Percentage', + value: 12 + } + } + ] + }, + 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', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); + }); + + it('should call the device handler with the received data', function(done) { + + iotAgentLib.setDataUpdateHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal(device1.type + ':' + device1.id); + type.should.equal(device1.type); + attributes[0].value.should.equal(12); + callback(null); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(204); + done(); + }); + }); + }); + + describe('When a IoT Agent receives an update on multiple contexts', function() { + it('should call the device handler for each of the contexts'); + }); + + describe('When a context query arrives to the IoT Agent', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1&attrs=dimming', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiare-servicepath': 'gardens' + } + }, + 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') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=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) { + var expectedResponse = utils + .readExampleFile('./test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponse.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal(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() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1&attrs=dimming', + method: 'GET', + 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', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], function(error) { + done(); + }); + }); + + it('should not give any error', function(done) { + request(options, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + done(); + }); + }); + + it('should return the empty value', function(done) { + request(options, function(error, response, body) { + var entities = JSON.parse(body); + entities[0].dimming.value.should.equal(''); + done(); + }); + }); + }); + + describe('When a query arrives to the IoT Agent without any attributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }, + sensorData = [ + { + id: 'Light:light1', + type: 'Light', + temperature: + { + type: 'centigrades', + value: 19 + } + } + ]; + + beforeEach(function(done) { + 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/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); + }); + + it('should return the information of all the attributes', function(done) { + var expectedResponse = utils.readExampleFile( + './test/unit/ngsiv2/examples/contextProviderResponses/' + + 'queryInformationResponseEmptyAttributes.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + should.exist(attributes); + attributes.length.should.equal(1); + attributes[0].should.equal('temperature'); + 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 for a type with static attributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + + '/v2/entities?id=Motion:motion1&attrs=moving,location', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }, + sensorData = [ + { + id: 'Motion:motion1', + type: 'Motion', + 'moving': + { + type: 'Boolean', + value: true + } + } + ]; + + beforeEach(function(done) { + 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/registerIoTAgent2.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device2) + ], done); + }); + + it('should return the information adding the static attributes', function(done) { + var expectedResponse = utils.readExampleFile( + './test/unit/ngsiv2/examples/contextProviderResponses/queryInformationStaticAttributesResponse.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal('Motion:motion1'); + type.should.equal('Motion'); + attributes[0].should.equal('moving'); + attributes[1].should.equal('location'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); + + describe('When the IoT Agent receives an update on the device data in JSON format for a type with' + + 'internalAttributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'RobotPre:TestRobotPre', + type: 'RobotPre', + moving: { + type: 'Boolean', + value: 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/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device3) + ], done); + }); + + it('should call the device handler with the received data', function(done) { + + iotAgentLib.setDataUpdateHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal(device3.type + ':' + device3.id); + type.should.equal(device3.type); + attributes[0].value.should.equal(true); + callback(null); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(204); + done(); + }); + }); + }); + + describe('When a context query arrives to the IoT Agent and id query param is not present', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiare-servicepath': 'gardens' + } + }, + sensorData = [ + { + id: 'Light:light1', + type: 'Light', + dimming: + { + type: 'Percentage', + value: 19 + } + }, { + id: 'Motion:motion1', + type: 'Motion', + moving: + { + type: 'Boolean', + value: true + } + }, { + id: 'RobotPre:TestRobotPre', + type: 'RobotPre', + moving: + { + type: 'Boolean', + value: false + } + } + ]; + + beforeEach(function(done) { + 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/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .times(3) + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1), + apply(iotAgentLib.register, device2), + apply(iotAgentLib.register, device3), + ], done); + }); + + it('should return the information querying the underlying devices', function(done) { + var expectedResponse = utils + .readExampleFile( + './test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json'); + var queryHandlerCalls = 0; + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + queryHandlerCalls++; + if (queryHandlerCalls === 1) { + id.should.equal(device1.id); + type.should.equal(device1.type); + attributes[0].should.equal('temperature'); + callback(null, sensorData[0]); + } else if(queryHandlerCalls === 2) { + id.should.equal(device2.id); + type.should.equal(device2.type); + attributes[0].should.equal('moving'); + callback(null, sensorData[1]); + } else if (queryHandlerCalls === 3) { + id.should.equal(device3.id); + type.should.equal(device3.type); + attributes[0].should.equal('moving'); + callback(null, sensorData[2]); + } + + }); + + 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 id query param is not present', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?type=Light', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiare-servicepath': 'gardens' + } + }, + 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') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent2.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/registrations', + utils.readExampleFile( + './test/unit/ngsiv2/examples/contextAvailabilityRequests/registerIoTAgent4.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .times(3) + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1), + apply(iotAgentLib.register, device2), + apply(iotAgentLib.register, device3), + ], done); + }); + + it('should return the information querying the underlying devices', function(done) { + var expectedResponse = utils + .readExampleFile( + './test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + id.should.equal(device1.id); + type.should.equal(device1.type); + attributes[0].should.equal('temperature'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); + + describe('When a query arrives to the IoT Agent with id, type and attributes', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + + '/v2/entities?id=Light:light1&type=Light&attrs=temperature', + method: 'GET', + json: true, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }, + sensorData = [ + { + id: 'Light:light1', + type: 'Light', + temperature: + { + type: 'centigrades', + value: 19 + } + } + ]; + + beforeEach(function(done) { + 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/registerIoTAgent1.json')) + .reply(201, null, {'Location': '/v2/registrations/6319a7f5254b05844116584d'}); + + contextBrokerMock + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities?options=upsert') + .reply(204); + + async.series([ + apply(iotAgentLib.activate, iotAgentConfig), + apply(iotAgentLib.register, device1) + ], done); + }); + + it('should return the information of all the attributes', function(done) { + var expectedResponse = utils.readExampleFile( + './test/unit/ngsiv2/examples/contextProviderResponses/' + + 'queryInformationResponseEmptyAttributes.json'); + + iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { + should.exist(attributes); + attributes.length.should.equal(1); + attributes[0].should.equal('temperature'); + callback(null, sensorData[0]); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + body.should.eql(expectedResponse); + done(); + }); + }); + }); +}); diff --git a/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js b/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js new file mode 100644 index 000000000..a3c8e6aba --- /dev/null +++ b/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js @@ -0,0 +1,383 @@ +/* + * 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, 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: Daniel Calvo - ATOS Research & Innovation + */ +'use strict'; + +var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), + utils = require('../../../tools/utils'), + should = require('should'), + logger = require('logops'), + nock = require('nock'), + mongoUtils = require('../../mongodb/mongoDBUtils'), + request = require('request'), + contextBrokerMock, + statusAttributeMock, + iotAgentConfig = { + contextBroker: { + host: '192.168.1.1', + port: '1026', + ngsiVersion: 'v2' + }, + server: { + port: 4041 + }, + types: { + 'Light': { + commands: [], + lazy: [ + { + name: 'temperature', + type: 'centigrades' + } + ], + active: [ + { + name: 'pressure', + type: 'Hgmm' + } + ] + }, + 'Termometer': { + commands: [], + lazy: [ + { + name: 'temp', + type: 'kelvin' + } + ], + active: [ + ] + }, + 'Motion': { + commands: [], + lazy: [ + { + name: 'moving', + type: 'Boolean' + } + ], + staticAttributes: [ + { + 'name': 'location', + 'type': 'Vector', + 'value': '(123,523)' + } + ], + active: [] + }, + 'Robot': { + commands: [ + { + name: 'position', + type: 'Array' + } + ], + lazy: [], + staticAttributes: [], + active: [] + } + }, + deviceRegistry: { + type: 'mongodb' + }, + + mongodb: { + host: 'localhost', + port: '27017', + db: 'iotagent' + }, + service: 'smartGondor', + subservice: 'gardens', + providerUrl: 'http://smartGondor.com', + deviceRegistrationDuration: 'P1M', + throttling: 'PT5S', + pollingExpiration: 200, + pollingDaemonFrequency: 20 + }, + device3 = { + id: 'r2d2', + type: 'Robot', + service: 'smartGondor', + subservice: 'gardens', + polling: true + }; + +describe('Polling commands', 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/registrations') + .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); + }); + + afterEach(function(done) { + delete(device3.registrationId); + iotAgentLib.clearAll(function() { + iotAgentLib.deactivate(function() { + mongoUtils.cleanDbs(function() { + nock.cleanAll(); + iotAgentLib.setDataUpdateHandler(); + iotAgentLib.setCommandHandler(); + done(); + }); + }); + }); + }); + + describe('When a command update arrives to the IoT Agent for a device with polling', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Robot:r2d2', + type: 'Robot', + position: { + type: 'Array', + value: '[28, -104, 23]' + } + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }; + + 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); + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should not call the client handler', function(done) { + var handlerCalled = false; + + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + handlerCalled = true; + callback(null, { + id: id, + type: type, + attributes: [ + { + name: 'position', + type: 'Array', + value: '[28, -104, 23]' + } + ] + }); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + handlerCalled.should.equal(false); + 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); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + statusAttributeMock.done(); + done(); + }); + }); + it('should store the commands in the queue', function(done) { + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + callback(null); + }); + + request(options, function(error, response, body) { + iotAgentLib.commandQueue('smartGondor', 'gardens', 'r2d2', function(error, listCommands) { + should.not.exist(error); + listCommands.count.should.equal(1); + listCommands.commands[0].name.should.equal('position'); + listCommands.commands[0].type.should.equal('Array'); + listCommands.commands[0].value.should.equal('[28, -104, 23]'); + done(); + }); + }); + }); + }); + + describe('When a command arrives with multiple values in the value field', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Robot:r2d2', + type: 'Robot', + position: { + type: 'Array', + value: { + attr1: 12, + attr2: 24 + } + } + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }; + + 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); + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should return a 200 OK both in HTTP and in the status code', function(done) { + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + callback(null); + }); + + request(options, function(error, response, body) { + should.not.exist(error); + + response.statusCode.should.equal(204); + + done(); + }); + }); + }); + + describe('When a polling command expires', function() { + var options = { + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update', + method: 'POST', + json: { + actionType: 'update', + entities: [ + { + id: 'Robot:r2d2', + type: 'Robot', + position: { + type: 'Array', + value: '[28, -104, 23]' + } + } + ] + }, + headers: { + 'fiware-service': 'smartGondor', + 'fiware-servicepath': 'gardens' + } + }; + + 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') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/entities/Robot:r2d2/attrs?type=Robot', + utils.readExampleFile( + './test/unit//ngsiv2/examples/contextRequests/updateContextCommandExpired.json')) + .reply(204); + + iotAgentLib.register(device3, function(error) { + done(); + }); + }); + + it('should remove it from the queue', function(done) { + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + callback(null); + }); + + request(options, function(error, response, body) { + setTimeout(function() { + iotAgentLib.commandQueue('smartGondor', 'gardens', 'r2d2', function(error, listCommands) { + should.not.exist(error); + listCommands.count.should.equal(0); + done(); + }); + }, 300); + }); + }); + + it('should mark it as ERROR in the Context Broker', function(done) { + iotAgentLib.setCommandHandler(function(id, type, service, subservice, attributes, callback) { + callback(null); + }); + + request(options, function(error, response, body) { + setTimeout(function() { + iotAgentLib.commandQueue('smartGondor', 'gardens', 'r2d2', function(error, listCommands) { + statusAttributeMock.done(); + done(); + }); + }, 300); + }); + }); + }); +}); diff --git a/test/unit/ngsiv2/plugins/multientity-plugin_test.js b/test/unit/ngsiv2/plugins/multientity-plugin_test.js index e1200b3b1..c64edc16a 100644 --- a/test/unit/ngsiv2/plugins/multientity-plugin_test.js +++ b/test/unit/ngsiv2/plugins/multientity-plugin_test.js @@ -32,6 +32,7 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), logger = require('logops'), nock = require('nock'), moment = require('moment'), + timekeeper = require('timekeeper'), contextBrokerMock, iotAgentConfig = { contextBroker: { @@ -181,6 +182,16 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), ] }, + 'SensorCommand':{ + commands: [ + { + name: 'PING', + type: 'command' + } + ], + type: 'SensorCommand', + lazy: [] + } }, service: 'smartGondor', @@ -624,3 +635,56 @@ describe('Multi-entity plugin is executed before timestamp process plugin', func }); }); +describe('Multi-entity plugin is executed for a command update for a regular entity ', function () { + beforeEach(function(done) { + logger.setLevel('FATAL'); + + iotAgentConfig.timestamp = true; + var time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00 + timekeeper.freeze(time); + iotAgentLib.activate(iotAgentConfig, function() { + iotAgentLib.clearAll(function() { + iotAgentLib.addUpdateMiddleware(iotAgentLib.dataPlugins.attributeAlias.update); + iotAgentLib.addQueryMiddleware(iotAgentLib.dataPlugins.attributeAlias.query); + iotAgentLib.addUpdateMiddleware(iotAgentLib.dataPlugins.multiEntity.update); + iotAgentLib.addUpdateMiddleware(iotAgentLib.dataPlugins.timestampProcess.update); + done(); + }); + }); + }); + + afterEach(function(done) { + timekeeper.reset(); + iotAgentLib.clearAll(function() { + iotAgentLib.deactivate(done); + }); + }); + + it('Should send the update to the context broker', function(done) { + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartGondor') + .matchHeader('fiware-servicepath', 'gardens') + .post('/v2/op/update', utils.readExampleFile('./test/unit/ngsiv2/examples' + + '/contextRequests/updateContextMultientityTimestampPlugin4.json')) + .reply(204); + var commands = [ + { + name: 'PING_status', + type: 'commandStatus', + value: 'OK', + }, + { + name: 'PING_info', + type: 'commandResult', + value:'1234567890' + } + ]; + + iotAgentLib.update('sensorCommand', 'SensorCommand', '', commands, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); +}); \ No newline at end of file From 081f2a4e9e33811d6b60dd3b457312d20ff06515 Mon Sep 17 00:00:00 2001 From: Fermin Galan Marquez Date: Tue, 24 Sep 2019 21:14:06 +0200 Subject: [PATCH 2/4] ADD PR #8 (in @dcalvoalonso fork) by @fgalan to the branch --- lib/services/devices/deviceService.js | 12 +- lib/services/northBound/contextServer.js | 80 +++++++--- .../unit/lazyAndCommands/lazy-devices-test.js | 2 +- .../queryInformationResponseWithoutId.json | 30 ---- ...ueryInformationResponseWithoutIdArray.json | 10 -- .../lazyAndCommands/lazy-devices-test.js | 143 ++++++++++-------- 6 files changed, 155 insertions(+), 122 deletions(-) delete mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json delete mode 100644 test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index 4770f41ba..317131344 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -508,9 +508,15 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) { }; } - logger.debug(context, 'Updating entity in the Context Broker:\n %s', JSON.stringify(options, null, 4)); - - request(options, updateEntityHandlerNgsi2(deviceData, updatedDevice, callback)); + // FIXME: maybe there is be a better way to theck options.json = {} + if (Object.keys(options.json).length === 0 && options.json.constructor === Object) { + logger.debug(context, 'Skip updating entity in the Context Broker (no actual attribute change)'); + callback(null, updatedDevice); + } + else{ + logger.debug(context, 'Updating entity in the Context Broker:\n %s', JSON.stringify(options, null, 4)); + request(options, updateEntityHandlerNgsi2(deviceData, updatedDevice, callback)); + } } /** diff --git a/lib/services/northBound/contextServer.js b/lib/services/northBound/contextServer.js index d972109eb..711d921c5 100644 --- a/lib/services/northBound/contextServer.js +++ b/lib/services/northBound/contextServer.js @@ -820,7 +820,7 @@ function handleQueryNgsi2(req, res, next) { getFunction(function handleFindDevice(error, innerDevice) { let deviceList = []; if (!innerDevice) { - callback(new errors.DeviceNotFound(contextEntity.id)); + return callback(new errors.DeviceNotFound(contextEntity.id)); } if(innerDevice.count) { @@ -862,12 +862,26 @@ function handleQueryNgsi2(req, res, next) { logger.debug(context, 'Handling query from [%s]', req.get('host')); var contextEntity = {}; - contextEntity.id = req.query.id; - contextEntity.type = req.query.type; - var queryAtts = []; - if(req.query.attrs) { - queryAtts = req.query.attrs.split(','); + + // At the present moment, IOTA supports query request with one entity and without patterns. This is aligned + // with the utilization cases in combination with ContextBroker. Other cases are returned as error + if (req.body.entities.length != 1) + { + logger.warn('queries with entities number different to 1 are not supported (%d found)', + req.body.entities.length); + handleQueryContextRequests({code: 400, name: 'BadRequest', message: 'more than one entity in query'}); + return; + } + if (req.body.entities[0].idPattern) + { + logger.warn('queries with idPattern are not supported'); + handleQueryContextRequests({code: 400, name: 'BadRequest', message: 'idPattern usage in query'}); + return; } + + contextEntity.id = req.body.entities[0].id; + contextEntity.type = req.body.entities[0].type; + var queryAtts = req.body.attrs; createQueryRequest(queryAtts, contextEntity, handleQueryContextRequests); } @@ -1072,10 +1086,10 @@ function setNotificationHandler(newHandler) { notificationHandler = newHandler; } -function queryErrorHandling(error, req, res, next) { +function queryErrorHandlingNgsi1(error, req, res, next) { var code = 500; - logger.debug(context, 'Query error [%s] handling request: %s', error.name, error.message); + logger.debug(context, 'Query NGSIv1 error [%s] handling request: %s', error.name, error.message); if (error.code && String(error.code).match(/^[2345]\d\d$/)) { code = error.code; @@ -1090,10 +1104,25 @@ function queryErrorHandling(error, req, res, next) { }); } -function updateErrorHandling(error, req, res, next) { +function queryErrorHandlingNgsi2(error, req, res, next) { var code = 500; - logger.debug(context, 'Update error [%s] handing request: %s', error.name, error.message); + logger.debug(context, 'Query NGSIv2 error [%s] handling request: %s', error.name, error.message); + + if (error.code && String(error.code).match(/^[2345]\d\d$/)) { + code = error.code; + } + + res.status(code).json({ + error: error.name, + description: error.message.replace(/[<>\"\'=;\(\)]/g, '') + }); +} + +function updateErrorHandlingNgsi1(error, req, res, next) { + var code = 500; + + logger.debug(context, 'Update NGSIv1 error [%s] handing request: %s', error.name, error.message); if (error.code && String(error.code).match(/^[2345]\d\d$/)) { code = error.code; @@ -1115,6 +1144,21 @@ function updateErrorHandling(error, req, res, next) { ); } +function updateErrorHandlingNgsi2(error, req, res, next) { + var code = 500; + + logger.debug(context, 'Update NGSIv2 error [%s] handing request: %s', error.name, error.message); + + if (error.code && String(error.code).match(/^[2345]\d\d$/)) { + code = error.code; + } + + res.status(code).json({ + error: error.name, + description: error.message.replace(/[<>\"\'=;\(\)]/g, '') + }); +} + /** * Load the routes related to context dispatching (NGSI10 calls). * @@ -1126,23 +1170,23 @@ function loadContextRoutes(router) { middlewares.ensureType, middlewares.validateJson(updateContextTemplateNgsi1), handleUpdateNgsi1, - updateErrorHandling + updateErrorHandlingNgsi1 ], updateMiddlewaresNgsi2 = [ middlewares.ensureType, middlewares.validateJson(updateContextTemplateNgsi2), handleUpdateNgsi2, - updateErrorHandling + updateErrorHandlingNgsi2 ], queryMiddlewaresNgsi1 = [ middlewares.ensureType, middlewares.validateJson(queryContextTemplate), handleQueryNgsi1, - queryErrorHandling + queryErrorHandlingNgsi1 ], queryMiddlewaresNgsi2 = [ handleQueryNgsi2, - queryErrorHandling + queryErrorHandlingNgsi2 ], updatePathsNgsi1 = [ '/v1/updateContext', @@ -1158,7 +1202,7 @@ function loadContextRoutes(router) { '//queryContext' ], queryPathsNgsi2 = [ - '/v2/entities', + '/v2/op/query', ]; // In a more evolved implementation, more endpoints could be added to queryPathsNgsi2 // according to http://fiware.github.io/specifications/ngsiv2/stable. @@ -1170,13 +1214,13 @@ function loadContextRoutes(router) { router.post(updatePathsNgsi2[i], updateMiddlewaresNgsi2); } for (i = 0; i < queryPathsNgsi2.length; i++) { - router.get(queryPathsNgsi2[i], queryMiddlewaresNgsi2); + router.post(queryPathsNgsi2[i], queryMiddlewaresNgsi2); } router.post('/notify', [ middlewares.ensureType, middlewares.validateJson(notificationTemplateNgsi2), handleNotificationNgsi2, - queryErrorHandling + queryErrorHandlingNgsi2 ]); } else { for (i = 0; i < updatePathsNgsi1.length; i++) { @@ -1189,7 +1233,7 @@ function loadContextRoutes(router) { middlewares.ensureType, middlewares.validateJson(notificationTemplateNgsi1), handleNotificationNgsi1, - queryErrorHandling + queryErrorHandlingNgsi1 ]); } } diff --git a/test/unit/lazyAndCommands/lazy-devices-test.js b/test/unit/lazyAndCommands/lazy-devices-test.js index a5fc743c7..eba23db2e 100644 --- a/test/unit/lazyAndCommands/lazy-devices-test.js +++ b/test/unit/lazyAndCommands/lazy-devices-test.js @@ -249,7 +249,7 @@ describe('IoT Agent Lazy Devices', function() { }, headers: { 'fiware-service': 'smartGondor', - 'fiare-servicepath': 'gardens' + 'fiware-servicepath': 'gardens' } }, sensorData = [ diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json deleted file mode 100644 index 4595f5b15..000000000 --- a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "id":"Light:light1", - "type":"Light", - "dimming":{ - "type": "Percentage", - "value": 19 - } - }, - { - "id": "Motion:motion1", - "type": "Motion", - "moving": { - "type": "Boolean", - "value": true - }, - "location":{ - "type": "Vector", - "value": "(123,523)" - } - }, - { - "id":"RobotPre:TestRobotPre", - "type":"RobotPre", - "moving":{ - "type": "Boolean", - "value": false - } - } -] \ No newline at end of file diff --git a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json b/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json deleted file mode 100644 index ee50ac83a..000000000 --- a/test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "id":"Light:light1", - "type":"Light", - "dimming":{ - "type": "Percentage", - "value": 19 - } - } -] \ No newline at end of file diff --git a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js index 2f28741d3..95fef89b0 100644 --- a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +++ b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js @@ -236,12 +236,20 @@ describe('IoT Agent Lazy Devices', function() { describe('When a context query arrives to the IoT Agent', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1&attrs=dimming', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', - 'fiare-servicepath': 'gardens' + 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + id: 'Light:light1' + } + ], + attrs: [ 'dimming' ] } }, sensorData = [ @@ -300,11 +308,20 @@ describe('IoT Agent Lazy Devices', function() { describe('When a context query arrives to the IoT Agent and no handler is set', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1&attrs=dimming', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', + json: true, headers: { 'fiware-service': 'smartGondor', 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + id: 'Light:light1' + } + ], + attrs: [ 'dimming' ] } }; @@ -343,7 +360,7 @@ describe('IoT Agent Lazy Devices', function() { it('should return the empty value', function(done) { request(options, function(error, response, body) { - var entities = JSON.parse(body); + var entities = body; entities[0].dimming.value.should.equal(''); done(); }); @@ -352,12 +369,19 @@ describe('IoT Agent Lazy Devices', function() { describe('When a query arrives to the IoT Agent without any attributes', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?id=Light:light1', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + id: 'Light:light1' + } + ] } }, sensorData = [ @@ -417,13 +441,20 @@ describe('IoT Agent Lazy Devices', function() { describe('When a context query arrives to the IoT Agent for a type with static attributes', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + - '/v2/entities?id=Motion:motion1&attrs=moving,location', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + id: 'Motion:motion1' + } + ], + attrs: [ 'moving', 'location'] } }, sensorData = [ @@ -545,14 +576,21 @@ describe('IoT Agent Lazy Devices', function() { }); }); - describe('When a context query arrives to the IoT Agent and id query param is not present', function() { + describe('When a context query arrives to the IoT Agent and id and type query params are not present', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', - 'fiare-servicepath': 'gardens' + 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + idPattern: '.*' + } + ] } }, sensorData = [ @@ -625,36 +663,13 @@ describe('IoT Agent Lazy Devices', function() { ], done); }); - it('should return the information querying the underlying devices', function(done) { - var expectedResponse = utils - .readExampleFile( - './test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutId.json'); - var queryHandlerCalls = 0; - - iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { - queryHandlerCalls++; - if (queryHandlerCalls === 1) { - id.should.equal(device1.id); - type.should.equal(device1.type); - attributes[0].should.equal('temperature'); - callback(null, sensorData[0]); - } else if(queryHandlerCalls === 2) { - id.should.equal(device2.id); - type.should.equal(device2.type); - attributes[0].should.equal('moving'); - callback(null, sensorData[1]); - } else if (queryHandlerCalls === 3) { - id.should.equal(device3.id); - type.should.equal(device3.type); - attributes[0].should.equal('moving'); - callback(null, sensorData[2]); - } - - }); + it('should return error as idPattern is not supported', function(done) { request(options, function(error, response, body) { should.not.exist(error); - body.should.eql(expectedResponse); + response.statusCode.should.equal(400); + body.error.should.equal('BadRequest'); + body.description.should.equal('idPattern usage in query'); done(); }); }); @@ -662,12 +677,20 @@ describe('IoT Agent Lazy Devices', function() { describe('When a context query arrives to the IoT Agent and id query param is not present', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/entities?type=Light', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', - 'fiare-servicepath': 'gardens' + 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + idPattern: '.*', + type: 'Light' + } + ] } }, sensorData = [ @@ -724,21 +747,13 @@ describe('IoT Agent Lazy Devices', function() { ], done); }); - it('should return the information querying the underlying devices', function(done) { - var expectedResponse = utils - .readExampleFile( - './test/unit/ngsiv2/examples/contextProviderResponses/queryInformationResponseWithoutIdArray.json'); - - iotAgentLib.setDataQueryHandler(function(id, type, service, subservice, attributes, callback) { - id.should.equal(device1.id); - type.should.equal(device1.type); - attributes[0].should.equal('temperature'); - callback(null, sensorData[0]); - }); + it('should return error as idPattern is not supported', function(done) { request(options, function(error, response, body) { should.not.exist(error); - body.should.eql(expectedResponse); + response.statusCode.should.equal(400); + body.error.should.equal('BadRequest'); + body.description.should.equal('idPattern usage in query'); done(); }); }); @@ -746,13 +761,21 @@ describe('IoT Agent Lazy Devices', function() { describe('When a query arrives to the IoT Agent with id, type and attributes', function() { var options = { - url: 'http://localhost:' + iotAgentConfig.server.port + - '/v2/entities?id=Light:light1&type=Light&attrs=temperature', - method: 'GET', + url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/query', + method: 'POST', json: true, headers: { 'fiware-service': 'smartGondor', 'fiware-servicepath': 'gardens' + }, + body: { + entities: [ + { + id: 'Light:light1', + type: 'Light' + } + ], + attrs: [ 'temperature' ] } }, sensorData = [ From 525fb0bbc297d250aacb6b221c43f8616aa73682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 25 Sep 2019 17:47:01 +0200 Subject: [PATCH 3/4] FIX lint issue --- lib/services/northBound/contextServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/northBound/contextServer.js b/lib/services/northBound/contextServer.js index 711d921c5..f37d77529 100644 --- a/lib/services/northBound/contextServer.js +++ b/lib/services/northBound/contextServer.js @@ -865,7 +865,7 @@ function handleQueryNgsi2(req, res, next) { // At the present moment, IOTA supports query request with one entity and without patterns. This is aligned // with the utilization cases in combination with ContextBroker. Other cases are returned as error - if (req.body.entities.length != 1) + if (req.body.entities.length !== 1) { logger.warn('queries with entities number different to 1 are not supported (%d found)', req.body.entities.length); From c09c3b50c8f52ed203a3707987ca1dd8ae4de88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 25 Sep 2019 20:23:25 +0200 Subject: [PATCH 4/4] REMOVE unused variables to finx lint --- .../lazyAndCommands/lazy-devices-test.js | 42 +------------------ 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js index 95fef89b0..1e1bf4d71 100644 --- a/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +++ b/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js @@ -592,34 +592,7 @@ describe('IoT Agent Lazy Devices', function() { } ] } - }, - sensorData = [ - { - id: 'Light:light1', - type: 'Light', - dimming: - { - type: 'Percentage', - value: 19 - } - }, { - id: 'Motion:motion1', - type: 'Motion', - moving: - { - type: 'Boolean', - value: true - } - }, { - id: 'RobotPre:TestRobotPre', - type: 'RobotPre', - moving: - { - type: 'Boolean', - value: false - } - } - ]; + }; beforeEach(function(done) { nock.cleanAll(); @@ -692,18 +665,7 @@ describe('IoT Agent Lazy Devices', function() { } ] } - }, - sensorData = [ - { - id: 'Light:light1', - type: 'Light', - dimming: - { - type: 'Percentage', - value: 19 - } - } - ]; + }; beforeEach(function(done) { nock.cleanAll();