iotagent-node-lib 4.7.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +2 -2
- package/CHANGES_NEXT_RELEASE +1 -0
- package/Changelog +8 -0
- package/doc/README.md +1 -0
- package/doc/api.md +8 -4
- package/doc/devel/northboundinteractions.md +122 -15
- package/doc/models/models.md +260 -0
- package/doc/requirements.txt +1 -1
- package/docker/Mosquitto/Dockerfile +1 -1
- package/lib/fiware-iotagent-lib.js +15 -11
- package/lib/jexlTranformsMap.js +182 -35
- package/lib/model/Command.js +9 -0
- package/lib/model/Device.js +1 -0
- package/lib/services/commands/commandRegistryMongoDB.js +15 -1
- package/lib/services/commands/commandService.js +3 -3
- package/lib/services/devices/deviceRegistryMongoDB.js +1 -1
- package/lib/services/devices/deviceService.js +11 -2
- package/lib/services/northBound/contextServer-NGSI-v2.js +12 -3
- package/lib/services/northBound/contextServer.js +2 -1
- package/lib/services/northBound/northboundServer.js +1 -0
- package/lib/templates/createDevice.json +4 -0
- package/lib/templates/updateDevice.json +4 -0
- package/package.json +1 -1
- package/test/functional/testUtils.js +13 -2
- package/test/unit/expressions/jexlExpression-test.js +165 -1
- package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +0 -326
package/lib/jexlTranformsMap.js
CHANGED
|
@@ -26,63 +26,210 @@
|
|
|
26
26
|
JEXL avaliable transformations*/
|
|
27
27
|
|
|
28
28
|
const map = {
|
|
29
|
-
jsonparse: (
|
|
30
|
-
|
|
29
|
+
jsonparse: (val) => {
|
|
30
|
+
const safeOperation =
|
|
31
|
+
(fn) =>
|
|
32
|
+
(...args) => {
|
|
33
|
+
try {
|
|
34
|
+
return fn(...args);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return safeOperation(JSON.parse)(val);
|
|
40
|
+
},
|
|
41
|
+
jsonstringify: (val) => {
|
|
42
|
+
const safeOperation =
|
|
43
|
+
(fn) =>
|
|
44
|
+
(...args) => {
|
|
45
|
+
try {
|
|
46
|
+
return fn(...args);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return safeOperation(JSON.stringify)(val);
|
|
52
|
+
},
|
|
31
53
|
indexOf: (val, char) => String(val).indexOf(char),
|
|
32
54
|
length: (val) => String(val).length,
|
|
33
55
|
trim: (val) => String(val).trim(),
|
|
34
56
|
substr: (val, int1, int2) => String(val).substr(int1, int2),
|
|
35
|
-
addreduce: (arr) =>
|
|
57
|
+
addreduce: (arr) => {
|
|
58
|
+
const safeOperation =
|
|
59
|
+
(fn) =>
|
|
60
|
+
(...args) => {
|
|
61
|
+
try {
|
|
62
|
+
return fn(...args);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
return safeOperation((arr) => arr.reduce((i, v) => i + v))(arr);
|
|
68
|
+
},
|
|
36
69
|
lengtharray: (arr) => arr.length,
|
|
37
70
|
typeof: (val) => typeof val,
|
|
38
|
-
isarray:
|
|
39
|
-
isnan:
|
|
40
|
-
parseint: (val) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
isarray: Array.isArray,
|
|
72
|
+
isnan: isNaN,
|
|
73
|
+
parseint: (val) => {
|
|
74
|
+
const safeParseNumber = (fn) => (val) => {
|
|
75
|
+
const result = fn(val);
|
|
76
|
+
return isNaN(result) ? null : result;
|
|
77
|
+
};
|
|
78
|
+
return safeParseNumber((val) => parseInt(val, 10))(val);
|
|
79
|
+
},
|
|
80
|
+
parsefloat: (val) => {
|
|
81
|
+
const safeParseNumber = (fn) => (val) => {
|
|
82
|
+
const result = fn(val);
|
|
83
|
+
return isNaN(result) ? null : result;
|
|
84
|
+
};
|
|
85
|
+
return safeParseNumber(parseFloat)(val);
|
|
86
|
+
},
|
|
87
|
+
toisodate: (val) => {
|
|
88
|
+
const safeDateOperation = (fn) => (val) => {
|
|
89
|
+
const date = new Date(val);
|
|
90
|
+
return isNaN(date.getTime()) ? null : fn(date);
|
|
91
|
+
};
|
|
92
|
+
return safeDateOperation((date) => date.toISOString())(val);
|
|
93
|
+
},
|
|
94
|
+
timeoffset: (val) => {
|
|
95
|
+
const safeDateOperation = (fn) => (val) => {
|
|
96
|
+
const date = new Date(val);
|
|
97
|
+
return isNaN(date.getTime()) ? null : fn(date);
|
|
98
|
+
};
|
|
99
|
+
return safeDateOperation((date) => date.getTimezoneOffset())(val);
|
|
100
|
+
},
|
|
101
|
+
tostring: (val) => {
|
|
102
|
+
const safeOperation =
|
|
103
|
+
(fn) =>
|
|
104
|
+
(...args) => {
|
|
105
|
+
try {
|
|
106
|
+
return fn(...args);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return safeOperation((val) => val.toString())(val);
|
|
112
|
+
},
|
|
113
|
+
urlencode: encodeURI,
|
|
114
|
+
urldecode: (val) => {
|
|
115
|
+
const safeOperation =
|
|
116
|
+
(fn) =>
|
|
117
|
+
(...args) => {
|
|
118
|
+
try {
|
|
119
|
+
return fn(...args);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
return safeOperation(decodeURI)(val);
|
|
125
|
+
},
|
|
47
126
|
replacestr: (str, from, to) => str.replace(from, to),
|
|
48
|
-
replaceregexp: (str, reg, to) =>
|
|
49
|
-
|
|
50
|
-
|
|
127
|
+
replaceregexp: (str, reg, to) => {
|
|
128
|
+
const safeOperation =
|
|
129
|
+
(fn) =>
|
|
130
|
+
(...args) => {
|
|
131
|
+
try {
|
|
132
|
+
return fn(...args);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return safeOperation((str, reg, to) => str.replace(new RegExp(reg), to))(str, reg, to);
|
|
138
|
+
},
|
|
139
|
+
replaceallregexp: (str, reg, to) => {
|
|
140
|
+
const safeOperation =
|
|
141
|
+
(fn) =>
|
|
142
|
+
(...args) => {
|
|
143
|
+
try {
|
|
144
|
+
return fn(...args);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
return safeOperation((str, reg, to) => str.replace(new RegExp(reg, 'g'), to))(str, reg, to);
|
|
150
|
+
},
|
|
51
151
|
split: (str, ch) => str.split(ch),
|
|
52
152
|
joinarrtostr: (arr, ch) => arr.join(ch),
|
|
53
153
|
concatarr: (arr, arr2) => arr.concat(arr2),
|
|
54
154
|
mapper: (val, values, choices) => choices[values.findIndex((target) => target === val)],
|
|
55
155
|
thmapper: (val, values, choices) =>
|
|
56
|
-
choices[
|
|
57
|
-
values.reduce((acc, curr, i) => (acc === 0 || acc ? acc : val <= curr ? (acc = i) : (acc = null)), null)
|
|
58
|
-
],
|
|
156
|
+
choices[values.reduce((acc, curr, i) => (acc !== null ? acc : val <= curr ? i : null), null)],
|
|
59
157
|
bitwisemask: (i, mask, op, shf) =>
|
|
60
158
|
(op === '&' ? parseInt(i) & mask : op === '|' ? parseInt(i) | mask : op === '^' ? parseInt(i) ^ mask : i) >>
|
|
61
159
|
shf,
|
|
62
160
|
slice: (arr, init, end) => arr.slice(init, end),
|
|
63
|
-
addset: (arr, x) =>
|
|
64
|
-
return Array.from(new Set(arr).add(x));
|
|
65
|
-
},
|
|
161
|
+
addset: (arr, x) => Array.from(new Set(arr).add(x)),
|
|
66
162
|
removeset: (arr, x) => {
|
|
67
|
-
|
|
163
|
+
const s = new Set(arr);
|
|
68
164
|
s.delete(x);
|
|
69
165
|
return Array.from(s);
|
|
70
166
|
},
|
|
71
167
|
touppercase: (val) => String(val).toUpperCase(),
|
|
72
168
|
tolowercase: (val) => String(val).toLowerCase(),
|
|
73
|
-
floor:
|
|
74
|
-
ceil:
|
|
75
|
-
round:
|
|
76
|
-
tofixed: (val, decimals) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
169
|
+
floor: Math.floor,
|
|
170
|
+
ceil: Math.ceil,
|
|
171
|
+
round: Math.round,
|
|
172
|
+
tofixed: (val, decimals) => {
|
|
173
|
+
const num = Number.parseFloat(val);
|
|
174
|
+
const dec = Number.parseInt(decimals);
|
|
175
|
+
return isNaN(num) || isNaN(dec) ? null : num.toFixed(dec);
|
|
176
|
+
},
|
|
177
|
+
gettime: (val) => {
|
|
178
|
+
const safeDateOperation = (fn) => (val) => {
|
|
179
|
+
const date = new Date(val);
|
|
180
|
+
return isNaN(date.getTime()) ? null : fn(date);
|
|
181
|
+
};
|
|
182
|
+
return safeDateOperation((date) => date.getTime())(val);
|
|
183
|
+
},
|
|
184
|
+
toisostring: (val) => {
|
|
185
|
+
const safeDateOperation = (fn) => (val) => {
|
|
186
|
+
const date = new Date(val);
|
|
187
|
+
return isNaN(date.getTime()) ? null : fn(date);
|
|
188
|
+
};
|
|
189
|
+
return safeDateOperation((date) => date.toISOString())(val);
|
|
190
|
+
},
|
|
191
|
+
localestring: (d, timezone, options) => {
|
|
192
|
+
const safeOperation =
|
|
193
|
+
(fn) =>
|
|
194
|
+
(...args) => {
|
|
195
|
+
try {
|
|
196
|
+
return fn(...args);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
return safeOperation((d, timezone, options) => new Date(d).toLocaleString(timezone, options))(
|
|
202
|
+
d,
|
|
203
|
+
timezone,
|
|
204
|
+
options
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
now: Date.now,
|
|
208
|
+
hextostring: (val) => {
|
|
209
|
+
const safeOperation =
|
|
210
|
+
(fn) =>
|
|
211
|
+
(...args) => {
|
|
212
|
+
try {
|
|
213
|
+
return fn(...args);
|
|
214
|
+
} catch (e) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
return safeOperation((val) => {
|
|
219
|
+
if (typeof val !== 'string' || !/^[0-9a-fA-F]+$/.test(val) || val.length % 2 !== 0) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))));
|
|
223
|
+
})(val);
|
|
224
|
+
},
|
|
225
|
+
valuePicker: (val, pick) =>
|
|
226
|
+
Object.entries(val)
|
|
227
|
+
.filter(([, v]) => v === pick)
|
|
228
|
+
.map(([k]) => k),
|
|
229
|
+
valuePickerMulti: (val, pick) =>
|
|
230
|
+
Object.entries(val)
|
|
231
|
+
.filter(([, v]) => pick.includes(v))
|
|
232
|
+
.map(([k]) => k)
|
|
86
233
|
};
|
|
87
234
|
|
|
88
235
|
exports.map = map;
|
package/lib/model/Command.js
CHANGED
|
@@ -31,6 +31,15 @@ const Command = new Schema({
|
|
|
31
31
|
value: Object,
|
|
32
32
|
service: { type: String, lowercase: true },
|
|
33
33
|
subservice: String,
|
|
34
|
+
execTs: { type: Date },
|
|
35
|
+
status: String,
|
|
36
|
+
info: String,
|
|
37
|
+
onDelivered: Object,
|
|
38
|
+
onOk: Object,
|
|
39
|
+
onError: Object,
|
|
40
|
+
onInfo: Object,
|
|
41
|
+
cmdExecution: Boolean,
|
|
42
|
+
dateExpiration: { type: Date },
|
|
34
43
|
creationDate: { type: Date, default: Date.now }
|
|
35
44
|
});
|
|
36
45
|
|
package/lib/model/Device.js
CHANGED
|
@@ -89,7 +89,21 @@ function updateCommand(service, subservice, deviceId, command, callback) {
|
|
|
89
89
|
function createCommand(service, subservice, deviceId, command, callback) {
|
|
90
90
|
/* eslint-disable-next-line new-cap */
|
|
91
91
|
const commandObj = new Command.model();
|
|
92
|
-
const attributeList = [
|
|
92
|
+
const attributeList = [
|
|
93
|
+
'name',
|
|
94
|
+
'type',
|
|
95
|
+
'value',
|
|
96
|
+
// new Command fields
|
|
97
|
+
'execTs',
|
|
98
|
+
'status',
|
|
99
|
+
'info',
|
|
100
|
+
'onDelivered',
|
|
101
|
+
'onOk',
|
|
102
|
+
'onError',
|
|
103
|
+
'onInfo',
|
|
104
|
+
'cmdExecution',
|
|
105
|
+
'dateExpiration'
|
|
106
|
+
];
|
|
93
107
|
|
|
94
108
|
for (let i = 0; i < attributeList.length; i++) {
|
|
95
109
|
commandObj[attributeList[i]] = command[attributeList[i]];
|
|
@@ -43,7 +43,7 @@ function listCommands(service, subservice, deviceId, callback) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function addCommand(service, subservice, deviceId, command, callback) {
|
|
46
|
-
logger.debug(context, 'Adding command [%j] to the queue for
|
|
46
|
+
logger.debug(context, 'Adding command [%j] to the queue for deviceId [%s]', command, deviceId);
|
|
47
47
|
config.getCommandRegistry().add(service, subservice, deviceId, command, callback);
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -73,13 +73,13 @@ function addCommandDevice(service, subservice, device, command, callback) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function updateCommand(service, subservice, deviceId, name, value, callback) {
|
|
76
|
-
logger.debug(context, 'Updating command [%s] for
|
|
76
|
+
logger.debug(context, 'Updating command [%s] for deviceId [%s] with value [%s]', name, deviceId, value);
|
|
77
77
|
|
|
78
78
|
config.getCommandRegistry().update(service, subservice, deviceId, value, callback);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function removeCommand(service, subservice, deviceId, name, callback) {
|
|
82
|
-
logger.debug(context, 'Removing command [%s] from
|
|
82
|
+
logger.debug(context, 'Removing command [%s] from deviceId [%s]', name, deviceId);
|
|
83
83
|
|
|
84
84
|
config.getCommandRegistry().remove(service, subservice, deviceId, name, callback);
|
|
85
85
|
}
|
|
@@ -90,7 +90,7 @@ function storeDevice(newDevice, callback) {
|
|
|
90
90
|
if (error.code === 11000) {
|
|
91
91
|
logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error);
|
|
92
92
|
|
|
93
|
-
callback(new errors.DuplicateDeviceId(newDevice));
|
|
93
|
+
callback(new errors.DuplicateDeviceId(newDevice), newDevice);
|
|
94
94
|
} else {
|
|
95
95
|
logger.debug(context, 'Error storing device information: %s', error);
|
|
96
96
|
|
|
@@ -40,6 +40,8 @@ const registrationUtils = require('./registrationUtils');
|
|
|
40
40
|
const subscriptions = require('../ngsi/subscriptionService');
|
|
41
41
|
const expressionPlugin = require('./../../plugins/expressionPlugin');
|
|
42
42
|
const pluginUtils = require('./../../plugins/pluginUtils');
|
|
43
|
+
const alarms = require('../common/alarmManagement');
|
|
44
|
+
const constants = require('../../constants');
|
|
43
45
|
const _ = require('underscore');
|
|
44
46
|
const context = {
|
|
45
47
|
op: 'IoTAgentNGSI.DeviceService'
|
|
@@ -250,7 +252,7 @@ function registerDevice(deviceObj, callback) {
|
|
|
250
252
|
/* eslint-disable-next-line no-unused-vars */
|
|
251
253
|
function (error, device) {
|
|
252
254
|
if (!error) {
|
|
253
|
-
innerCb(new errors.DuplicateDeviceId(deviceObj));
|
|
255
|
+
innerCb(new errors.DuplicateDeviceId(deviceObj), device);
|
|
254
256
|
} else {
|
|
255
257
|
innerCb();
|
|
256
258
|
}
|
|
@@ -664,7 +666,14 @@ function findOrCreate(deviceId, apikey, group, callback) {
|
|
|
664
666
|
if (group.autoprovision === undefined || group.autoprovision === true) {
|
|
665
667
|
logger.debug(context, 'Registering autoprovision of Device %j for its conf %j', newDevice, group);
|
|
666
668
|
registerDevice(newDevice, function (error, device) {
|
|
667
|
-
|
|
669
|
+
if (error && error.name === 'DUPLICATE_DEVICE_ID') {
|
|
670
|
+
alarms.release(constants.MONGO_ALARM, error);
|
|
671
|
+
logger.warn(context, 'Error %j already registered autoprovisioned device: %j ', error, device);
|
|
672
|
+
callback(null, device, group);
|
|
673
|
+
} else {
|
|
674
|
+
logger.debug(context, 'registered autoprovisioned device: %j ', device);
|
|
675
|
+
callback(error, device, group);
|
|
676
|
+
}
|
|
668
677
|
});
|
|
669
678
|
} else {
|
|
670
679
|
logger.info(
|
|
@@ -292,9 +292,18 @@ function handleNotificationNgsi2(req, res, next) {
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
|
+
logger.debug(context, 'extracted atts %j from dataElement %j', atts, dataElement);
|
|
296
|
+
var id = null;
|
|
297
|
+
var type = null;
|
|
298
|
+
if (dataElement.targetEntityId && dataElement.targetEntityId.value) {
|
|
299
|
+
id = dataElement.targetEntityId.value;
|
|
300
|
+
}
|
|
301
|
+
if (dataElement.targetEntityType && dataElement.targetEntityType.value) {
|
|
302
|
+
type = dataElement.targetEntityType.value;
|
|
303
|
+
}
|
|
295
304
|
deviceService.getDeviceByNameAndType(
|
|
296
|
-
|
|
297
|
-
|
|
305
|
+
id,
|
|
306
|
+
type,
|
|
298
307
|
req.headers['fiware-service'],
|
|
299
308
|
req.headers['fiware-servicepath'],
|
|
300
309
|
function (error, device) {
|
|
@@ -336,7 +345,7 @@ function handleNotificationNgsi2(req, res, next) {
|
|
|
336
345
|
logger.error(context, 'Error found when processing notification: %j', error);
|
|
337
346
|
next(error);
|
|
338
347
|
} else {
|
|
339
|
-
res.status(
|
|
348
|
+
res.status(204).json();
|
|
340
349
|
}
|
|
341
350
|
}
|
|
342
351
|
|
|
@@ -30,7 +30,7 @@ const context = {
|
|
|
30
30
|
op: 'IoTAgentNGSI.ContextServer'
|
|
31
31
|
};
|
|
32
32
|
const contextServerUtils = require('./contextServerUtils');
|
|
33
|
-
|
|
33
|
+
const executeUpdateSideEffects = contextServerUtils.executeUpdateSideEffects;
|
|
34
34
|
let contextServerHandler;
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -157,4 +157,5 @@ exports.setCommandHandler = intoTrans(context, setCommandHandler);
|
|
|
157
157
|
exports.setNotificationHandler = intoTrans(context, setNotificationHandler);
|
|
158
158
|
exports.addNotificationMiddleware = intoTrans(context, addNotificationMiddleware);
|
|
159
159
|
exports.setQueryHandler = intoTrans(context, setQueryHandler);
|
|
160
|
+
exports.executeUpdateSideEffects = intoTrans(context, executeUpdateSideEffects);
|
|
160
161
|
exports.init = init;
|
|
@@ -125,6 +125,7 @@ exports.setRemoveDeviceHandler = intoTrans(context, deviceProvisioning.setRemove
|
|
|
125
125
|
exports.addDeviceProvisionMiddleware = deviceProvisioning.addDeviceProvisionMiddleware;
|
|
126
126
|
exports.addConfigurationProvisionMiddleware = groupProvisioning.addConfigurationProvisionMiddleware;
|
|
127
127
|
exports.addNotificationMiddleware = contextServer.addNotificationMiddleware;
|
|
128
|
+
exports.executeUpdateSideEffects = contextServer.executeUpdateSideEffects;
|
|
128
129
|
exports.clear = clear;
|
|
129
130
|
exports.start = intoTrans(context, start);
|
|
130
131
|
exports.stop = intoTrans(context, stop);
|
|
@@ -145,6 +145,10 @@
|
|
|
145
145
|
"description": "Optional expression for command transformation",
|
|
146
146
|
"type": "string"
|
|
147
147
|
},
|
|
148
|
+
"headers": {
|
|
149
|
+
"description": "Optional headers to include with command",
|
|
150
|
+
"type": "object"
|
|
151
|
+
},
|
|
148
152
|
"payloadType": {
|
|
149
153
|
"description": "Payload type",
|
|
150
154
|
"type": "string"
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.8.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -74,16 +74,27 @@ function sendMeasureIotaLib(measure, provision) {
|
|
|
74
74
|
* This is not a problem for the tests using other transports than Lib, in that case, the type will be retrieved
|
|
75
75
|
* from the real provision.
|
|
76
76
|
*/
|
|
77
|
+
let typeInformation = {
|
|
78
|
+
service: provision.headers['fiware-service'],
|
|
79
|
+
subservice: provision.headers['fiware-servicepath']
|
|
80
|
+
};
|
|
77
81
|
let type;
|
|
82
|
+
let staticAttrs;
|
|
78
83
|
if (Array.isArray(provision.json.services) && provision.json.services.length > 0) {
|
|
79
84
|
type = provision.json.services[0].entity_type;
|
|
85
|
+
staticAttrs = provision.json.services[0].static_attributes;
|
|
80
86
|
} else {
|
|
81
87
|
type = DEF_TYPE;
|
|
82
88
|
}
|
|
89
|
+
typeInformation.type = type;
|
|
90
|
+
if (staticAttrs) {
|
|
91
|
+
typeInformation.staticAttributes = staticAttrs;
|
|
92
|
+
}
|
|
93
|
+
typeInformation.id = measure.qs.i;
|
|
83
94
|
iotAgentLib.update(
|
|
84
95
|
type + ':' + measure.qs.i,
|
|
85
96
|
type,
|
|
86
|
-
|
|
97
|
+
typeInformation,
|
|
87
98
|
jsonToIotaMeasures(measure.json),
|
|
88
99
|
function (error, result, body) {
|
|
89
100
|
error ? reject(error) : resolve(result);
|
|
@@ -230,7 +241,7 @@ async function testCase(measure, expectation, provision, env, config, type, tran
|
|
|
230
241
|
if (transport === 'MQTT') {
|
|
231
242
|
try {
|
|
232
243
|
let client = await MQTT.connectAsync('mqtt://' + config.mqtt.host);
|
|
233
|
-
await client.publish('/' + measure.qs.k + '/' + measure.qs.i + '/attrs', JSON.stringify(measure.json));
|
|
244
|
+
await client.publish('/json/' + measure.qs.k + '/' + measure.qs.i + '/attrs', JSON.stringify(measure.json));
|
|
234
245
|
await client.end();
|
|
235
246
|
} catch (error) {
|
|
236
247
|
expect.fail(ERR_MQTT + error);
|
|
@@ -40,7 +40,12 @@ describe('Jexl expression interpreter', function () {
|
|
|
40
40
|
object: {
|
|
41
41
|
name: 'John',
|
|
42
42
|
surname: 'Doe'
|
|
43
|
-
}
|
|
43
|
+
},
|
|
44
|
+
invalidJson: '{"name": "John", surname": "Doe"}', // Invalid JSON for jsonparse
|
|
45
|
+
invalidDate: 'invalid-date', // Invalid date for toisodate and other date functions
|
|
46
|
+
emptyArray: [], // Empty array for addreduce
|
|
47
|
+
invalidHex: 'zz', // Invalid hex string for hextostring
|
|
48
|
+
nonNumeric: 'not-a-number' // Non-numeric input for parseint, parsefloat
|
|
44
49
|
};
|
|
45
50
|
|
|
46
51
|
describe('When a expression with a single value is parsed', function () {
|
|
@@ -392,4 +397,163 @@ describe('Jexl expression interpreter', function () {
|
|
|
392
397
|
});
|
|
393
398
|
});
|
|
394
399
|
});
|
|
400
|
+
|
|
401
|
+
// Check errors
|
|
402
|
+
describe('When invalid inputs are used', function () {
|
|
403
|
+
describe('When an invalid JSON string is parsed', function () {
|
|
404
|
+
it('should return null for jsonparse', function (done) {
|
|
405
|
+
expressionParser.parse('invalidJson|jsonparse', scope, function (error, result) {
|
|
406
|
+
should.not.exist(error);
|
|
407
|
+
should(result).be.Null();
|
|
408
|
+
done();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('When an invalid date string is parsed', function () {
|
|
414
|
+
it('should return null for toisodate', function (done) {
|
|
415
|
+
expressionParser.parse('invalidDate|toisodate', scope, function (error, result) {
|
|
416
|
+
should.not.exist(error);
|
|
417
|
+
should(result).be.Null();
|
|
418
|
+
done();
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should return null for timeoffset', function (done) {
|
|
423
|
+
expressionParser.parse('invalidDate|timeoffset', scope, function (error, result) {
|
|
424
|
+
should.not.exist(error);
|
|
425
|
+
should(result).be.Null();
|
|
426
|
+
done();
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
describe('When an empty array is processed with addreduce', function () {
|
|
432
|
+
it('should return null for addreduce', function (done) {
|
|
433
|
+
expressionParser.parse('emptyArray|addreduce', scope, function (error, result) {
|
|
434
|
+
should.not.exist(error);
|
|
435
|
+
should(result).be.Null();
|
|
436
|
+
done();
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('When a non-numeric string is parsed by parseint and parsefloat', function () {
|
|
442
|
+
it('should return null for parseint', function (done) {
|
|
443
|
+
expressionParser.parse('nonNumeric|parseint', scope, function (error, result) {
|
|
444
|
+
should.not.exist(error);
|
|
445
|
+
should(result).be.Null();
|
|
446
|
+
done();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should return null for parsefloat', function (done) {
|
|
451
|
+
expressionParser.parse('nonNumeric|parsefloat', scope, function (error, result) {
|
|
452
|
+
should.not.exist(error);
|
|
453
|
+
should(result).be.Null();
|
|
454
|
+
done();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe('When an invalid hex string is processed by hextostring', function () {
|
|
460
|
+
it('should return null for hextostring', function (done) {
|
|
461
|
+
expressionParser.parse('invalidHex|hextostring', scope, function (error, result) {
|
|
462
|
+
should.not.exist(error);
|
|
463
|
+
should(result).be.Null();
|
|
464
|
+
done();
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('When invalid regular expressions are used in replace functions', function () {
|
|
470
|
+
it('should return null for replaceregexp with invalid regex', function (done) {
|
|
471
|
+
expressionParser.parse(
|
|
472
|
+
'theString|replaceregexp("[a-z", "replacement")',
|
|
473
|
+
scope,
|
|
474
|
+
function (error, result) {
|
|
475
|
+
should.not.exist(error);
|
|
476
|
+
should(result).be.Null();
|
|
477
|
+
done();
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should return null for replaceallregexp with invalid regex', function (done) {
|
|
483
|
+
expressionParser.parse(
|
|
484
|
+
'theString|replaceallregexp("[a-z", "replacement")',
|
|
485
|
+
scope,
|
|
486
|
+
function (error, result) {
|
|
487
|
+
should.not.exist(error);
|
|
488
|
+
should(result).be.Null();
|
|
489
|
+
done();
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
describe('When tostring is used with null or undefined', function () {
|
|
496
|
+
it('should return null for tostring(null)', function (done) {
|
|
497
|
+
expressionParser.parse('null|tostring', scope, function (error, result) {
|
|
498
|
+
should.not.exist(error);
|
|
499
|
+
should(result).be.Null();
|
|
500
|
+
done();
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should return null for tostring(undefined)', function (done) {
|
|
505
|
+
expressionParser.parse('undefined|tostring', scope, function (error, result) {
|
|
506
|
+
should.not.exist(error);
|
|
507
|
+
should(result).be.Null();
|
|
508
|
+
done();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
describe('When urldecode is used with an invalid URI', function () {
|
|
514
|
+
it('should return null for urldecode with malformed URI', function (done) {
|
|
515
|
+
expressionParser.parse('"%%%invalidURI"|urldecode', scope, function (error, result) {
|
|
516
|
+
should.not.exist(error);
|
|
517
|
+
should(result).be.Null();
|
|
518
|
+
done();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
describe('When tofixed is used with invalid inputs', function () {
|
|
524
|
+
it('should return null for tofixed with non-numeric value', function (done) {
|
|
525
|
+
expressionParser.parse('"notANumber"|tofixed(2)', scope, function (error, result) {
|
|
526
|
+
should.not.exist(error);
|
|
527
|
+
should(result).be.Null();
|
|
528
|
+
done();
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should return null for tofixed with invalid decimal value', function (done) {
|
|
533
|
+
expressionParser.parse('"123.456"|tofixed("invalid")', scope, function (error, result) {
|
|
534
|
+
should.not.exist(error);
|
|
535
|
+
should(result).be.Null();
|
|
536
|
+
done();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('When gettime is used with invalid date', function () {
|
|
542
|
+
it('should return null for gettime with invalid date string', function (done) {
|
|
543
|
+
expressionParser.parse('"invalidDate"|gettime', scope, function (error, result) {
|
|
544
|
+
should.not.exist(error);
|
|
545
|
+
should(result).be.Null();
|
|
546
|
+
done();
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it('should return null for gettime with null', function (done) {
|
|
551
|
+
expressionParser.parse('null|gettime', scope, function (error, result) {
|
|
552
|
+
should.not.exist(error);
|
|
553
|
+
should(result).be.Null();
|
|
554
|
+
done();
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
});
|
|
395
559
|
});
|