diff --git a/.jshintrc b/.jshintrc index bf36e3197..517729321 100644 --- a/.jshintrc +++ b/.jshintrc @@ -17,6 +17,7 @@ "node": true, "expr": true, "unused": "vars", + "esversion": 6, "globals": { "describe":true, "it": true, diff --git a/.travis.yml b/.travis.yml index 7ec7b8646..70cf8a644 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,3 +19,6 @@ install: before_install: - npm update -q +before_script: + - npm run lint + diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 518b28786..b5f519f61 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,4 +1,5 @@ Fix: use trust and cbHost from deviceGroup (#685) +Fix: multientity multimeasure with the same att name Add missed conf env vars about authentication (#704) Add timestamp in device and group provision (#655) Fix: missing support for device=true in the delete service endpoint (see #596) @@ -23,3 +24,5 @@ Fix: updating dependencies due to known vulnerabilities in the previous ones Remove: old unused dependencies (sax, grunt, closure-linter-wrapper) Fix: mosquitto.conf.example file not found by iot/mosquitto Dockerfile (#554) Fix: isDomain is not used anymore for context availability registration (#701) +Fix: checks ISO8601 timeinstants provided by devices (#679) +Fix: corrects linting and includes npm run lint in travis diff --git a/lib/errors.js b/lib/errors.js index e96a13cde..e962c6091 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -19,6 +19,8 @@ * * For those usages not covered by the GNU Affero General Public License * please contact with::daniel.moranjimenez@telefonica.com + * + * Modified by: Daniel Calvo - ATOS Research & Innovation */ 'use strict'; @@ -168,5 +170,10 @@ module.exports = { this.name = 'BAD_ANSWER'; this.message = 'Invalid statusCode in operation [' + operation + ']'; this.code = 400; + }, + BadTimestamp: function(payload) { + this.name = 'BAD_TIMESTAMP'; + this.message = 'Invalid ISO8601 timestamp [' + payload + ']'; + this.code = 400; } }; diff --git a/lib/plugins/attributeAlias.js b/lib/plugins/attributeAlias.js index 0073e3a7a..2e7fb9b92 100644 --- a/lib/plugins/attributeAlias.js +++ b/lib/plugins/attributeAlias.js @@ -27,7 +27,9 @@ var config = require('../commonConfig'), utils = require('./pluginUtils'), + /*jshint unused:false*/ logger = require('logops'), + /*jshint unused:false*/ context = { op: 'IoTAgentNGSI.attributeAlias' }, @@ -38,10 +40,7 @@ function extractSingleMapping(previous, current) { previous.direct[current.object_id] = current.name; previous.types[current.object_id] = current.type; - if (!previous.inverse[current.name]) { - previous.inverse[current.name] = []; - } - previous.inverse[current.name].push(current.object_id); // collision using multientity + previous.inverse[current.name] = current.object_id; // collision using multientity return previous; } @@ -79,6 +78,7 @@ function applyAlias(mappings) { return function aliasApplier(attribute) { if (mappings.direct[attribute.name]) { if (config.checkNgsi2()) { + /*jshint camelcase: false */ attribute.object_id = attribute.name; // inverse not usefull due to collision } attribute.type = mappings.types[attribute.name]; diff --git a/lib/plugins/expressionParser.js b/lib/plugins/expressionParser.js index 4bc960c4c..faf9771f4 100644 --- a/lib/plugins/expressionParser.js +++ b/lib/plugins/expressionParser.js @@ -175,6 +175,8 @@ function expressionApplier(context, typeInformation) { name: attribute.name, type: attribute.type }; + + /*jshint camelcase: false */ if (config.checkNgsi2() && attribute.object_id) { newAttribute.object_id = attribute.object_id; } diff --git a/lib/plugins/expressionPlugin.js b/lib/plugins/expressionPlugin.js index d726063a8..ae5349f6d 100644 --- a/lib/plugins/expressionPlugin.js +++ b/lib/plugins/expressionPlugin.js @@ -29,7 +29,9 @@ var _ = require('underscore'), parser = require('./expressionParser'), config = require('../commonConfig'), + /*jshint unused:false*/ logger = require('logops'), + /*jshint unused:false*/ context = { op: 'IoTAgentNGSI.expressionPlugin' }, diff --git a/lib/plugins/multiEntity.js b/lib/plugins/multiEntity.js index 89102f30e..204eb4141 100644 --- a/lib/plugins/multiEntity.js +++ b/lib/plugins/multiEntity.js @@ -30,7 +30,9 @@ var _ = require('underscore'), parser = require('./expressionParser'), config = require('../commonConfig'), + /*jshint unused:false*/ logger = require('logops'), + /*jshint unused:false*/ context = { op: 'IoTAgentNGSI.MultiEntityPlugin' }, @@ -158,6 +160,7 @@ function generateNewCEsNgsi2(entity, newEntities, entityTypes, typeInformation, if (entity.hasOwnProperty(att)) { if (_.contains(newEntityAttributeNames, att)) { if (entity[att].multi && entity[att].multi.length > 0) { + // jshint maxdepth:7 if (mappings.inverse[att] && mappings.inverse[att].length > 0) { for (var j in (mappings.inverse[att])) { if (_.contains(newEntityAttributeObjectIds, mappings.inverse[att][j])) { @@ -167,10 +170,12 @@ function generateNewCEsNgsi2(entity, newEntities, entityTypes, typeInformation, } } } - for (var j in entity[att].multi) { - if (entity[att].multi[j].object_id && _.contains(newEntityAttributeObjectIds, entity[att].multi[j].object_id)) { - result[att] = entity[att].multi[j]; - delete entity[att].multi[j].object_id; + // jshint maxdepth:7 + for (var k in entity[att].multi) { + if (entity[att].multi[k].object_id && _.contains(newEntityAttributeObjectIds, + entity[att].multi[k].object_id)) { + result[att] = entity[att].multi[k]; + delete entity[att].multi[k].object_id; } } } else { @@ -184,13 +189,41 @@ function generateNewCEsNgsi2(entity, newEntities, entityTypes, typeInformation, return result; } + function filterByAttributeObjectIds() { + var result = {}; + for (var att in entity) { + if (entity.hasOwnProperty(att)) { + if (_.contains(newEntityAttributeNames, att)) { + if (entity[att].object_id && _.contains(newEntityAttributeObjectIds, entity[att].object_id )){ + result[att] = entity[att]; + delete entity[att].object_id; + } else { + // Check matches in rest of multientity attributes with same name (#635) + // jshint maxdepth:7 + if (entity[att].multi) { + for (var j in entity[att].multi) { + if (entity[att].multi[j].object_id && + _.contains(newEntityAttributeObjectIds, entity[att].multi[j].object_id)) { + result[att] = entity[att].multi[j]; + delete entity[att].multi[j].object_id; + } + } + } + } + } + } + } + return result; + } + var attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity); ctx = parser.extractContext(attsArray); for (var i = 0; i < newEntities.length; i++) { newEntityAttributeNames = _.pluck(multiEntityAttributes.filter(filterByEntityName(newEntities[i])), 'name'); - newEntityAttributeObjectIds = _.pluck(multiEntityAttributes.filter(filterByEntityName(newEntities[i])), 'object_id'); - newEntityAttributes = filterAttributes(); + newEntityAttributeObjectIds = _.pluck(multiEntityAttributes.filter( + filterByEntityName(newEntities[i])), 'object_id'); + newEntityAttributes = filterByAttributeObjectIds(); entityName = parser.applyExpression(newEntities[i], ctx, typeInformation); newEntityAttributes.type = entityTypes[newEntities[i]]; @@ -264,4 +297,4 @@ function updateAttribute(entity, typeInformation, callback) { } } -exports.update = updateAttribute; \ No newline at end of file +exports.update = updateAttribute; diff --git a/lib/plugins/pluginUtils.js b/lib/plugins/pluginUtils.js index e99ca18e3..06c0e2e2a 100644 --- a/lib/plugins/pluginUtils.js +++ b/lib/plugins/pluginUtils.js @@ -39,6 +39,7 @@ function extractAttributesArrayFromNgsi2Entity(entity) { if (i !== 'id' && i !== 'type') { var att = Object.assign({}, entity[i]); if (att.multi) { + // jshint maxdepth:5 for (var j in att.multi) { var matt = Object.assign({}, entity[i].multi[j]); matt.name = i; @@ -61,14 +62,16 @@ function extractAttributesArrayFromNgsi2Entity(entity) { * @param {String} id The identifier * @param {String} type The type * @param {Object} attsArray The atts array + * @param {Object} withObjectId The flag to keep object_id * @return {Object} A NGSIv2 entity */ -function createNgsi2Entity(id, type, attsArray, objectId) { +function createNgsi2Entity(id, type, attsArray, withObjectId) { var entity = {}; entity.id = id; entity.type = type; for (var i = 0; i < attsArray.length; i++) { - if (entity[attsArray[i].name] && objectId && attsArray[i].object_id) { + /*jshint camelcase: false */ + if (entity[attsArray[i].name] && withObjectId && attsArray[i].object_id) { // Check if multiple measures with multientity attributes with same name(#635) if (!entity[attsArray[i].name].multi) { entity[attsArray[i].name].multi = []; @@ -76,6 +79,7 @@ function createNgsi2Entity(id, type, attsArray, objectId) { entity[attsArray[i].name].multi.push({ 'type' : attsArray[i].type, 'value' : attsArray[i].value, + /*jshint camelcase: false */ 'object_id' : attsArray[i].object_id, 'metadata' : attsArray[i].metadata }); @@ -85,6 +89,9 @@ function createNgsi2Entity(id, type, attsArray, objectId) { 'value' : attsArray[i].value, 'metadata' : attsArray[i].metadata }; + if (withObjectId && attsArray[i].object_id) { + entity[attsArray[i].name].object_id = attsArray[i].object_id; + } } } diff --git a/lib/services/devices/deviceRegistryMongoDB.js b/lib/services/devices/deviceRegistryMongoDB.js index 2aa05f77b..0ef30bf0d 100644 --- a/lib/services/devices/deviceRegistryMongoDB.js +++ b/lib/services/devices/deviceRegistryMongoDB.js @@ -68,8 +68,8 @@ function storeDevice(newDevice, callback) { } // Ensure protocol is in newDevice - if ( !newDevice['protocol'] && config.getConfig().iotManager && config.getConfig().iotManager.protocol) { - deviceObj['protocol'] = config.getConfig().iotManager.protocol; + if ( !newDevice.protocol && config.getConfig().iotManager && config.getConfig().iotManager.protocol) { + deviceObj.protocol = config.getConfig().iotManager.protocol; } logger.debug(context, 'Storing device with id [%s] and type [%s]', newDevice.id, newDevice.type); diff --git a/lib/services/devices/deviceService.js b/lib/services/devices/deviceService.js index 0c0b223a5..0e7f116ff 100644 --- a/lib/services/devices/deviceService.js +++ b/lib/services/devices/deviceService.js @@ -216,6 +216,7 @@ function formatAttributesNgsi2(originalVector, staticAtts) { // (#628) check if attribute has entity_name: // In that case attribute should not be appear in current entity + /*jshint camelcase: false */ if (!originalVector[i].entity_name) { attributeList[originalVector[i].name] = { type: originalVector[i].type, @@ -293,9 +294,10 @@ function createInitialEntityNgsi2(deviceData, newDevice, callback) { jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands)); logger.debug(context, 'deviceData: %j', deviceData); - if ( (('timestamp' in deviceData && deviceData['timestamp'] !== undefined) ? deviceData.timestamp : config.getConfig().timestamp) && - ! utils.isTimestampedNgsi2(options.json)) { - logger.debug(context, 'config.timestamp %s %s', deviceData['timestamp'], config.getConfig().timestamp); + if ( (('timestamp' in deviceData && deviceData.timestamp !== undefined) ? + deviceData.timestamp : config.getConfig().timestamp) && + ! utils.isTimestampedNgsi2(options.json)) { + logger.debug(context, 'config.timestamp %s %s', deviceData.timestamp, config.getConfig().timestamp); options.json[constants.TIMESTAMP_ATTRIBUTE] = { type: constants.TIMESTAMP_TYPE_NGSI2, value: moment() @@ -344,6 +346,7 @@ function createInitialEntityNgsi1(deviceData, newDevice, callback) { for (var i = 0; i < originalVector.length; i++) { // (#628) check if attribute has entity_name: // In that case attribute should not be appear in current entity + /*jshint camelcase: false */ if (!originalVector[i].entity_name) { attributeList.push({ name: originalVector[i].name, @@ -383,8 +386,9 @@ function createInitialEntityNgsi1(deviceData, newDevice, callback) { deviceData.staticAttributes, formatCommands(deviceData.commands)); - if ( (('timestamp' in deviceData && deviceData['timestamp'] !== undefined) ? deviceData.timestamp : config.getConfig().timestamp) && - ! utils.isTimestamped(options.json)) { + if ( (('timestamp' in deviceData && deviceData.timestamp !== undefined) ? + deviceData.timestamp : config.getConfig().timestamp) && + ! utils.isTimestamped(options.json)) { options.json.contextElements[0].attributes.push({ name: constants.TIMESTAMP_ATTRIBUTE, type: constants.TIMESTAMP_TYPE, @@ -441,8 +445,9 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) { jsonConcat(options.json, formatAttributesNgsi2(deviceData.staticAttributes, true)); jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands)); - if ( (('timestamp' in deviceData && deviceData['timestamp'] !== undefined) ? deviceData.timestamp : config.getConfig().timestamp) && - ! utils.isTimestampedNgsi2(options.json)) { + if ( (('timestamp' in deviceData && deviceData.timestamp !== undefined) ? + deviceData.timestamp : config.getConfig().timestamp) && + ! utils.isTimestampedNgsi2(options.json)) { options.json[constants.TIMESTAMP_ATTRIBUTE] = { type: constants.TIMESTAMP_TYPE_NGSI2, value: moment() @@ -533,7 +538,8 @@ function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuratio if (deviceData[fields[i]] && configuration && configuration[confField]) { deviceData[fields[i]] = mergeArrays(deviceData[fields[i]], configuration[confField]); - } else if (!deviceData[fields[i]] && configuration && confField in configuration && configuration[confField] !== undefined) { + } else if (!deviceData[fields[i]] && configuration && + confField in configuration && configuration[confField] !== undefined) { deviceData[fields[i]] = configuration[confField]; } else if (!deviceData[fields[i]] && (!configuration || !configuration[confField])) { deviceData[fields[i]] = defaults[i]; @@ -1035,7 +1041,7 @@ function findOrCreate(deviceId, group, callback) { newDevice.protocol = config.getConfig().iotManager.protocol; } - if ('timestamp' in group && group['timestamp'] !== undefined) { + if ('timestamp' in group && group.timestamp !== undefined) { newDevice.timestamp = group.timestamp; } diff --git a/lib/services/devices/registrationUtils.js b/lib/services/devices/registrationUtils.js index af3cd429b..167d7df63 100644 --- a/lib/services/devices/registrationUtils.js +++ b/lib/services/devices/registrationUtils.js @@ -322,7 +322,8 @@ function sendRegistrationsNgsi2(unregister, deviceData, callback) { ).reduce(mergeWithSameName, []); if (options.json.dataProvided.attrs.length === 0) { - logger.debug(context, 'Registration with Context Provider is not needed. Device without lazy atts or commands'); + logger.debug(context, 'Registration with Context Provider is not needed.' + + 'Device without lazy atts or commands'); callback(null, deviceData); } else { logger.debug(context, 'Sending device registrations to Context Broker at [%s]', options.url); diff --git a/lib/services/groups/groupService.js b/lib/services/groups/groupService.js index 181a3c7b6..231ba8b6d 100644 --- a/lib/services/groups/groupService.js +++ b/lib/services/groups/groupService.js @@ -172,6 +172,14 @@ function remove(service, subservice, resource, apikey, device, callback) { callback(null, deviceGroup._id); } + function unregisterDevice(device, cb) { + deviceService.unregister(device.id, service, subservice, function(error) { + if (error) { + cb(error); + } + }); + } + function deleteDevices(device, service, subservice, id, callback) { if(device) { deviceService.listDevices(service, subservice, function (error, devices) { @@ -179,13 +187,12 @@ function remove(service, subservice, resource, apikey, device, callback) { callback(error); } else { if (devices && devices.count > 0){ - for(var device of devices.devices) { - deviceService.unregister(device.id, service, subservice, function(error) { + + async.map(devices.devices, unregisterDevice, function(error) { if (error) { callback(error); } }); - } } } }); diff --git a/lib/services/ngsi/ngsiService.js b/lib/services/ngsi/ngsiService.js index a2e2d30dd..567e59a2e 100644 --- a/lib/services/ngsi/ngsiService.js +++ b/lib/services/ngsi/ngsiService.js @@ -472,9 +472,17 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca typeInformation, token); - if ( (('timestamp' in typeInformation && typeInformation['timestamp'] !== undefined) ? typeInformation.timestamp : config.getConfig().timestamp) && - ! utils.isTimestampedNgsi2(result)) { - result = addTimestampNgsi2(result, typeInformation.timezone); + if ( ('timestamp' in typeInformation && typeInformation.timestamp !== + undefined) ? typeInformation.timestamp : config.getConfig().timestamp) { + // jshint maxdepth:5 + if (!utils.isTimestampedNgsi2(result)) { + options.json = addTimestampNgsi2(result, typeInformation.timezone); + // jshint maxdepth:5 + } else if (!utils.IsValidTimestampedNgsi2(result)) { + logger.error(context, 'Invalid timestamp:%s', JSON.stringify(result)); + callback(new errors.BadTimestamp(result)); + return; + } } options.json = { @@ -486,9 +494,15 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca delete result.type; options.json = result; logger.debug(context, 'typeInformation: %j', typeInformation); - if ( (('timestamp' in typeInformation && typeInformation['timestamp'] !== undefined) ? typeInformation.timestamp : config.getConfig().timestamp) && - ! utils.isTimestampedNgsi2(options.json)) { - options.json = addTimestampNgsi2(options.json, typeInformation.timezone); + if ( ('timestamp' in typeInformation && typeInformation.timestamp !== + undefined) ? typeInformation.timestamp : config.getConfig().timestamp) { + if (!utils.isTimestampedNgsi2(options.json)) { + options.json = addTimestampNgsi2(options.json, typeInformation.timezone); + } else if (!utils.IsValidTimestampedNgsi2(options.json)) { + logger.error(context, 'Invalid timestamp:%s', JSON.stringify(options.json)); + callback(new errors.BadTimestamp(options.json)); + return; + } } } } else { @@ -496,6 +510,35 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca delete payload.type; options.json = payload; } + // Purge object_id from entities before sent to CB + // object_id was added by createNgsi2Entity to allow multientity + // with duplicate attribute names. + var att; + if (options.json.entities) { + for (var entity = 0; entity < options.json.entities.length; entity++) { + for (att in options.json.entities[entity]) { + /*jshint camelcase: false */ + if (options.json.entities[entity][att].object_id) { + /*jshint camelcase: false */ + delete options.json.entities[entity][att].object_id; + } + if (options.json.entities[entity][att].multi) { + delete options.json.entities[entity][att].multi; + } + } + } + } else { + for (att in options.json) { + /*jshint camelcase: false */ + if (options.json[att].object_id) { + /*jshint camelcase: false */ + delete options.json[att].object_id; + } + if (options.json[att].multi) { + delete options.json[att].multi; + } + } + } logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url); logger.debug(context, 'Using the following request:\n\n%s\n\n', JSON.stringify(options, null, 4)); @@ -558,9 +601,16 @@ function sendUpdateValueNgsi1(entityName, attributes, typeInformation, token, ca options.json = payload; } - if ( (('timestamp' in typeInformation && typeInformation['timestamp'] !== undefined) ? typeInformation.timestamp : config.getConfig().timestamp) && - ! utils.isTimestamped(options.json)) { - options.json = addTimestamp(options.json, typeInformation.timezone); + if ( ('timestamp' in typeInformation && typeInformation.timestamp !== undefined) ? + typeInformation.timestamp : config.getConfig().timestamp) { + if (!utils.isTimestamped(options.json)) { + options.json = addTimestamp(options.json, typeInformation.timezone); + } else if (!utils.IsValidTimestamped(options.json)) { + logger.error(context, 'Invalid timestamp:%s', JSON.stringify(options.json)); + callback(new errors.BadTimestamp(options.json)); + return; + } + } logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url); @@ -725,7 +775,9 @@ function updateTrust(deviceGroup, deviceInformation, trust, response, callback) function executeWithDeviceInformation(operationFunction) { return function(entityName, type, apikey, attributes, deviceInformation, callback) { - logger.debug(context, 'executeWithDeviceInfo entityName %s type %s apikey %s attributes %j deviceInformation %j', entityName, type, apikey, attributes, deviceInformation); + logger.debug(context, + 'executeWithDeviceInfo entityName %s type %s apikey %s attributes %j deviceInformation %j', + entityName, type, apikey, attributes, deviceInformation); config.getGroupRegistry().getType(type, function(error, deviceGroup) { var typeInformation; if (error) { diff --git a/lib/services/northBound/restUtils.js b/lib/services/northBound/restUtils.js index 12d690b3c..e17b00979 100644 --- a/lib/services/northBound/restUtils.js +++ b/lib/services/northBound/restUtils.js @@ -29,6 +29,7 @@ var logger = require('logops'), constants = require('../../constants'), intoTrans = require('../common/domain').intoTrans, revalidator = require('revalidator'), + moment = require('moment'), context = { op: 'IoTAgentNGSI.RestUtils' }, @@ -140,6 +141,62 @@ function checkBody(template) { }; } +/** + * Checks if the timestamp properties of NGSIv1 entities are valid ISO8601 dates. + * + * @param {Object} payload NGSIv1 payload to be analyzed. + * @return {Boolean} true if timestamp attributes are valid ISO8601. false if not. + */ +function IsValidTimestamped(payload) { + for (var i in payload.contextElements[0].attributes) { + if (payload.contextElements[0].attributes[i].name === constants.TIMESTAMP_ATTRIBUTE && + ! moment(payload.contextElements[0].attributes[i].value, moment.ISO_8601).isValid()) { + return false; + } + } + + return true; +} + +/** + * Checks if the timestamp properties of NGSIv2 entities are valid ISO8601 dates. + * + * @param {Object} payload NGSIv2 payload to be analyzed. + * @return {Boolean} true if timestamp attributes are valid ISO8601. false if not. + */ +function IsValidTimestampedNgsi2(payload) { + function isValidTimestampedNgsi2Entity(entity) { + for (var i in entity) { + if (entity.hasOwnProperty(i)) { + if (i === constants.TIMESTAMP_ATTRIBUTE && + ! moment(entity[i].value, moment.ISO_8601).isValid()) { + return false; + } + } + } + + return true; + } + + if (payload instanceof Array) { + for (var i = 0; i < payload.length; i++) { + if (!isValidTimestampedNgsi2Entity(payload[i])) { + return false; + } + } + + return true; + } else { + return isValidTimestampedNgsi2Entity(payload); + } +} + +/** + * Checks if timestamp attributes are included in NGSIv1 entities. + * + * @param {Object} payload NGSIv1 payload to be analyzed. + * @return {Boolean} true if timestamp attributes are included. false if not. + */ function isTimestamped(payload) { for (var i in payload.contextElements[0].attributes) { if (payload.contextElements[0].attributes[i].name === constants.TIMESTAMP_ATTRIBUTE) { @@ -150,6 +207,12 @@ function isTimestamped(payload) { return false; } +/** + * Checks if timestamp attributes are included in NGSIv2 entities. + * + * @param {Object} payload NGSIv1 payload to be analyzed. + * @return {Boolean} true if timestamp attributes are included. false if not. + */ function isTimestampedNgsi2(payload) { function isTimestampedNgsi2Entity(entity) { @@ -182,4 +245,6 @@ exports.xmlRawBody = intoTrans(context, xmlRawBody); exports.checkRequestAttributes = intoTrans(context, checkRequestAttributes); exports.checkBody = intoTrans(context, checkBody); exports.isTimestamped = isTimestamped; +exports.IsValidTimestamped = IsValidTimestamped; exports.isTimestampedNgsi2 = isTimestampedNgsi2; +exports.IsValidTimestampedNgsi2 = IsValidTimestampedNgsi2; diff --git a/package.json b/package.json index 74ad2c883..c0a18641b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage", "test": "mocha --recursive 'test/**/*.js' --reporter spec --timeout 3000 --ui bdd --exit", "test:watch": "npm run test -- -w ./lib", - "lint": "jshint lib/ --config .jshintrc && jshint test/ --config test/.jshintrc", + "lint": "jshint lib/ --config .jshintrc && jshint test/ --config test/.jshintrc", "test:coverage": "istanbul cover _mocha -- --recursive 'test/**/*.js' --reporter spec --exit", "watch": "watch 'npm test && npm run lint' ./lib ./test" }, diff --git a/test/.jshintrc b/test/.jshintrc index d44254452..2cbeefda0 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -17,6 +17,7 @@ "node": true, "expr": true, "unused": "vars", + "esversion": 6, "globals": { "describe":true, "it": true, diff --git a/test/unit/ngsiService/active-devices-test.js b/test/unit/ngsiService/active-devices-test.js index 9d963cf90..358f8b2a4 100644 --- a/test/unit/ngsiService/active-devices-test.js +++ b/test/unit/ngsiService/active-devices-test.js @@ -352,6 +352,45 @@ describe('Active attributes test', function() { }); }); + describe('When the IoTA gets a set of values with a TimeInstant which are not in ISO8601 format', function() { + var modifiedValues; + + beforeEach(function(done) { + + modifiedValues = [ + { + name: 'state', + type: 'Boolean', + value: 'true' + }, + { + name: 'TimeInstant', + type: 'ISO8601', + value: '2018-10-05T11:03:56 00:00Z' + } + ]; + + nock.cleanAll(); + + iotAgentConfig.timestamp = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + afterEach(function(done) { + delete iotAgentConfig.timestamp; + done(); + }); + + it('should fail with a 400 BAD_TIMESTAMP error', function(done) { + iotAgentLib.update('light1', 'Light', '', modifiedValues, function(error) { + should.exist(error); + error.code.should.equal(400); + error.name.should.equal('BAD_TIMESTAMP'); + done(); + }); + }); + }); + describe('When the IoTA gets a set of values with a TimeInstant, the timestamp flag is on' + 'and timezone is defined', function() { var modifiedValues; diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json new file mode 100644 index 000000000..a434f55db --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json @@ -0,0 +1,33 @@ +{ + "actionType": "APPEND", + "entities": [ + { + "id": "Sensor", + "type": "Sensor" + }, + { + "vol": { + "type": "number", + "value": "38" + }, + "type": "WM", + "id": "SO1" + }, + { + "type": "WM", + "id": "SO2" + }, + { + "type": "WM", + "id": "SO3" + }, + { + "type": "WM", + "id": "SO4" + }, + { + "type": "WM", + "id": "SO5" + } + ] +} diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json new file mode 100644 index 000000000..c1c930d34 --- /dev/null +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json @@ -0,0 +1,45 @@ +{ + "actionType": "APPEND", + "entities": [ + { + "id": "Sensor", + "type": "Sensor" + }, + { + "vol": { + "type": "number", + "value": "38" + }, + "type": "WM", + "id": "SO1" + }, + { + "vol": { + "type": "number", + "value": "39" + }, + "type": "WM", + "id": "SO2" + }, + { + "vol": { + "type": "number", + "value": "40" + }, + "type": "WM", + "id": "SO3" + }, + { + "type": "WM", + "id": "SO4" + }, + { + "vol": { + "type": "number", + "value": "42" + }, + "type": "WM", + "id": "SO5" + } + ] +} diff --git a/test/unit/ngsiv2/ngsiService/active-devices-test.js b/test/unit/ngsiv2/ngsiService/active-devices-test.js index 2e214bab0..8b7c14af9 100644 --- a/test/unit/ngsiv2/ngsiService/active-devices-test.js +++ b/test/unit/ngsiv2/ngsiService/active-devices-test.js @@ -217,6 +217,45 @@ describe('Active attributes test', function() { }); }); + describe('When the IoTA gets a set of values with a TimeInstant which are not in ISO8601 format', function() { + var modifiedValues; + + beforeEach(function(done) { + + modifiedValues = [ + { + name: 'state', + type: 'Boolean', + value: 'true' + }, + { + name: 'TimeInstant', + type: 'ISO8601', + value: '2018-10-05T11:03:56 00:00Z' + } + ]; + + nock.cleanAll(); + + iotAgentConfig.timestamp = true; + iotAgentLib.activate(iotAgentConfig, done); + }); + + afterEach(function(done) { + delete iotAgentConfig.timestamp; + done(); + }); + + it('should fail with a 400 BAD_TIMESTAMP error', function(done) { + iotAgentLib.update('light1', 'Light', '', modifiedValues, function(error) { + should.exist(error); + error.code.should.equal(400); + error.name.should.equal('BAD_TIMESTAMP'); + done(); + }); + }); + }); + describe('When the IoT Agent receives new information, the timestamp flag is on' + 'and timezone is defined', function() { var modifiedValues; diff --git a/test/unit/ngsiv2/plugins/multientity-plugin_test.js b/test/unit/ngsiv2/plugins/multientity-plugin_test.js index 963d87e3f..0a9fef5be 100644 --- a/test/unit/ngsiv2/plugins/multientity-plugin_test.js +++ b/test/unit/ngsiv2/plugins/multientity-plugin_test.js @@ -137,7 +137,51 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), entity_type: 'Higrometer' } ] - } + }, + 'Sensor001': { + commands: [], + type: 'Sensor', + lazy: [], + active: [ + { + type : 'number', + name : 'vol', + object_id : 'cont1', + entity_name : 'SO1', + entity_type : 'WM' + }, + { + type : 'number', + name : 'vol', + object_id : 'cont2', + entity_name : 'SO2', + entity_type : 'WM' + }, + { + type : 'number', + name : 'vol', + object_id : 'cont3', + entity_name : 'SO3', + entity_type : 'WM' + }, + { + type : 'number', + name : 'vol', + object_id : 'cont4', + entity_name : 'SO4', + entity_type : 'WM' + }, + { + type : 'number', + name : 'vol', + object_id : 'cont5', + entity_name : 'SO5', + entity_type : 'WM' + } + ] + + }, + }, service: 'smartGondor', subservice: 'gardens', @@ -148,7 +192,7 @@ var iotAgentLib = require('../../../../lib/fiware-iotagent-lib'), describe('Multi-entity plugin', function() { beforeEach(function(done) { - logger.setLevel('FATAL'); + logger.setLevel('DEBUG'); iotAgentLib.activate(iotAgentConfig, function() { iotAgentLib.clearAll(function() { @@ -336,6 +380,82 @@ describe('Multi-entity plugin', function() { }); }); }); + + describe('When an update comes for a multientity measurement and there are attributes with' + + ' the same name but different alias and mapped to different CB entities', function() { + var values = [ + { + name: 'cont1', + type: 'number', + value: '38' + } + ]; + + beforeEach(function() { + nock.cleanAll(); + + 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/updateContextMultientityPlugin6.json')) + .reply(204); + }); + + it('should update only the appropriate CB entity', function(done) { + iotAgentLib.update('Sensor', 'Sensor001', '', values, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + + describe('When an update comes for a multientity multi measurement and there are attributes with' + + ' the same name but different alias and mapped to different CB entities', function() { + var values = [ + { + name: 'cont1', + type: 'number', + value: '38' + }, + { + name: 'cont2', + type: 'number', + value: '39' + }, + { + name: 'cont3', + type: 'number', + value: '40' + }, + { + name: 'cont5', + type: 'number', + value: '42' + } + ]; + + beforeEach(function() { + nock.cleanAll(); + + 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/updateContextMultientityPlugin7.json')) + .reply(204); + }); + + it('should update only the appropriate CB entity', function(done) { + iotAgentLib.update('Sensor', 'Sensor001', '', values, function(error) { + should.not.exist(error); + contextBrokerMock.done(); + done(); + }); + }); + }); + }); describe('Multi-entity plugin is executed before timestamp process plugin', function() { diff --git a/test/unit/provisioning/device-group-api-test.js b/test/unit/provisioning/device-group-api-test.js index 56c274b42..0e7cdc48c 100644 --- a/test/unit/provisioning/device-group-api-test.js +++ b/test/unit/provisioning/device-group-api-test.js @@ -428,7 +428,7 @@ describe('Device Group Configuration API', function() { function test (error, response, body) { response.statusCode.should.equal(404); done(); - }; + } async.series([ async.apply(request, optionsDeleteDevice), diff --git a/test/unit/provisioning/provisionDeviceMultientity-test.js b/test/unit/provisioning/provisionDeviceMultientity-test.js index 8878760a1..a9cf304f0 100644 --- a/test/unit/provisioning/provisionDeviceMultientity-test.js +++ b/test/unit/provisioning/provisionDeviceMultientity-test.js @@ -86,7 +86,8 @@ describe('Device provisioning API: Provision devices', function() { var options = { url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices', method: 'POST', - json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionNewDeviceMultientity.json'), + json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/' + + 'provisionNewDeviceMultientity.json'), headers: { 'fiware-service': 'smartGondor', 'fiware-servicepath': '/gardens'