UNPKG

iotagent-node-lib

Version:

IoT Agent library to interface with NGSI Context Broker

630 lines (567 loc) 24.9 kB
/* * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U * * This file is part of fiware-iotagent-lib * * fiware-iotagent-lib is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * fiware-iotagent-lib is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with fiware-iotagent-lib. * If not, see http://www.gnu.org/licenses/. * * For those usages not covered by the GNU Affero General Public License * please contact with::[contacto@tid.es] */ const should = require('should'); const expressionParser = require('../../../lib/plugins/jexlParser'); expressionParser.setTransforms({}); describe('Jexl expression interpreter', function () { const scope = { value: 6, other: 3, theString: '12.6, -19.4', spaces: '5 a b c d 5', big: 2000, number: 145, number2: 155, number3inside: 200, floatNumber: 1.2, array: [1, 2], object: { name: 'John', surname: 'Doe' }, invalidJson: '{"name": "John", surname": "Doe"}', // Invalid JSON for jsonparse invalidDate: 'invalid-date', // Invalid date for toisodate and other date functions emptyArray: [], // Empty array for addreduce invalidHex: 'zz', // Invalid hex string for hextostring nonNumeric: 'not-a-number' // Non-numeric input for parseint, parsefloat }; describe('When a expression with a single value is parsed', function () { it('should return that value', function (done) { expressionParser.parse('5 * value', scope, function (error, result) { should.not.exist(error); result.should.equal(30); done(); }); }); }); const arithmetic = [ ['5 * value', 30], ['(6 + value) * 3', 36], ['value / 12 + 1', 1.5], ['(5 + 2) * (value + 7)', 91], ['(5 - other) * (value + 7)', 26], ['3 * 5.2', 15.6], ['value * 5.2', 31.2] ]; function arithmeticUseCase(arithmeticExpr) { describe('When an expression with the arithmetic operation [' + arithmeticExpr[0] + '] is parsed', function () { it('should be interpreted appropriately', function (done) { expressionParser.parse(arithmeticExpr[0], scope, function (error, result) { should.not.exist(error); result.should.approximately(arithmeticExpr[1], 0.000001); done(); }); }); }); } for (let i = 0; i < arithmetic.length; i++) { arithmeticUseCase(arithmetic[i]); } describe('When an expression with two strings is concatenated', function () { it('should return the concatenation of both strings', function (done) { expressionParser.parse('"Pruebas" + "DeStrings"', scope, function (error, result) { should.not.exist(error); result.should.equal('PruebasDeStrings'); done(); }); }); }); describe('When string transformation functions are executed', function () { it('should return the appropriate piece of the string', function (done) { expressionParser.parse( 'theString|substr(theString|indexOf(",") + 1, theString|length)|trim', scope, function (error, result) { should.not.exist(error); result.should.equal('-19.4'); done(); } ); }); }); describe('When an expression contains variables with numbers', function () { it('should return the appropriate result', function (done) { expressionParser.parse('number + number2 + number3inside', scope, function (error, result) { should.not.exist(error); result.should.equal(500); done(); }); }); }); describe('When an expression contains variables with float numbers', function () { it('should return the appropriate result', function (done) { expressionParser.parse('floatNumber * 2', scope, function (error, result) { should.not.exist(error); result.should.equal(2.4); done(); }); }); }); describe('When an expression contains variables with float numbers and strings', function () { it('should return the appropriate result', function (done) { expressionParser.parse('floatNumber +"echo"', scope, function (error, result) { should.not.exist(error); result.should.equal('1.2echo'); done(); }); }); }); describe('When an expression contains multiple parenthesis', function () { it('should return the appropriate result', function (done) { expressionParser.parse('((number) * (number2))', scope, function (error, result) { should.not.exist(error); result.should.equal(22475); done(); }); }); }); describe('When trim() function is executed', function () { it('should return the appropriate piece of the string', function (done) { expressionParser.parse('spaces|trim', scope, function (error, result) { should.not.exist(error); result.should.equal('5 a b c d 5'); done(); }); }); }); describe('When an expression with strings containing spaces is concatenated', function () { it('should honour the whitespaces', function (done) { expressionParser.parse('"Pruebas " + "De Strings"', scope, function (error, result) { should.not.exist(error); result.should.equal('Pruebas De Strings'); done(); }); }); }); describe('When an expression with strings with single quotation marks is parsed', function () { it('should accept the strings', function (done) { expressionParser.parse("'Pruebas ' + 'De Strings'", scope, function (error, result) { should.not.exist(error); result.should.equal('Pruebas De Strings'); done(); }); }); }); describe('When a string is concatenated with a number', function () { it('should result in a string concatenation', function (done) { expressionParser.parse('"number " + 5', scope, function (error, result) { should.not.exist(error); result.should.equal('number 5'); done(); }); }); }); describe('When an expression with a parse error is parsed', function () { it('should raise an INVALID_EXPRESSION error', function (done) { /* eslint-disable-next-line no-unused-vars */ expressionParser.parse('"numb+sd ññ ((', scope, function (error, result) { should.exist(error); error.name.should.equal('INVALID_EXPRESSION'); done(); }); }); }); describe('When an string function is used with an expression', function () { it('should work on the expression value', function (done) { expressionParser.parse('(24 * big)|indexOf("80")', scope, function (error, result) { should.not.exist(error); result.should.equal(1); done(); }); }); }); describe('When an slice function is used with an string', function () { it('should work on the expression value', function (done) { expressionParser.parse('"AABBCC"|slice(2,4)', scope, function (error, result) { should.not.exist(error); result.should.equal('BB'); done(); }); }); }); describe('When an ternary operator is used with an expression', function () { it('should work on the expression value', function (done) { expressionParser.parse('value == 6? true : false', scope, function (error, result) { should.not.exist(error); result.should.equal(true); done(); }); }); }); describe('When an logic operator is used with an expression', function () { it('should work on the expression value', function (done) { expressionParser.parse('value == 6 && spaces|indexOf("a")>0', scope, function (error, result) { should.not.exist(error); result.should.equal(true); done(); }); }); }); describe('When an concatarr function is used with an array', function () { it('should work on the expression value', function (done) { expressionParser.parse('array|concatarr(array)', scope, function (error, result) { should.not.exist(error); result.should.deepEqual([1, 2, 1, 2]); done(); }); }); }); describe('When an joinarrtostr function is used with an array', function () { it('should work on the expression value', function (done) { expressionParser.parse('array|joinarrtostr("_")', scope, function (error, result) { should.not.exist(error); result.should.equal('1_2'); done(); }); }); }); describe('When a function is applied to an array', function () { it('should work on the expression value', function (done) { expressionParser.parse('array[1]+1', scope, function (error, result) { should.not.exist(error); result.should.equal(3); done(); }); }); }); describe('When a function is applied to an object', function () { it('should work on the expression value', function (done) { expressionParser.parse('object.name', scope, function (error, result) { should.not.exist(error); result.should.equal('John'); done(); }); }); }); describe('When an transformstion map is provided', function () { const niceMap = { method1: (val) => val, method2: (val) => val }; const wrongMap = { wrongTransformation1: 'not a function', wrongTransformation2: 666, rightTransformation: (val) => val }; const noMap = "I'm not what you expect"; it('it should detect when it is not a map', function (done) { const [error, message, resultMap] = expressionParser.checkTransformationMap(noMap); should.exist(error); message.should.equal('No transformations were added to JEXL Parser'); resultMap.should.eql({}); done(); }); it('it should be empty {}', function (done) { const [error, message, resultMap] = expressionParser.checkTransformationMap({}); should.not.exist(error); message.should.equal('No transformations were added to JEXL Parser'); resultMap.should.eql({}); done(); }); it('it should be empty null', function (done) { const [error, message, resultMap] = expressionParser.checkTransformationMap(null); should.not.exist(error); message.should.equal('No transformations were added to JEXL Parser'); resultMap.should.eql({}); done(); }); it('it should detect wrong transformations (not a funtion)', function (done) { const [error, message, resultMap] = expressionParser.checkTransformationMap(wrongMap); should.not.exist(error); message.should.equal('wrongTransformation1,wrongTransformation2 must be a function'); should.not.exist(resultMap.wrongTransformation1); should.not.exist(resultMap.wrongTransformation2); should.exist(resultMap.rightTransformation); done(); }); it('it should be correct (map of funtions)', function (done) { const [error, message, resultMap] = expressionParser.checkTransformationMap(niceMap); should.not.exist(error); message.should.equal('Transformations can be added to JEXL parser'); resultMap.should.eql(niceMap); done(); }); }); describe('When a JSON parse transformation is applied', function () { it('should work on the expression value', function (done) { expressionParser.parse( '"{\\"name\\":\\"John\\",\\"surname\\":\\"Doe\\"}"|jsonparse', scope, function (error, result) { should.not.exist(error); result.should.eql(scope.object); done(); } ); }); }); describe('When a JSON stringify transformation is applied', function () { it('should work on the expression value', function (done) { expressionParser.parse('{name: "John",surname: "Doe"}|jsonstringify', scope, function (error, result) { should.not.exist(error); result.should.equal(JSON.stringify(scope.object)); done(); }); }); }); describe('When a JSON with an NGSI operator is applied', function () { it('should work on the expression value', function (done) { const scope = { ina: 34, outa: 4 }; expressionParser.parse('{$inc:ina-outa}', scope, function (error, result) { should.not.exist(error); result.should.eql({ $inc: 30 }); done(); }); }); }); describe('When an expression aims at creating an object', function () { it('it should work', function (done) { expressionParser.parse('{type:"Point",coordinates: [value,other]}', scope, function (error, result) { should.not.exist(error); result.should.deepEqual({ type: 'Point', coordinates: [6, 3] }); done(); }); }); }); describe('When an expression access to non existent property', function () { it('it should return null', function (done) { expressionParser.parse('object["nothing"]', scope, function (error, result) { should.not.exist(error); should.equal(result, null); done(); }); }); it('it should return null', function (done) { expressionParser.parse('object["nothing"]?object["nothing"]:null', scope, function (error, result) { should.not.exist(error); should.equal(result, null); done(); }); }); }); describe('When number localization functions are executed', function () { it('should return the localized string representation of the number', function (done) { const scope = { theNumber: 1234567.89, locale: 'es-ES', options: { minimumFractionDigits: 2, maximumFractionDigits: 2 } }; expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) { should.not.exist(error); result.should.equal('1.234.567,89'); done(); }); }); it('should return null for invalid number input', function (done) { const scope = { theNumber: 'not_a_number', locale: 'en-US', options: {} }; expressionParser.parse('theNumber|localestringnumber(locale, options)', scope, function (error, result) { should.not.exist(error); should.equal(result, null); done(); }); }); }); // Check errors describe('When invalid inputs are used', function () { describe('When an invalid JSON string is parsed', function () { it('should return null for jsonparse', function (done) { expressionParser.parse('invalidJson|jsonparse', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When an invalid date string is parsed', function () { it('should return null for toisodate', function (done) { expressionParser.parse('invalidDate|toisodate', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); it('should return null for timeoffset', function (done) { expressionParser.parse('invalidDate|timeoffset', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When an empty array is processed with addreduce', function () { it('should return null for addreduce', function (done) { expressionParser.parse('emptyArray|addreduce', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When a non-numeric string is parsed by parseint and parsefloat', function () { it('should return null for parseint', function (done) { expressionParser.parse('nonNumeric|parseint', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); it('should return null for parsefloat', function (done) { expressionParser.parse('nonNumeric|parsefloat', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When an invalid hex string is processed by hextostring', function () { it('should return null for hextostring', function (done) { expressionParser.parse('invalidHex|hextostring', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When invalid regular expressions are used in replace functions', function () { it('should return null for replaceregexp with invalid regex', function (done) { expressionParser.parse( 'theString|replaceregexp("[a-z", "replacement")', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); } ); }); it('should return null for replaceallregexp with invalid regex', function (done) { expressionParser.parse( 'theString|replaceallregexp("[a-z", "replacement")', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); } ); }); }); describe('When tostring is used with null or undefined', function () { it('should return null for tostring(null)', function (done) { expressionParser.parse('null|tostring', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); it('should return null for tostring(undefined)', function (done) { expressionParser.parse('undefined|tostring', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When urldecode is used with an invalid URI', function () { it('should return null for urldecode with malformed URI', function (done) { expressionParser.parse('"%%%invalidURI"|urldecode', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When tofixed is used with invalid inputs', function () { it('should return null for tofixed with non-numeric value', function (done) { expressionParser.parse('"notANumber"|tofixed(2)', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); it('should return null for tofixed with invalid decimal value', function (done) { expressionParser.parse('"123.456"|tofixed("invalid")', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); describe('When gettime is used with invalid date', function () { it('should return null for gettime with invalid date string', function (done) { expressionParser.parse('"invalidDate"|gettime', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); it('should return null for gettime with null', function (done) { expressionParser.parse('null|gettime', scope, function (error, result) { should.not.exist(error); should(result).be.Null(); done(); }); }); }); }); describe('When hash function is executed', function () { it('should return an 8-character uppercase hexadecimal hash', function (done) { expressionParser.parse('"test"|hash', scope, function (error, result) { should.not.exist(error); result.should.match(/^[0-9A-F]{8}$/); result.should.equal('00364492'); done(); }); }); it('should return consistent hash for the same input', function (done) { expressionParser.parse('value|hash', scope, function (error, result) { should.not.exist(error); result.should.match(/^[0-9A-F]{8}$/); const firstHash = result; expressionParser.parse('value|hash', scope, function (error, result) { should.not.exist(error); result.should.equal(firstHash); done(); }); }); }); it('should handle numeric values', function (done) { expressionParser.parse('number|hash', scope, function (error, result) { should.not.exist(error); result.should.match(/^[0-9A-F]{8}$/); done(); }); }); it('should handle empty string', function (done) { expressionParser.parse('""|hash', scope, function (error, result) { should.not.exist(error); result.should.equal('00000000'); done(); }); }); }); });