7a5da39410d7c8834db0841fb4ede7b10444173d
[project/firewall4.git] / tests / mock.uc
1 {%
2 let _fs = require("fs");
3
4 let _log = (level, fmt, ...args) => {
5 let color, prefix;
6
7 switch (level) {
8 case 'info':
9 color = 34;
10 prefix = '!';
11 break;
12
13 case 'warn':
14 color = 33;
15 prefix = 'W';
16 break;
17
18 case 'error':
19 color = 31;
20 prefix = 'E';
21 break;
22
23 default:
24 color = 0;
25 prefix = 'I';
26 }
27
28 let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt);
29 warn(replace(sprintf(f, ...args), "\n", "\n "), "\n");
30 };
31
32 let I = (...args) => _log('info', ...args);
33 let N = (...args) => _log('notice', ...args);
34 let W = (...args) => _log('warn', ...args);
35 let E = (...args) => _log('error', ...args);
36
37 let read_json_file = (path) => {
38 let fd = _fs.open(path, "r");
39 if (fd) {
40 let data = fd.read("all");
41 fd.close();
42
43 try {
44 return json(data);
45 }
46 catch (e) {
47 E("Unable to parse JSON data in %s: %s", path, e);
48
49 return NaN;
50 }
51 }
52
53 return null;
54 };
55
56 let format_json = (data) => {
57 let rv;
58
59 let format_value = (value) => {
60 switch (type(value)) {
61 case "object":
62 return sprintf("{ /* %d keys */ }", length(value));
63
64 case "array":
65 return sprintf("[ /* %d items */ ]", length(value));
66
67 case "string":
68 if (length(value) > 64)
69 value = substr(value, 0, 64) + "...";
70
71 /* fall through */
72 return sprintf("%J", value);
73
74 default:
75 return sprintf("%J", value);
76 }
77 };
78
79 switch (type(data)) {
80 case "object":
81 rv = "{";
82
83 let k = sort(keys(data));
84
85 for (let i, n in k)
86 rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n]));
87
88 rv += " }";
89 break;
90
91 case "array":
92 rv = "[";
93
94 for (let i, v in data)
95 rv += (i ? "," : "") + " " + format_value(v);
96
97 rv += " ]";
98 break;
99
100 default:
101 rv = format_value(data);
102 }
103
104 return rv;
105 };
106
107 let trace_call = (ns, func, args) => {
108 let msg = "[call] " +
109 (ns ? ns + "." : "") +
110 func;
111
112 for (let k, v in args) {
113 msg += ' ' + k + ' <';
114
115 switch (type(v)) {
116 case "array":
117 case "object":
118 msg += format_json(v);
119 break;
120
121 default:
122 msg += v;
123 }
124
125 msg += '>';
126 }
127
128 switch (TRACE_CALLS) {
129 case '1':
130 case 'stdout':
131 print(msg + "\n");
132 break;
133
134 case 'stderr':
135 warn(msg + "\n");
136 break;
137 }
138 };
139
140
141 /* Setup mock environment */
142 let mocks = {
143
144 /* Mock ubus module */
145 ubus: {
146 connect: function() {
147 let self = this;
148
149 return {
150 call: (object, method, args) => {
151 let signature = [ object + "~" + method ];
152
153 if (type(args) == "object") {
154 for (let i, k in sort(keys(args))) {
155 switch (type(args[k])) {
156 case "string":
157 case "double":
158 case "bool":
159 case "int":
160 push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_"));
161 break;
162
163 default:
164 push(signature, type(args[k]));
165 }
166 }
167 }
168
169 let candidates = [];
170
171 for (let i = length(signature); i > 0; i--) {
172 let path = sprintf("./tests/mocks/ubus/%s.json", join("~", signature)),
173 mock = read_json_file(path);
174
175 if (mock != mock) {
176 self._error = "Invalid argument";
177
178 return null;
179 }
180 else if (mock) {
181 trace_call("ctx", "call", { object, method, args });
182
183 return mock;
184 }
185
186 push(candidates, path);
187 pop(signature);
188 }
189
190 I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args);
191 I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates));
192
193 self._error = "Method not found";
194
195 return null;
196 },
197
198 disconnect: () => null,
199
200 error: () => self.error()
201 };
202 },
203
204 error: function() {
205 let e = this._error;
206 delete(this._error);
207
208 return e;
209 }
210 },
211
212
213 /* Mock uci module */
214 uci: {
215 cursor: () => ({
216 _configs: {},
217
218 load: function(file) {
219 let basename = replace(file, /^.+\//, ''),
220 path = sprintf("./tests/mocks/uci/%s.json", basename),
221 mock = read_json_file(path);
222
223 if (!mock || mock != mock) {
224 I("No configuration fixture defined for uci package %s.", file);
225 I("Provide a mock configuration through the following JSON file:\n%s\n", path);
226
227 return null;
228 }
229
230 this._configs[basename] = mock;
231 },
232
233 _get_section: function(config, section) {
234 if (!exists(this._configs, config)) {
235 this.load(config);
236
237 if (!exists(this._configs, config))
238 return null;
239 }
240
241 let extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$");
242
243 if (extended) {
244 let stype = extended[1],
245 sindex = +extended[2],
246 sections = [];
247
248 for (let sid, sobj in this._configs[config])
249 if (sobj[".type"] == stype)
250 push(sections, sobj);
251
252 sort(sections, (a, b) => (a[".index"] || 999) - (b[".index"] || 999));
253
254 if (sindex < 0)
255 sindex = sections.length + sindex;
256
257 return sections[sindex];
258 }
259
260 return this._configs[config][section];
261 },
262
263 get: function(config, section, option) {
264 let sobj = this._get_section(config, section);
265
266 if (option && index(option, ".") == 0)
267 return null;
268 else if (sobj && option)
269 return sobj[option];
270 else if (sobj)
271 return sobj[".type"];
272 },
273
274 get_all: function(config, section) {
275 return section ? this._get_section(config, section) : this._configs[config];
276 },
277
278 foreach: function(config, stype, cb) {
279 let rv = false;
280
281 if (exists(this._configs, config)) {
282 let i = 0;
283
284 for (let sid, sobj in this._configs[config]) {
285 i++;
286
287 if (stype == null || sobj[".type"] == stype) {
288 cb({ ".index": i - 1, ".type": stype, ".name": sid, ...sobj });
289 rv = true;
290 }
291 }
292 }
293
294 return rv;
295 }
296 })
297 },
298
299
300 /* Mock fs module */
301 fs: {
302 readlink: function(path) {
303 trace_call("fs", "readlink", { path });
304
305 return path + "-link";
306 },
307
308 stat: function(path) {
309 let file = sprintf("./tests/mocks/fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')),
310 mock = read_json_file(file);
311
312 if (!mock || mock != mock) {
313 I("No stat result fixture defined for fs.stat() call on %s.", path);
314 I("Provide a mock result through the following JSON file:\n%s\n", file);
315
316 if (match(path, /\/$/))
317 mock = { type: "directory" };
318 else
319 mock = { type: "file" };
320 }
321
322 trace_call("fs", "stat", { path });
323
324 return mock;
325 },
326
327 unlink: function(path) {
328 trace_call("fs", "unlink", { path });
329
330 return true;
331 },
332
333 popen: (cmdline, mode) => {
334 let read = (!mode || index(mode, "r") != -1),
335 path = sprintf("./tests/mocks/fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')),
336 fd = read ? _fs.open(path, "r") : null,
337 mock = null;
338
339 if (fd) {
340 mock = fd.read("all");
341 fd.close();
342 }
343
344 if (read && !mock) {
345 I("No stdout fixture defined for fs.popen() command %s.", cmdline);
346 I("Provide a mock output through the following text file:\n%s\n", path);
347
348 return null;
349 }
350
351 trace_call("fs", "popen", { cmdline, mode });
352
353 return {
354 read: function(amount) {
355 let rv;
356
357 switch (amount) {
358 case "all":
359 rv = mock;
360 mock = "";
361 break;
362
363 case "line":
364 let i = index(mock, "\n");
365 i = (i > -1) ? i + 1 : mock.length;
366 rv = substr(mock, 0, i);
367 mock = substr(mock, i);
368 break;
369
370 default:
371 let n = +amount;
372 n = (n > 0) ? n : 0;
373 rv = substr(mock, 0, n);
374 mock = substr(mock, n);
375 break;
376 }
377
378 return rv;
379 },
380
381 write: function() {},
382 close: function() {},
383
384 error: function() {
385 return null;
386 }
387 };
388 },
389
390 open: (fpath, mode) => {
391 let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1),
392 path = sprintf("./tests/mocks/fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')),
393 fd = read ? _fs.open(path, "r") : null,
394 mock = null;
395
396 if (fd) {
397 mock = fd.read("all");
398 fd.close();
399 }
400
401 if (read && !mock) {
402 I("No stdout fixture defined for fs.open() path %s.", fpath);
403 I("Provide a mock output through the following text file:\n%s\n", path);
404
405 return null;
406 }
407
408 trace_call("fs", "open", { path: fpath, mode });
409
410 return {
411 read: function(amount) {
412 let rv;
413
414 switch (amount) {
415 case "all":
416 rv = mock;
417 mock = "";
418 break;
419
420 case "line":
421 let i = index(mock, "\n");
422 i = (i > -1) ? i + 1 : mock.length;
423 rv = substr(mock, 0, i);
424 mock = substr(mock, i);
425 break;
426
427 default:
428 let n = +amount;
429 n = (n > 0) ? n : 0;
430 rv = substr(mock, 0, n);
431 mock = substr(mock, n);
432 break;
433 }
434
435 return rv;
436 },
437
438 write: function() {},
439 close: function() {},
440
441 error: function() {
442 return null;
443 }
444 };
445 },
446
447 error: () => "Unspecified error"
448 },
449
450
451 /* Mock stdlib functions */
452
453 system: function(argv, timeout) {
454 trace_call(null, "system", { command: argv, timeout });
455
456 return 0;
457 },
458
459 time: function() {
460 printf("time()\n");
461
462 return 1615382640;
463 },
464
465 print: function(...args) {
466 if (length(args) == 1 && type(args[0]) in ["array", "object"])
467 printf("%s\n", format_json(args[0]));
468 else
469 global.print(...args);
470 }
471 };
472
473
474 /* Execute test file */
475
476 if (!TESTFILE)
477 E("The TESTFILE variable is not defined.");
478
479 include(TESTFILE, mocks);