diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index eb81c7693..c8b33c641 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,3 +1,5 @@ +- Large refactor of IOTA Lib code to make it simpler +- Remove: time compression support - Remove: autocast (including env var IOTA_AUTOCAST) (#1498) - Fix: use but not store timestamp and explicitAttrs from group with autoprovisioned devices (#1504, partially) - Fix: MongoDB connection authentication (user and password were not actually used) (#1510) diff --git a/lib/plugins/jexlParser.js b/lib/plugins/jexlParser.js index c5af0d6d7..dacf8bb24 100644 --- a/lib/plugins/jexlParser.js +++ b/lib/plugins/jexlParser.js @@ -103,16 +103,16 @@ function applyExpression(expression, context, typeInformation) { // Delete null values from context. Related: // https://github.com/telefonicaid/iotagent-node-lib/issues/1440 // https://github.com/TomFrost/Jexl/issues/133 - deleteNulls(context); + deleteNullsAndNaN(context); const result = parse(expression, context); logger.debug(logContext, 'applyExpression "[%j]" over "[%j]" result "[%j]" ', expression, context, result); const expressionResult = result !== undefined ? result : expression; return expressionResult; } -function deleteNulls(object) { +function deleteNullsAndNaN(object) { for (let key in object) { - if (object[key] === null) { + if (object[key] === null || Number.isNaN(object[key])) { delete object[key]; } } diff --git a/lib/services/ngsi/entities-NGSI-LD.js b/lib/services/ngsi/entities-NGSI-LD.js index 7ceb5e489..36cb8a629 100644 --- a/lib/services/ngsi/entities-NGSI-LD.js +++ b/lib/services/ngsi/entities-NGSI-LD.js @@ -41,11 +41,87 @@ const _ = require('underscore'); const context = { op: 'IoTAgentNGSI-LD' }; -const NGSIv2 = require('./entities-NGSI-v2'); const NGSIUtils = require('./ngsiUtils'); const NGSI_LD_URN = 'urn:ngsi-ld:'; +/** + * Adds timestamp to ngsi payload entities accoding to timezone, and an optional timestampvalue. + * + * @param {Object} payload NGSIv2 payload with one or more entities + * @param String timezone TimeZone value (optional) + * @param String timestampValue Timestamp value (optional). If not provided current timestamp is used + * @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false + * @return {Object} NGSIv2 payload entities with timestamp + */ +function addTimestamp(payload, timezone, timestampValue) { + function addTimestampEntity(entity, timezone, timestampValue) { + const timestamp = { + type: constants.TIMESTAMP_TYPE_NGSI2 + }; + + if (timestampValue) { + timestamp.value = timestampValue; + } else if (!timezone) { + timestamp.value = new Date().toISOString(); + } else { + timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + + function addMetadata(attribute) { + let timestampFound = false; + + if (!attribute.metadata) { + attribute.metadata = {}; + } + + for (let i = 0; i < attribute.metadata.length; i++) { + if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) { + if ( + attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 && + attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value + ) { + timestampFound = true; + break; + } + } + } + + if (!timestampFound) { + attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; + } + + return attribute; + } + let keyCount = 0; + for (const key in entity) { + /* eslint-disable-next-line no-prototype-builtins */ + if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') { + addMetadata(entity[key]); + keyCount += 1; + } + } + // Add timestamp just to entity with attrs: multientity plugin could + // create empty entities just with id and type. + if (keyCount > 0) { + entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp; + } + + return entity; + } + + if (payload instanceof Array) { + for (let i = 0; i < payload.length; i++) { + if (!utils.isTimestampedNgsi2(payload[i])) { + payload[i] = addTimestampEntity(payload[i], timezone, timestampValue); + } + } + + return payload; + } + return addTimestampEntity(payload, timezone, timestampValue); +} + /** * Amends an NGSIv2 attribute to NGSI-LD format * All native JSON types are respected and cast as Property values @@ -843,7 +919,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c ? config.getConfig().timestamp : timestampValue !== undefined ) { - newEntity = NGSIv2.addTimestamp(newEntity, typeInformation.timezone, timestampValue); + newEntity = addTimestamp(newEntity, typeInformation.timezone, timestampValue); logger.debug(context, 'sendUpdateValueNgsiLD \n timestamped newEntity=%j', newEntity); } payload.push(newEntity); @@ -927,14 +1003,14 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c // timeInstant is provided as measure if (Object.keys(payload[0]).length > 1) { // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities - payload[0] = NGSIv2.addTimestamp(payload[0], typeInformation.timezone, timestampValue); + payload[0] = addTimestamp(payload[0], typeInformation.timezone, timestampValue); } } else { // jshint maxdepth:5 for (let n = 0; n < payload.length; n++) { if (!utils.isTimestampedNgsi2(payload[n])) { // legacy check needed? - payload[n] = NGSIv2.addTimestamp(payload[n], typeInformation.timezone); + payload[n] = addTimestamp(payload[n], typeInformation.timezone); // jshint maxdepth:5 } else if (!utils.IsValidTimestampedNgsi2(payload[n])) { // legacy check needed? diff --git a/lib/services/ngsi/entities-NGSI-v2.js b/lib/services/ngsi/entities-NGSI-v2.js index 56993c3bf..3e98d1059 100644 --- a/lib/services/ngsi/entities-NGSI-v2.js +++ b/lib/services/ngsi/entities-NGSI-v2.js @@ -30,13 +30,11 @@ const request = require('../../request-shim'); const alarms = require('../common/alarmManagement'); const errors = require('../../errors'); -const utils = require('../northBound/restUtils'); const pluginUtils = require('../../plugins/pluginUtils'); const config = require('../../commonConfig'); const constants = require('../../constants'); const jexlParser = require('../../plugins/jexlParser'); const expressionPlugin = require('../../plugins/expressionPlugin'); -const compressTimestampPlugin = require('../../plugins/compressTimestamp'); const moment = require('moment-timezone'); const NGSIUtils = require('./ngsiUtils'); const logger = require('logops'); @@ -94,83 +92,6 @@ function formatGeoAttrs(attr) { return obj; } -/** - * Adds timestamp to ngsiv2 payload entities accoding to timezone, and an optional timestampvalue. - * - * @param {Object} payload NGSIv2 payload with one or more entities - * @param String timezone TimeZone value (optional) - * @param String timestampValue Timestamp value (optional). If not provided current timestamp is used - * @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false - * @return {Object} NGSIv2 payload entities with timestamp - */ -function addTimestampNgsi2(payload, timezone, timestampValue) { - function addTimestampEntity(entity, timezone, timestampValue) { - const timestamp = { - type: constants.TIMESTAMP_TYPE_NGSI2 - }; - - if (timestampValue) { - timestamp.value = timestampValue; - } else if (!timezone) { - timestamp.value = new Date().toISOString(); - } else { - timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); - } - - function addMetadata(attribute) { - let timestampFound = false; - - if (!attribute.metadata) { - attribute.metadata = {}; - } - - for (let i = 0; i < attribute.metadata.length; i++) { - if (attribute.metadata[i] === constants.TIMESTAMP_ATTRIBUTE) { - if ( - attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 && - attribute.metadata[constants.TIMESTAMP_ATTRIBUTE].value === timestamp.value - ) { - timestampFound = true; - break; - } - } - } - - if (!timestampFound) { - attribute.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; - } - - return attribute; - } - let keyCount = 0; - for (const key in entity) { - /* eslint-disable-next-line no-prototype-builtins */ - if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') { - addMetadata(entity[key]); - keyCount += 1; - } - } - // Add timestamp just to entity with attrs: multientity plugin could - // create empty entities just with id and type. - if (keyCount > 0) { - entity[constants.TIMESTAMP_ATTRIBUTE] = timestamp; - } - - return entity; - } - - if (payload instanceof Array) { - for (let i = 0; i < payload.length; i++) { - if (!utils.isTimestampedNgsi2(payload[i])) { - payload[i] = addTimestampEntity(payload[i], timezone, timestampValue); - } - } - - return payload; - } - return addTimestampEntity(payload, timezone, timestampValue); -} - /** * Generate an operation handler for NGSIv2-based operations (query and update). The handler takes care of identifiying * the errors and calling the appropriate callback with a success or a failure depending on how the operation ended. @@ -324,692 +245,377 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal } /** - * Makes an update in the Device's entity in the context broker, with the values given in the 'measures' array. This + * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This * array should comply to the NGSIv2's attribute format. * * @param {String} entityName Name of the entity to register. - * @param {Array} measures Attribute array containing the values to update. + * @param {Array} measures measure array containing the values to update. * @param {Object} typeInformation Configuration information for the device. * @param {String} token User token to identify against the PEP Proxies (optional). */ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback) { - logger.debug( - context, - 'sendUpdateValueNgsi2 called with: entityName=%s measures=%j typeInformation=%j', - entityName, - measures, - typeInformation - ); - // if any attribute name of a measure is 'id' or 'type' should be removed - var measuresWithoutIdType = []; - measures.forEach(function (attribute) { - if (attribute.name !== 'id' && attribute.name !== 'type') { - measuresWithoutIdType.push(attribute); + //aux function used to builf JEXL context. + //it returns a flat object from an Attr array + function reduceAttrToPlainObject(attrs, initObj = {}) { + if (attrs !== undefined && Array.isArray(attrs)) { + return attrs.reduce((result, item) => { + result[item.name] = item.value; + return result; + }, initObj); + } else { + return initObj; } - }); - measures = measuresWithoutIdType; - - const payload = { - entities: [ - { - // CB entity id should be always a String - id: String(entityName) - } - ] - }; - - let url = '/v2/op/update'; - - if (typeInformation && typeInformation.type) { - // CB entity type should be always a String - payload.entities[0].type = String(typeInformation.type); } - payload.actionType = 'append'; + let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture + let jexlctxt = {}; //will store the whole context (not just for JEXL) + let payload = {}; //will store the final payload + let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions. + let plainMeasures = null; //will contain measures POJO + let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); - let options = NGSIUtils.createRequestObject(url, typeInformation, token); - - if (typeInformation && typeInformation.staticAttributes) { - measures = measures.concat(typeInformation.staticAttributes); - } + //Make a clone and overwrite + typeInformation = JSON.parse(JSON.stringify(typeInformation)); + //Check mandatory information: type if (!typeInformation || !typeInformation.type) { callback(new errors.TypeNotFound(null, entityName)); return; } - let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation); - logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList); - let measureAttrsForCtxt = []; - // This ctxt should include all possible attrs - let attributesCtxt = []; - if (typeInformation && typeInformation.staticAttributes) { - typeInformation.staticAttributes.forEach(function (att) { - attributesCtxt.push(att); - }); - } + //Make a copy of measures in an plain object: plainMeasures + plainMeasures = reduceAttrToPlainObject(measures); - // Check explicitAttrs: adds all final needed attributes to payload - if ( - typeInformation.explicitAttrs === undefined || - (typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs) - // explicitAttrs is not defined => default case: all attrs should be included - ) { - // This loop adds all measure values (measures) into payload entities (entity[0]) - for (let i = 0; i < measures.length; i++) { - if (measures[i].name && measures[i].type) { - payload.entities[0][measures[i].name] = { - value: measures[i].value, - type: measures[i].type - }; - const metadata = NGSIUtils.getMetaData(typeInformation, measures[i].name, measures[i].metadata); - if (metadata) { - payload.entities[0][measures[i].name].metadata = metadata; - } + //Build the initital JEXL Context + //All the measures (avoid references make another copy instead) + jexlctxt = reduceAttrToPlainObject(measures); + //All the static + jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt); + //id type Service and Subservice + jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt); + + //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on) + const mustInsertTimeInstant = + typeInformation.timestamp !== undefined + ? typeInformation.timestamp + : config.getConfig().timestamp !== undefined + ? config.getConfig().timestamp + : false; + + if (mustInsertTimeInstant) { + //remove TimeInstant from measures + measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE); + + if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) { + //if it comes from a measure + if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) { + timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE]; } else { - callback(new errors.BadRequest(null, entityName)); - return; + callback(new errors.BadTimestamp(null, entityName)); } + } else if (!typeInformation.timezone) { + timestamp.value = new Date().toISOString(); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; + } else { + timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value; } - logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload); - // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0]) - if (typeInformation.active) { - typeInformation.active.forEach((attr) => { - if (attr.expression) { - if (attr.object_id) { - payload.entities[0][attr.object_id] = { - value: payload.entities[0][attr.object_id] - ? payload.entities[0][attr.object_id].value - : undefined, - type: attr.type, - object_id: attr.object_id - }; - } else { - payload.entities[0][attr.name] = { - value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined, - type: attr.type - }; - } - } - }); - } - } else { - let selectedAttrs = []; - if (typeInformation.staticAttributes) { - typeInformation.staticAttributes.forEach(function (att) { - selectedAttrs.push(att.name); - }); + } + + logger.debug( + context, + 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j with value=%j', + entityName, + plainMeasures, + typeInformation, + jexlctxt, + mustInsertTimeInstant, + timestamp.value + ); + + //Now we can calculate the EntityName of primary entity + let entityNameCalc = null; + if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') { + try { + logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp); + entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation); + } catch (e) { + logger.debug( + context, + 'Error evaluating expression for entityName: %j with context: %j', + typeInformation.entityNameExp, + jexlctxt + ); } - if (typeof typeInformation.explicitAttrs === 'string') { - // explicitAttrs is a jexlExpression + } - // Measures - for (let i = 0; i < measures.length; i++) { - if (measures[i].name && measures[i].type) { - const measureAttr = { - name: measures[i].name, - value: measures[i].value, - type: measures[i].type - }; - attributesCtxt.push(measureAttr); - // check measureAttr by object_id -> if in active - let j = 0; - let found = false; - while (j < typeInformation.active.length && !found) { - if (measures[i].name === typeInformation.active[j].object_id) { - let measureAttrByObjectId = { - name: typeInformation.active[j].name, - value: measures[i].value, - type: measures[i].type - }; - attributesCtxt.push(measureAttrByObjectId); - found = true; - } - j++; - } - } - } - // This context is just to calculate explicitAttrs when is an expression + entityName = entityNameCalc ? entityNameCalc : entityName; + //enrich JEXL context + jexlctxt['entity_name'] = entityName; - let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList)); - // typeInformation.active all attrs with expressions - if (typeInformation.active) { - typeInformation.active.forEach(function (att) { - if (att.expression !== undefined) { - let expandedAttr = { - name: att.name, - value: att.expression, - type: att.type - }; - attributesCtxt.push(expandedAttr); - if (att.object_id !== undefined) { - let expandedAttrByObjectId = { - name: att.object_id, - value: att.expression, - type: att.type - }; - attributesCtxt.push(expandedAttrByObjectId); - } - ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList)); - } - }); - } - // calculate expression for explicitAttrs + let preprocessedAttr = []; + //Add Raw Static, Lazy, Command and Actives attr attributes + if (typeInformation && typeInformation.staticAttributes) { + preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes); + } + if (typeInformation && typeInformation.lazy) { + preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy); + } + if (typeInformation && typeInformation.active) { + preprocessedAttr = preprocessedAttr.concat(typeInformation.active); + } + + //Proccess every proto Attribute to populate entities data steuture + entities[entityName] = {}; + entities[entityName][typeInformation.type] = []; + + for (let currentAttr of preprocessedAttr) { + let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values ) + let attrEntityName = entityName; + let attrEntityType = typeInformation.type; + let valueExpression = null; + //manage active attr without object__id (name by default) + currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name; + //Enrich the attr (skip, hit, value, meta-timeInstant) + currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null; + + //determine AttrEntityName for multientity + if ( + currentAttr.entity_name !== null && + currentAttr.entity_name !== undefined && + currentAttr.entity_name !== '' && + typeof currentAttr.entity_name == 'string' + ) { try { - logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt); - let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation); - if (res === true) { - // like explicitAttrs == true - // selectAttrs should be measures which are defined attributes - typeInformation.active.forEach((attr) => { - selectedAttrs.push(attr.name); - selectedAttrs.push(attr.object_id); - }); - } else if (res === false) { - // like explicitAttrs == false - // selectAttrs should be measures and defined attributes - typeInformation.active.forEach((attr) => { - selectedAttrs.push(attr.name); - selectedAttrs.push(attr.object_id); - }); - for (let i = 0; i < measures.length; i++) { - selectedAttrs.push(measures[i].name); - } - } else { - selectedAttrs = res; - } - if (selectedAttrs.length === 0) { - // implies do nothing - logger.info( - context, - 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j', - typeInformation.explicitAttrs, - ctxt - ); - return callback(null); + logger.debug(context, + 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j', + currentAttr.name, + currentAttr.entity_name, + jexlctxt + ); + attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation); + if (!attrEntityName) { + attrEntityName = currentAttr.entity_name; } } catch (e) { - // nothing to do: exception is already logged at info level + logger.debug( + context, + 'Exception evaluating entityNameExp:%j, with jexlctxt: %j', + currentAttr.entity_name, + jexlctxt + ); + attrEntityName = currentAttr.entity_name; } - - typeInformation.active.forEach((attr) => { - if (selectedAttrs.includes(attr.name)) { - selectedAttrs.push(attr.object_id); - } - // Check if selectedAttrs includes an attribute with format {object_id: xxxx} - if (selectedAttrs.includes({ object_id: attr.object_id })) { - selectedAttrs.push(attr.object_id); - } - }); - } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') { - // explicitAtts is true => Add just measures which are defined in active attributes - // and active attributes with expressions - // and TimeInstant - selectedAttrs.push('TimeInstant'); - typeInformation.active.forEach((attr) => { - // Measures - if (attr.expression !== undefined) { - selectedAttrs.push(attr.name); - selectedAttrs.push(attr.object_id); - } else { - // check if active attr is receiving a measure - let i = 0; - let found = false; - while (i < measures.length && !found) { - if (measures[i].name && measures[i].type) { - if (measures[i].name === attr.object_id || measures[i].name === attr.name) { - selectedAttrs.push(attr.name); - selectedAttrs.push(attr.object_id); - found = true; - } - } - i++; - } - } - }); } - // This loop adds selected measured values (measures) into payload entities (entity[0]) - for (let i = 0; i < measures.length; i++) { - if (measures[i].name && selectedAttrs.includes(measures[i].name) && measures[i].type) { - const attr = typeInformation.active.find((obj) => { - return obj.name === measures[i].name; - }); - payload.entities[0][measures[i].name] = { - value: measures[i].value, - type: measures[i].type - }; - // ensure payload has attr with proper object_id - if (attr && attr.object_id) { - payload.entities[0][measures[i].name].object_id = attr.object_id; - } - const metadata = NGSIUtils.getMetaData(typeInformation, measures[i].name, measures[i].metadata); - if (metadata) { - payload.entities[0][measures[i].name].metadata = metadata; - } - } else if (measures[i].name && !selectedAttrs.includes(measures[i].name) && measures[i].type) { - const att = { - name: measures[i].name, - type: measures[i].type, - value: measures[i].value - }; - measureAttrsForCtxt.push(att); - } + + //determine AttrEntityType for multientity + if ( + currentAttr.entity_type !== null && + currentAttr.entity_type !== undefined && + currentAttr.entity_type !== '' && + typeof currentAttr.entity_type === 'string' + ) { + attrEntityType = currentAttr.entity_type; } - logger.debug( - context, - 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j', - payload, - selectedAttrs - ); - let selectedAttrsByObjectId = selectedAttrs - .filter((o) => o !== undefined && o.object_id) - .map(function (el) { - return el.object_id; - }); - // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0]) - if (typeInformation.active) { - typeInformation.active.forEach((attr) => { - if (selectedAttrs.includes(attr.name)) { - if (attr.object_id) { - payload.entities[0][attr.object_id] = { - value: payload.entities[0][attr.object_id] - ? payload.entities[0][attr.object_id].value - : payload.entities[0][attr.name] - ? payload.entities[0][attr.name].value - : undefined, - type: attr.type, - object_id: attr.object_id - }; - } else { - payload.entities[0][attr.name] = { - value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined, - type: attr.type - }; - } - } else if (attr.object_id !== undefined && selectedAttrsByObjectId.includes(attr.object_id)) { - payload.entities[0][attr.object_id] = { - value: payload.entities[0][attr.object_id] - ? payload.entities[0][attr.object_id].value - : payload.entities[0][attr.name] - ? payload.entities[0][attr.name].value - : undefined, - type: attr.type, - object_id: attr.object_id - }; + //PRE POPULATE CONTEXT + jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id]; + + //determine Value + if (currentAttr.value !== undefined) { + //static attributes already have a value + hitted = true; + valueExpression = currentAttr.value; + } else if (plainMeasures[currentAttr.object_id] !== undefined) { + //we have got a meaure for that Attr + //actives ¿lazis? + hitted = true; + valueExpression = plainMeasures[currentAttr.object_id]; + } + //remove measures that has been shadowed by an alias (some may be left and managed later) + //Maybe we must filter object_id if there is name == object_id + measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name); + + if ( + currentAttr.expression !== undefined && + currentAttr.expression !== '' && + typeof currentAttr.expression == 'string' + ) { + try { + hitted = true; + valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation); + //we fallback to null if anything unexpecte happend + if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) { + valueExpression = null; } - }); + } catch (e) { + valueExpression = null; + } + logger.debug(context, + 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j', + currentAttr.name, + currentAttr.expression, + jexlctxt, + valueExpression + ); } - } // END check explicitAttrs - logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload); - - const currentEntity = payload.entities[0]; - // Prepare attributes for expresionPlugin - const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity); + currentAttr.hitted = hitted; + currentAttr.value = valueExpression; - // Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult' - let attsArrayFiltered = []; - if (attsArray) { - attsArrayFiltered = attsArray.filter((obj) => { - return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type); - }); - } - attributesCtxt = attributesCtxt.concat(attsArrayFiltered); // just copy - if (measureAttrsForCtxt) { - measureAttrsForCtxt.forEach(function (att) { - attributesCtxt.push(att); - }); - } - attributesCtxt = attributesCtxt.concat(idTypeSSSList); - let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation); - logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt); + //add TimeInstant to attr metadata + if (mustInsertTimeInstant) { + if (!currentAttr.metadata) { + currentAttr.metadata = {}; + } + currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; + } - // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active) - // attributes without expressions should be processed before - logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity); - if (typeInformation.active && typeInformation.active.length > 0) { - for (const k in currentEntity) { - typeInformation.active.forEach(function (att) { - if ( - (att.object_id && att.object_id === k && att.expression) || - (att.name && att.name === k && att.expression) - ) { - const m = currentEntity[k]; - delete currentEntity[k]; - currentEntity[k] = m; // put into the end of currentEntity - } - }); + //store de New Attributte in entity data structure + if (hitted === true) { + if (entities[attrEntityName] === undefined) { + entities[attrEntityName] = {}; + } + if (entities[attrEntityName][attrEntityType] === undefined) { + entities[attrEntityName][attrEntityType] = []; + } + //store de New Attributte + entities[attrEntityName][attrEntityType].push(currentAttr); } + + //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined) + jexlctxt[currentAttr.name] = valueExpression; } - // Evaluate entityNameExp with a context including measures - if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') { + //now we can compute explicit (Bool or Array) with the complete JexlContext + let explicit = false; + if (typeof typeInformation.explicitAttrs === 'string') { try { - logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j ', typeInformation.entityNameExp); - entityName = expressionPlugin.applyExpression(typeInformation.entityNameExp, ctxt, typeInformation); - // CB entity id should be always a String - entityName = String(entityName); - payload.entities[0].id = entityName; - ctxt['entity_name'] = entityName; - } catch (e) { + explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation); + if (explicit instanceof Array && mustInsertTimeInstant) { + explicit.push(constants.TIMESTAMP_ATTRIBUTE); + } logger.debug( context, - 'Error evaluating expression for entityName: %s with context: %s', - typeInformation.entityNameExp, - ctxt + 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j', + typeInformation.explicitAttrs, + jexlctxt, + explicit ); + } catch (e) { + // nothing to do: exception is already logged at info level } + } else if (typeof typeInformation.explicitAttrs == 'boolean') { + explicit = typeInformation.explicitAttrs; } - logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity); - let timestampValue = undefined; - // Loop for each final attribute to apply alias, multientity and expressions - for (const j in currentEntity) { - // discard id and type - if (j !== 'id' || j !== 'type') { - // Apply Mapping Alias: object_id in attributes are in typeInformation.active - let attr; - let newAttr = payload.entities[0][j]; - if (typeInformation.active) { - attr = typeInformation.active.find((obj) => { - return obj.object_id === j; - }); - } - if (!attr) { - if (typeInformation.lazy) { - attr = typeInformation.lazy.find((obj) => { - return obj.object_id === j; - }); - } - } - if (!attr) { - if (typeInformation.active) { - attr = typeInformation.active.find((obj) => { - return obj.name === j; - }); - } - } - if (attr && attr.name) { - if (['id', 'type'].includes(attr.name)) { - // invalid mapping - logger.debug( - context, - 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j', - attr, - newAttr - ); - if (!['id', 'type'].includes(attr.object_id)) { - delete payload.entities[0][attr.object_id]; - } - attr = undefined; // stop processing attr - newAttr = undefined; - } else { - ctxt[attr.name] = payload.entities[0][j].value; + //more mesures may be added to the attribute list (unnhandled/left mesaures) l + if (explicit === false && Object.keys(measures).length > 0) { + //add Timestamp to measures if needed + if (mustInsertTimeInstant) { + for (let currentMeasure of measures) { + if (!currentMeasure.metadata) { + currentMeasure.metadata = {}; } + currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp; } - logger.debug( - context, - 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ', - j, - attr, - ctxt, - newAttr - ); - if (attr && attr.type) { - newAttr.type = attr.type; - } + //If just measures in the principal entity we missed the Timestamp. + } + entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures); + } - // Apply expression - if (attr && attr.expression) { - logger.debug( - context, - 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j', - attr.expression, - ctxt, - typeInformation - ); - let res = null; - try { - if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) { - res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation); - if ( - // By default undefined is handled like null: should not progress - // Some op results (like nonexistent * 2) are a kind of null with a number type - // but NaN value - (attr.skipValue === undefined && - (res === null || (typeof res === 'number' && isNaN(res)))) || - (attr.skipValue !== undefined && - (res === attr.skipValue || - (typeof res === 'number' && isNaN(res) && attr.skipValue === null))) - ) { - logger.debug( - context, - 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j', - attr.skipValue, - res, - attr.expression - ); - delete payload.entities[0][j]; // remove measure attr - attr = undefined; // stop process attr - } - } else { - logger.info( - context, - 'sendUpdateValueNgsi2 no context available for apply expression=%j', - attr.expression - ); - res = newAttr.value; // keep newAttr value - } - } catch (e) { - logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e); - if (attr && attr.name && ctxt[attr.name] !== undefined) { - res = ctxt[attr.name]; - } - } - // jexl expression plugin - newAttr.value = res; + //PRE-PROCESSING FINISHED + //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload - logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr); - // update current context with value after apply expression - if (attr && attr.name) { - ctxt[attr.name] = newAttr.value; - } - } + //Get ready to build and send NGSI payload (entities-->payload) + payload.actionType = 'append'; - // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active - if (attr && (attr.entity_type || attr.entity_name)) { - // Create a newEntity for this attribute - let newEntityName = null; - if (attr.entity_name) { - try { - if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) { - newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation); - } else { - logger.info( - context, - 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j', - attr.entity_name - ); - newEntityName = attr.entity_name; - } - newEntityName = newEntityName ? newEntityName : attr.entity_name; - } catch (e) { - logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e); - newEntityName = attr.entity_name; - } - logger.debug( - context, - 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j', - attr.entity_name, - newEntityName, - payload - ); - } - // CB entity id and type should be always a String - let newEntity = { - id: newEntityName ? String(newEntityName) : String(payload.entities[0].id), - type: attr.entity_type ? String(attr.entity_type) : String(payload.entities[0].type) - }; - // Check if there is already a newEntity created - const alreadyEntity = payload.entities.find((entity) => { - return entity.id === newEntity.id && entity.type === newEntity.type; - }); - if (alreadyEntity) { - // Use alreadyEntity - alreadyEntity[attr.name] = newAttr; - } else { - // Add newEntity to payload.entities - newEntity[attr.name] = newAttr; - if ( - 'timestamp' in typeInformation && typeInformation.timestamp !== undefined - ? typeInformation.timestamp - : config.getConfig().timestamp !== undefined - ? config.getConfig().timestamp - : timestampValue !== undefined - ) { - newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue); - logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity); - } - payload.entities.push(newEntity); - } - if (attr && attr.name) { - if (attr.name !== j) { - logger.debug( - context, - 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j', - j, - attr, - payload - ); - delete payload.entities[0][j]; - } - } - // if (attr && (attr.entity_type || attr.entity_name)) - } else { - // Not a multientity attr - if (attr && attr.name) { - payload.entities[0][attr.name] = newAttr; - if (attr.name !== j) { - delete payload.entities[0][j]; // keep alias name, remove measure name - } - } - if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) { - const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value); - if (extendedTime) { - // TBD: there is not flag about compressTimestamp in iotagent-node-lib, - // but there is one in agents - newAttr.value = extendedTime; - } - } - if (j === constants.TIMESTAMP_ATTRIBUTE) { - if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) { - timestampValue = newAttr.value; - logger.debug( - context, - 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j', - payload - ); - } - } + payload.entities = []; + for (let ename in entities) { + for (let etype in entities[ename]) { + let e = {}; + e.id = String(ename); + e.type = String(etype); + //extract attributes + let isEmpty = true; + for (let attr of entities[ename][etype]) { + //Handling id/type measures, skip, hit & explicit (condition) if ( - newAttr && - newAttr.metadata && - newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] && - newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 && - newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value + attr.name !== 'id' && + attr.name !== 'type' && + (attr.value !== attr.skipValue || attr.skipValue === undefined) && + (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures + (typeof explicit === 'boolean' || //true and false already handled + (explicit instanceof Array && //check the array version + (explicit.includes(attr.name) || + explicit.some( + (item) => attr.object_id !== undefined && item.object_id === attr.object_id + )))) ) { - const extendedTime = compressTimestampPlugin.fromBasicToExtended( - newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value - ); - if (extendedTime) { - newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime; - } + isEmpty = false; + e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata }; } } - } // if (j !== 'id' || j !== 'type') - - // final attr loop - logger.debug( - context, - 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j', - j, - currentEntity, - payload - ); - } - // for attr loop - - // Add timestamp to paylaod - if ( - 'timestamp' in typeInformation && typeInformation.timestamp !== undefined - ? typeInformation.timestamp - : config.getConfig().timestamp !== undefined - ? config.getConfig().timestamp - : timestampValue !== undefined - ) { - if (timestampValue) { - // timeInstant is provided as measure - if (payload.entities.length > 0) { - for (let n = 0; n < payload.entities.length; n++) { - // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities - payload.entities[n] = addTimestampNgsi2( - payload.entities[n], - typeInformation.timezone, - timestampValue - ); + if (!isEmpty) { + if (mustInsertTimeInstant) { + e[constants.TIMESTAMP_ATTRIBUTE] = timestamp; } - } - } else { - // jshint maxdepth:5 - for (let n = 0; n < payload.entities.length; n++) { - if (!utils.isTimestampedNgsi2(payload.entities[n])) { - // legacy check needed? - payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone); - // jshint maxdepth:5 - } else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) { - // legacy check needed? - logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0])); - callback(new errors.BadTimestamp(payload.entities)); - return; - } - } - } - } - logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload); - - for (let m = 0; m < payload.entities.length; m++) { - for (const key in payload.entities[m]) { - // purge object_id from payload - if (payload.entities[m][key] && payload.entities[m][key].object_id) { - delete payload.entities[m][key].object_id; + payload.entities.push(e); } } } - logger.debug(context, 'sendUpdateValueNgsi2 payload without object_id=%j', payload); - + + let url = '/v2/op/update'; + let options = NGSIUtils.createRequestObject(url, typeInformation, token); options.json = payload; - // Prevent to update an entity with an empty payload + // Prevent to update an entity with an empty payload: more than id and type if ( Object.keys(options.json).length > 0 && (options.json.entities.length > 1 || - (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type + (options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) ) { // Final check: (to keep tests unchanged) before do CB requests // one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type // multi entities -> request /v2/op/update // Note that the options object is prepared for the second case (multi entity), so we "patch" it // only in the first case - if (options.json.entities.length === 1) { + + //Multientity more than one name o more than one type at primary entity + let multientity = Object.keys(entities).length > 1 || Object.keys(entities[entityName]).length > 1; + + if (!multientity) { // recreate options object to use single entity update url = '/v2/entities?options=upsert'; options = NGSIUtils.createRequestObject(url, typeInformation, token); - options.json = payload.entities[0]; + delete payload.actionType; + + let entityAttrs = payload.entities[0]; + const transformedObject = {}; + for (let attrname in entityAttrs) { + let attr = entityAttrs[attrname]; + transformedObject[attrname] = { + type: attr.type, + value: attr.value, + metadata: attr.metadata + }; + } + transformedObject.id = entityAttrs.id; + transformedObject.type = entityAttrs.type; + options.json = transformedObject; options.method = 'POST'; } // else: keep current options object created for a batch update - logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url); - logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4)); + + //Send the NGSI request + logger.debug(context, + 'Updating device value in the Context Broker at: %j', + options.url + ); + logger.debug(context, + 'Using the following NGSI v2 request: %j', + options + ); + request( options, generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback) @@ -1017,9 +623,9 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call } else { logger.debug( context, - 'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n', + 'Not updating device value in the Context Broker at: %j, due to empty payload: %j', options.url, - JSON.stringify(options, null, 4) + options ); callback(null); } @@ -1031,5 +637,5 @@ exports.sendUpdateValue = function (entityName, measures, typeInformation, token return sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback); }); }; -exports.addTimestamp = addTimestampNgsi2; + exports.formatGeoAttrs = formatGeoAttrs; diff --git a/test/tools/utils.js b/test/tools/utils.js index b051b1b70..ca539159b 100644 --- a/test/tools/utils.js +++ b/test/tools/utils.js @@ -35,6 +35,31 @@ function readExampleFile(name, raw) { return raw ? text : JSON.parse(text); } +function deepEqual(objA, objB) { + if (objA === objB) { + return true; + } + + if (typeof objA !== 'object' || typeof objB !== 'object' || objA === null || objB === null) { + return false; + } + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) { + return false; + } + + for (const key of keysA) { + if (!keysB.includes(key) || !deepEqual(objA[key], objB[key])) { + return false; + } + } + + return true; +} exports.readExampleFile = readExampleFile; +exports.deepEqual = deepEqual; exports.request = request; diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json index 120b31301..d94537daa 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json @@ -3,6 +3,6 @@ "type": "Light", "pressure": { "type": "Number", - "value": 1040 + "value": null } } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json index e43a88d08..48570ed81 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json @@ -9,12 +9,6 @@ 52 ], "type": "Point" - }, - "metadata": { - "TimeInstant": { - "type": "DateTime", - "value": "1970-01-01T00:00:00.001Z" - } } }, "TimeInstant": { diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json index c49081d54..1dfb9f6a0 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json @@ -1,6 +1,14 @@ { "id":"gps1", "type":"GPS", + "lat": { + "type": "Number", + "value": 52 + }, + "lon": { + "type": "Number", + "value": 13 + }, "location": { "type": "geo:json", "value": { diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json index 75ffaff03..c49081d54 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json @@ -1,14 +1,6 @@ { "id":"gps1", "type":"GPS", - "lat": { - "type": "Number", - "value": 52 - }, - "lon": { - "type": "Number", - "value": 13 - }, "location": { "type": "geo:json", "value": { diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json index 0fa9011cb..67f720cfb 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json @@ -1,10 +1,6 @@ { "id":"gps1", "type":"GPS", - "TimeInstant": { - "type": "DateTime", - "value": "2015-08-05T07:35:01.468+00:00" - }, "location": { "value": { "coordinates": [ @@ -13,12 +9,6 @@ ], "type": "Point" }, - "type": "geo:json", - "metadata": { - "TimeInstant": { - "type": "DateTime", - "value": "2015-08-05T07:35:01.468+00:00" - } - } + "type": "geo:json" } } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json index 7cbda7342..20751ec73 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json @@ -1,9 +1,5 @@ { "id":"gps1","type":"GPS", - "TimeInstant": { - "type": "DateTime", - "value": "2015-08-05T07:35:01.468+00:00" - }, "location": { "value": { "coordinates": [ @@ -12,12 +8,6 @@ ], "type": "Point" }, - "type": "geo:json", - "metadata": { - "TimeInstant": { - "type": "DateTime", - "value": "2015-08-05T07:35:01.468+00:00" - } - } + "type": "geo:json" } } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json index 97a44f128..293022ef1 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json @@ -1,10 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "ws11", - "type": "WrongStation" - }, { "id": "WrongStation1", "type": "WrongStation", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json index 8eabddc23..960d7b7a0 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json @@ -1,10 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "gps1", - "type": "GPS" - }, { "explicit": { "type": "number", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json index 10e089ad2..5bf8c40c1 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json @@ -1,10 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "gps1", - "type": "GPS" - }, { "attr1": { "type": "number", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json index a4ab4da8d..c4beec37a 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json @@ -1,9 +1,6 @@ { "actionType": "append", "entities": [ - { "id": "ws5", - "type": "WeatherStation" - }, { "id": "Higro2000", "type": "Higrometer", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json index 6b1be25ef..a483a8562 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json @@ -1,17 +1,7 @@ { "actionType": "append", "entities": [ - { "id": "ws6", - "type": "WeatherStation" - }, - { - "id": "Higro2000", - "type": "Higrometer", - "pressure": { - "type": "Hgmm", - "value": "16" - } - }, + { "id": "Higro2002", "type": "Higrometer", @@ -19,6 +9,14 @@ "type": "Hgmm", "value": "17" } - } + }, + { + "id": "Higro2000", + "type": "Higrometer", + "pressure": { + "type": "Hgmm", + "value": "16" + } + } ] } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json index b853d3bc7..e6e84e1b4 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json @@ -1,10 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "Sensor", - "type": "Sensor" - }, { "vol": { "type": "number", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json index eef32c3ea..2126c890e 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json @@ -1,10 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "Sensor", - "type": "Sensor" - }, { "vol": { "type": "number", @@ -17,7 +13,7 @@ "vol": { "type": "number", "value": "39" - }, + }, "type": "WM", "id": "SO2" }, diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json index 2c54d0598..48ff6bd5c 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json @@ -1,18 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "ws7", - "type": "WeatherStation" - }, - { - "pressure": { - "type": "Hgmm", - "value": "16" - }, - "type": "Higrometer", - "id": "Higro2000" - }, { "pressure": { "type": "Hgmm", @@ -26,6 +14,14 @@ }, "type": "Higrometer", "id": "Higro2002" + }, + { + "pressure": { + "type": "Hgmm", + "value": "16" + }, + "type": "Higrometer", + "id": "Higro2000" } ] } diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json index ea03aaf23..8a5a11ba0 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json @@ -1,9 +1,5 @@ { "entities": [ - { - "id": "ws4", - "type": "WeatherStation" - }, { "id": "Higro2000", "type": "Higrometer", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json index 383eeb2e1..380551909 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json @@ -1,14 +1,6 @@ { "actionType": "append", "entities": [ - { - "id": "ws5", - "type": "WeatherStation", - "TimeInstant": { - "type": "DateTime", - "value": "2018-06-13T13:28:34.611Z" - } - }, { "id": "Higro2000", "type": "Higrometer", diff --git a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json index 066725039..0a6616e04 100644 --- a/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json +++ b/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json @@ -3,7 +3,13 @@ "type":"Lamp", "luminosity": { "value": "100", - "type": "text" + "type": "text", + "metadata": { + "unitCode": { + "type": "Text", + "value": "CAL" + } + } }, "controlledProperty": { "value": "StaticValue", diff --git a/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js b/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js index b4619f4e3..9595b0abf 100644 --- a/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +++ b/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js @@ -33,7 +33,6 @@ const nock = require('nock'); const timekeeper = require('timekeeper'); let contextBrokerMock; const iotAgentConfig = { - logLevel: 'DEBUG', contextBroker: { host: '192.168.1.1', port: '1026', @@ -62,7 +61,8 @@ const iotAgentConfig = { { object_id: 'a', name: 'alive', - type: 'None' + type: 'None', + skipValue: 'null passes' }, { object_id: 'u', @@ -96,7 +96,8 @@ const iotAgentConfig = { object_id: 'p', name: 'pressure', type: 'Number', - expression: 'pressure * / 20' + expression: 'pressure * / 20', + skipValue: 'null passes' } ] }, @@ -361,7 +362,7 @@ const iotAgentConfig = { { name: 'lat', type: 'string', - value: '52' + value: 52 } ], active: [ @@ -376,7 +377,7 @@ const iotAgentConfig = { expression: "{coordinates: [lon,lat], type: 'Point'}" } ], - explicitAttrs: "theLocation ? [{object_id: 'theLocation'}] : []" + explicitAttrs: "mylocation ? [{object_id: 'theLocation'}] : []" }, GPS6: { commands: [], @@ -733,7 +734,6 @@ const iotAgentConfig = { }; const iotAgentConfigTS = { - logLevel: 'DEBUG', contextBroker: { host: '192.168.1.1', port: '1026', @@ -1175,7 +1175,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1388,7 +1387,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1735,7 +1733,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1779,7 +1776,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1828,7 +1824,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1872,14 +1867,13 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') .post( '/v2/entities?options=upsert', utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json' + './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json' ) ) .reply(204); @@ -1916,14 +1910,13 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') .post( '/v2/entities?options=upsert', utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json' + './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json' ) ) .reply(204); @@ -1965,7 +1958,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -2004,7 +1996,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -2038,14 +2029,13 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') .post( '/v2/entities?options=upsert', utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34b.json' + './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json' ) ) .reply(204); @@ -2134,7 +2124,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -2146,7 +2135,6 @@ describe('Java expression language (JEXL) based transformations plugin', functio ) .reply(204); }); - afterEach(function (done) { done(); }); @@ -2516,7 +2504,6 @@ describe('Java expression language (JEXL) based transformations plugin - Timesta timekeeper.freeze(time); nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') diff --git a/test/unit/ngsiv2/ngsiService/active-devices-test.js b/test/unit/ngsiv2/ngsiService/active-devices-test.js index 29c9f392d..d1277e695 100644 --- a/test/unit/ngsiv2/ngsiService/active-devices-test.js +++ b/test/unit/ngsiv2/ngsiService/active-devices-test.js @@ -377,7 +377,6 @@ describe('NGSI-v2 - Active attributes test', function () { ]; timekeeper.freeze(time); - nock.cleanAll(); contextBrokerMock = nock('http://192.168.1.1:1026') diff --git a/test/unit/ngsiv2/ngsiService/staticAttributes-test.js b/test/unit/ngsiv2/ngsiService/staticAttributes-test.js index e2c28dfbf..200eddd53 100644 --- a/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +++ b/test/unit/ngsiv2/ngsiService/staticAttributes-test.js @@ -344,8 +344,6 @@ describe('NGSI-v2 - Static attributes test', function () { beforeEach(function (done) { nock.cleanAll(); - logger.setLevel('DEBUG'); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') diff --git a/test/unit/ngsiv2/plugins/alias-plugin_test.js b/test/unit/ngsiv2/plugins/alias-plugin_test.js index da0ce09bc..3c624ab33 100644 --- a/test/unit/ngsiv2/plugins/alias-plugin_test.js +++ b/test/unit/ngsiv2/plugins/alias-plugin_test.js @@ -89,7 +89,8 @@ const iotAgentConfig = { { object_id: 'al', name: 'keep_alive', - type: 'None' + type: 'None', + skipValue: 'null passes' }, { object_id: 'ta', @@ -111,8 +112,7 @@ const iotAgentConfig = { describe('NGSI-v2 - Attribute alias plugin', function () { beforeEach(function (done) { - logger.setLevel('DEBUG'); - + logger.setLevel('FATAL'); iotAgentLib.activate(iotAgentConfig, function () { iotAgentLib.clearAll(function () { done(); @@ -325,7 +325,6 @@ describe('NGSI-v2 - Attribute alias plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') diff --git a/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js b/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js deleted file mode 100644 index 1a9e64911..000000000 --- a/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U - * - * This file is part of fiware-iotagent-lib - * - * fiware-iotagent-lib is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * fiware-iotagent-lib is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with fiware-iotagent-lib. - * If not, see http://www.gnu.org/licenses/. - * - * For those usages not covered by the GNU Affero General Public License - * please contact with::daniel.moranjimenez@telefonica.com - * - * Modified by: Daniel Calvo - ATOS Research & Innovation - */ - -const iotAgentLib = require('../../../../lib/fiware-iotagent-lib'); -const utils = require('../../../tools/utils'); -const should = require('should'); -const logger = require('logops'); -const nock = require('nock'); -let contextBrokerMock; -const iotAgentConfig = { - contextBroker: { - host: '192.168.1.1', - port: '1026', - ngsiVersion: 'v2' - }, - server: { - port: 4041, - host: 'localhost' - }, - types: { - Light: { - commands: [], - type: 'Light', - lazy: [ - { - name: 'temperature', - type: 'centigrades' - } - ], - active: [ - { - name: 'pressure', - type: 'Hgmm' - } - ] - }, - BrokenLight: { - commands: [], - lazy: [ - { - name: 'temperature', - type: 'centigrades' - } - ], - active: [ - { - name: 'pressure', - type: 'Hgmm' - } - ] - }, - Termometer: { - type: 'Termometer', - commands: [], - lazy: [ - { - name: 'temp', - type: 'kelvin' - } - ], - active: [] - }, - Humidity: { - type: 'Humidity', - cbHost: 'http://192.168.1.1:3024', - commands: [], - lazy: [], - active: [ - { - name: 'humidity', - type: 'percentage' - } - ] - }, - Motion: { - type: 'Motion', - commands: [], - lazy: [], - staticAttributes: [ - { - name: 'location', - type: 'Vector', - value: '(123,523)' - } - ], - active: [ - { - name: 'humidity', - type: 'percentage' - } - ] - } - }, - service: 'smartgondor', - subservice: 'gardens', - providerUrl: 'http://smartgondor.com' -}; - -describe('NGSI-v2 - Timestamp compression plugin', function () { - beforeEach(function (done) { - logger.setLevel('FATAL'); - iotAgentLib.activate(iotAgentConfig, function () { - iotAgentLib.clearAll(function () { - done(); - }); - }); - }); - - afterEach(function (done) { - iotAgentLib.clearAll(function () { - iotAgentLib.deactivate(done); - }); - }); - describe('When an update comes with a timestamp through the plugin', function () { - const values = [ - { - name: 'state', - type: 'Boolean', - value: 'true' - }, - { - name: 'TheTargetValue', - type: 'DateTime', - value: '20071103T131805' - } - ]; - - beforeEach(function () { - nock.cleanAll(); - - contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', 'smartgondor') - .matchHeader('fiware-servicepath', 'gardens') - .post( - '/v2/entities?options=upsert', - utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json' - ) - ) - .reply(204); - }); - - it('should return an entity with all its timestamps expanded to have separators', function (done) { - iotAgentLib.update('light1', 'Light', '', values, function (error) { - should.not.exist(error); - contextBrokerMock.done(); - done(); - }); - }); - }); - - describe('When an update comes with a timestamp through the plugin with metadata.', function () { - const values = [ - { - name: 'state', - type: 'Boolean', - value: true, - metadata: { - TimeInstant: { - type: 'DateTime', - value: '20071103T131805' - } - } - }, - { - name: 'TheTargetValue', - type: 'DateTime', - value: '20071103T131805' - } - ]; - - beforeEach(function () { - nock.cleanAll(); - - contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', 'smartgondor') - .matchHeader('fiware-servicepath', 'gardens') - .post( - '/v2/entities?options=upsert', - utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json' - ) - ) - .reply(204); - }); - - it('should return an entity with all its timestamps expanded to have separators', function (done) { - iotAgentLib.update('light1', 'Light', '', values, function (error) { - should.not.exist(error); - contextBrokerMock.done(); - done(); - }); - }); - }); -}); diff --git a/test/unit/ngsiv2/plugins/multientity-plugin_test.js b/test/unit/ngsiv2/plugins/multientity-plugin_test.js index 7d8290a87..9e23f6fe6 100644 --- a/test/unit/ngsiv2/plugins/multientity-plugin_test.js +++ b/test/unit/ngsiv2/plugins/multientity-plugin_test.js @@ -708,7 +708,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -746,7 +745,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -785,7 +783,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1157,7 +1154,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () { .reply(204); }); - describe('When an update comes for a multientity whith a wrong mapping)', function () { + describe('When an update comes for a multientity whith a wrong mapping', function () { const values = [ { name: 'v', @@ -1178,7 +1175,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1378,7 +1374,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1430,7 +1425,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { beforeEach(function () { nock.cleanAll(); - contextBrokerMock = nock('http://192.168.1.1:1026') .matchHeader('fiware-service', 'smartgondor') .matchHeader('fiware-servicepath', 'gardens') @@ -1495,7 +1489,6 @@ describe('NGSI-v2 - Multi-entity plugin', function () { describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plugin', function () { beforeEach(function (done) { logger.setLevel('FATAL'); - iotAgentConfig.timestamp = true; iotAgentLib.activate(iotAgentConfig, function () { iotAgentLib.clearAll(function () { @@ -1566,7 +1559,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu delete expectedBody.entities[1].TimeInstant; delete expectedBody.entities[1].humidity.metadata.TimeInstant; - return JSON.stringify(body) === JSON.stringify(expectedBody); + return utils.deepEqual(body, expectedBody); } return false; }) @@ -1589,22 +1582,22 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu ); // Note that TimeInstant fields are not included in the json used by this mock as they are dynamic // fields. The following code just checks that TimeInstant fields are present. - if (!body.entities[1].TimeInstant || !body.entities[1].humidity.metadata.TimeInstant) { + if (!body.entities[0].TimeInstant || !body.entities[0].humidity.metadata.TimeInstant) { return false; } - const timeInstantEntity2 = body.entities[1].TimeInstant; - const timeInstantAtt = body.entities[1].humidity.metadata.TimeInstant; + const timeInstantEntity2 = body.entities[0].TimeInstant; + const timeInstantAtt = body.entities[0].humidity.metadata.TimeInstant; if ( moment(timeInstantEntity2, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid && moment(timeInstantAtt, 'YYYY-MM-DDTHH:mm:ss.SSSZ').isValid ) { - delete body.entities[1].TimeInstant; - delete body.entities[1].humidity.metadata.TimeInstant; + delete body.entities[0].TimeInstant; + delete body.entities[0].humidity.metadata.TimeInstant; - delete expectedBody.entities[1].TimeInstant; - delete expectedBody.entities[1].humidity.metadata.TimeInstant; - return JSON.stringify(body) === JSON.stringify(expectedBody); + delete expectedBody.entities[0].TimeInstant; + delete expectedBody.entities[0].humidity.metadata.TimeInstant; + return utils.deepEqual(body, expectedBody); } return false; }) @@ -1642,7 +1635,6 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu value: '2018-06-13T13:28:34.611Z' } ]; - iotAgentLib.update('ws5', 'WeatherStation', '', tsValue, function (error) { should.not.exist(error); contextBrokerMock.done(); @@ -1655,7 +1647,6 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu describe('NGSI-v2 - Multi-entity plugin is executed for a command update for a regular entity ', function () { beforeEach(function (done) { logger.setLevel('FATAL'); - iotAgentConfig.timestamp = true; const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00 timekeeper.freeze(time); diff --git a/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js b/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js deleted file mode 100644 index 5a278dba1..000000000 --- a/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U - * - * This file is part of fiware-iotagent-lib - * - * fiware-iotagent-lib is free software: you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the License, - * or (at your option) any later version. - * - * fiware-iotagent-lib is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with fiware-iotagent-lib. - * If not, see http://www.gnu.org/licenses/. - * - * For those usages not covered by the GNU Affero General Public License - * please contact with::daniel.moranjimenez@telefonica.com - * - * Modified by: Daniel Calvo - ATOS Research & Innovation - */ - -const iotAgentLib = require('../../../../lib/fiware-iotagent-lib'); -const utils = require('../../../tools/utils'); -const should = require('should'); -const logger = require('logops'); -const nock = require('nock'); -let contextBrokerMock; -const iotAgentConfig = { - contextBroker: { - host: '192.168.1.1', - port: '1026', - ngsiVersion: 'v2' - }, - server: { - port: 4041, - host: 'localhost' - }, - types: { - Light: { - commands: [], - type: 'Light', - lazy: [ - { - name: 'temperature', - type: 'centigrades' - } - ], - active: [ - { - name: 'pressure', - type: 'Hgmm' - } - ] - } - }, - service: 'smartgondor', - subservice: 'gardens', - providerUrl: 'http://smartgondor.com' -}; - -describe('NGSI-v2 - Timestamp processing plugin', function () { - beforeEach(function (done) { - logger.setLevel('FATAL'); - - iotAgentLib.activate(iotAgentConfig, function () { - iotAgentLib.clearAll(function () { - done(); - }); - }); - }); - - afterEach(function (done) { - iotAgentLib.clearAll(function () { - iotAgentLib.deactivate(done); - }); - }); - describe('When an update comes with a timestamp through the plugin', function () { - const values = [ - { - name: 'state', - type: 'Boolean', - value: true - }, - { - name: 'TimeInstant', - type: 'DateTime', - value: '2016-05-30T16:25:22.304Z' - } - ]; - - beforeEach(function () { - nock.cleanAll(); - - contextBrokerMock = nock('http://192.168.1.1:1026') - .matchHeader('fiware-service', 'smartgondor') - .matchHeader('fiware-servicepath', 'gardens') - .post( - '/v2/entities?options=upsert', - // this tests breaks jexlBasedTransformation-test with uses updateContextExpressionPlugin32 which do not includes Timestamp in metadata attributes - utils.readExampleFile( - './test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json' - ) - ) - .reply(204); - }); - - it('should return an entity with all its timestamps expanded to have separators', function (done) { - iotAgentLib.update('light1', 'Light', '', values, function (error) { - should.not.exist(error); - contextBrokerMock.done(); - done(); - }); - }); - }); -});