@gregoriusrippenstein/erlang-red-unittest 0.12.3 → 0.15.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/README.md +5 -1
- package/nodes/locales/en-US/ut-assert-success.html +8 -2
- package/nodes/locales/en-US/ut-assert-success.json +7 -1
- package/nodes/ut-assert-debug.js +403 -19
- package/nodes/ut-assert-failure.js +29 -18
- package/nodes/ut-assert-status.js +28 -18
- package/nodes/ut-assert-success.html +33 -12
- package/nodes/ut-assert-success.js +59 -28
- package/nodes/ut-assert-values.js +126 -16
- package/package.json +1 -1
- package/plugins/sidebar.html +131 -28
- package/testflows/01b074cfa6ae8818/flows.json +1 -0
- package/testflows/02b18fc0ed9de178/flows.json +1 -0
- package/testflows/0616f795a4aee63e/flows.json +1 -0
- package/testflows/069552b2eb6bce48/flows.json +265 -0
- package/testflows/08183069b5f8ef9c/flows.json +156 -0
- package/testflows/084fb0fc954b7156/flows.json +132 -0
- package/testflows/090eb2d5d71fd45f/flows.json +158 -0
- package/testflows/09aab59a2455cf15/flows.json +1 -0
- package/testflows/0b299a3175fc0df1/flows.json +1 -0
- package/testflows/0c35d269ca7178c6/flows.json +443 -0
- package/testflows/0cb8971e5f88ed1e/flows.json +1 -0
- package/testflows/0cc95e44532c14ec/flows.json +94 -0
- package/testflows/0e54563ba63b1501/flows.json +1 -0
- package/testflows/12572f9ac11e1786/flows.json +1 -0
- package/testflows/1566e453a88578a9/flows.json +1 -0
- package/testflows/15c2fad089960984/flows.json +1 -0
- package/testflows/1658196c31549916/flows.json +1 -0
- package/testflows/16b0bc0cdb0c5807/flows.json +1 -0
- package/testflows/16b254125478329a/flows.json +114 -0
- package/testflows/182731f54d855071/flows.json +52 -0
- package/testflows/1a196bce3e57f5c1/flows.json +1 -0
- package/testflows/1ace9e051618db79/flows.json +125 -0
- package/testflows/1bac0a78e0a36ec0/flows.json +1 -0
- package/testflows/1c1d2cb981cf9f01/flows.json +120 -0
- package/testflows/1d3326a724ecbea0/flows.json +1 -0
- package/testflows/1db9c9a1b92e0263/flows.json +172 -0
- package/testflows/1eb35963b752eca9/flows.json +1 -0
- package/testflows/2037454a5ee0d6c3/flows.json +1 -0
- package/testflows/20eeaf41372f7b17/flows.json +62 -0
- package/testflows/213803d61ecfde3a/flows.json +1 -0
- package/testflows/218350f2e240a15d/flows.json +1 -0
- package/testflows/24f35eaa8656755f/flows.json +1 -0
- package/testflows/27804627fb8f56bd/flows.json +178 -0
- package/testflows/2a12af161b544aa1/flows.json +1 -0
- package/testflows/2a2c508260b96236/flows.json +141 -0
- package/testflows/2a95a7b40a798878/flows.json +1 -0
- package/testflows/2c1c291ec8466bfb/flows.json +1 -0
- package/testflows/2c59ef974b2523ba/flows.json +1 -0
- package/testflows/30465efe017cc9b0/flows.json +1 -0
- package/testflows/30c79fc42a5c35eb/flows.json +1 -0
- package/testflows/338c5b411f4edfe8/flows.json +1 -0
- package/testflows/34d211e37618313c/flows.json +1 -0
- package/testflows/36846ad6698e1bc0/flows.json +163 -0
- package/testflows/37883612d913fd66/flows.json +1 -0
- package/testflows/38616d22e1569aa5/flows.json +1 -0
- package/testflows/3be6bc074c7647bd/flows.json +319 -0
- package/testflows/3e9e526b992d4c48/flows.json +1 -0
- package/testflows/3ea113b4ec3b6e5e/flows.json +1 -0
- package/testflows/3ed472eab9503b4f/flows.json +1 -0
- package/testflows/3edda6bd788f88c2/flows.json +1 -0
- package/testflows/435d3b48dd0f462c/flows.json +1 -0
- package/testflows/44f12f6e4a455084/flows.json +272 -0
- package/testflows/45209c67f865ce73/flows.json +1 -0
- package/testflows/459322e0f8e0b785/flows.json +1 -0
- package/testflows/4657dbc0aa4a97bb/flows.json +268 -0
- package/testflows/47514a335b4cd67e/flows.json +362 -0
- package/testflows/486c1412721bb241/flows.json +525 -0
- package/testflows/48d9fdcde317974e/flows.json +1 -0
- package/testflows/4de34883c5d93dc3/flows.json +1 -0
- package/testflows/50d5679818477b66/flows.json +1 -0
- package/testflows/51870a76d1aaf67a/flows.json +1 -0
- package/testflows/51bfa9456b1745c0/flows.json +639 -0
- package/testflows/538be5947c639b32/flows.json +75 -0
- package/testflows/538d2f4704cd2eca/flows.json +1 -0
- package/testflows/5433b57035d597f9/flows.json +1 -0
- package/testflows/545871d31c3f5f3f/flows.json +376 -0
- package/testflows/55f105cfd894b06c/flows.json +121 -0
- package/testflows/55f7fbe90e5befa8/flows.json +1 -0
- package/testflows/562c518969666f24/flows.json +1 -0
- package/testflows/57d44c342a1f00e2/flows.json +1 -0
- package/testflows/597ee2a683d9908f/flows.json +129 -0
- package/testflows/59aa8a866d8d7a70/flows.json +1 -0
- package/testflows/59da63b5adb460db/flows.json +188 -0
- package/testflows/5a06352874fa379d/flows.json +1 -0
- package/testflows/5cd133958df17529/flows.json +1 -0
- package/testflows/5cf6aec7d688fce4/flows.json +1 -0
- package/testflows/5d4908af3d4f95e5/flows.json +1 -0
- package/testflows/5e07acf2da13b504/flows.json +1 -0
- package/testflows/5f6929bb3374b782/flows.json +1 -0
- package/testflows/61142eb3e4e54eab/flows.json +536 -0
- package/testflows/61847d719e50f83f/flows.json +1 -0
- package/testflows/63ca9baf860d1d8f/flows.json +1 -0
- package/testflows/641ddaab2819c61d/flows.json +1 -0
- package/testflows/64445798b59d2630/flows.json +257 -0
- package/testflows/684b9066e98b9722/flows.json +1 -0
- package/testflows/698b83b8aeb5d5b6/flows.json +1 -0
- package/testflows/6a9ffd45c418497b/flows.json +1 -0
- package/testflows/6d46e3fe1f2245b8/flows.json +86 -0
- package/testflows/6dd3a5754f83dc2e/flows.json +232 -0
- package/testflows/6ea4c6b373eeaa8d/flows.json +1 -0
- package/testflows/6f0dcbf18234c5e2/flows.json +1 -0
- package/testflows/6ff45e2a0ce77393/flows.json +149 -0
- package/testflows/700c94899fdd3334/flows.json +107 -0
- package/testflows/71f65246c742cfc9/flows.json +1 -0
- package/testflows/7581c2be261f9c17/flows.json +1874 -0
- package/testflows/77854a5ea962257b/flows.json +1 -0
- package/testflows/7b77600dcf78933f/flows.json +1 -0
- package/testflows/7dae2b1a7881c607/flows.json +1 -0
- package/testflows/7e1d04570c6bdff9/flows.json +1 -0
- package/testflows/7ecfd18f58fc6510/flows.json +1 -0
- package/testflows/7fea9696f6186962/flows.json +141 -0
- package/testflows/80655e5d4035fa71/flows.json +215 -0
- package/testflows/8180ae5891c673de/flows.json +1 -0
- package/testflows/8421f37b6c40e1ec/flows.json +1 -0
- package/testflows/84a5a362cafe703f/flows.json +1 -0
- package/testflows/866410b56fa42447/flows.json +1076 -0
- package/testflows/894f6e38bfd1aca6/flows.json +1 -0
- package/testflows/89aeaeef64509bed/flows.json +101 -0
- package/testflows/8a627c9bfe3b4aff/flows.json +552 -0
- package/testflows/8bc7412d6a4869c6/flows.json +1 -0
- package/testflows/92054e0dd22f14c6/flows.json +1 -0
- package/testflows/93c6e969dcf84e70/flows.json +1 -0
- package/testflows/9463eba0ab843567/flows.json +1 -0
- package/testflows/96f6f83dcab507e5/flows.json +105 -0
- package/testflows/9746601768c3b3b6/flows.json +93 -0
- package/testflows/987cd33b9cda8529/flows.json +1 -0
- package/testflows/988f27b9abdde6e9/flows.json +1 -0
- package/testflows/9942fa1aeb1b2428/flows.json +2346 -0
- package/testflows/9a1bdfdcd7b2be8e/flows.json +1 -0
- package/testflows/9b1023c95a2613cc/flows.json +141 -0
- package/testflows/9c51a179141515b2/flows.json +1 -0
- package/testflows/9d11bfc3a6d88535/flows.json +301 -0
- package/testflows/9e7ccc97f80d9afb/flows.json +93 -0
- package/testflows/9f0d6c3ad7798a77/flows.json +142 -0
- package/testflows/9f7351d7a7304584/flows.json +83 -0
- package/testflows/9ff2e8ab21d922c5/flows.json +1 -0
- package/testflows/a123cd431f1967cd/flows.json +1757 -0
- package/testflows/a4c9cabf4b335488/flows.json +86 -0
- package/testflows/a6dc7002d0a8640e/flows.json +1 -0
- package/testflows/a862ae412a70ded1/flows.json +1 -0
- package/testflows/a916165378c446e3/flows.json +240 -0
- package/testflows/ad2bf1089481dcfb/flows.json +1 -0
- package/testflows/ae38b2dbd23d1681/flows.json +3089 -0
- package/testflows/ae8a9b0bbbaff447/flows.json +271 -0
- package/testflows/b07b511dc8bb8339/flows.json +92 -0
- package/testflows/b1430ea37ba7cc19/flows.json +1 -0
- package/testflows/b1463d7bbf545725/flows.json +1 -0
- package/testflows/b1501de32c769cf2/flows.json +94 -0
- package/testflows/b2a67e301fabab0e/flows.json +1 -0
- package/testflows/b2fcc5806b2715c0/flows.json +1 -0
- package/testflows/b3f365d0b7d1f97d/flows.json +1 -0
- package/testflows/b50f775ae3b74d38/flows.json +1 -0
- package/testflows/b5fbcdbffb568569/flows.json +1 -0
- package/testflows/b6d4e4592b27a344/flows.json +1 -0
- package/testflows/b723353a316fa50e/flows.json +1 -0
- package/testflows/b7ebaf91f4d66ab3/flows.json +1 -0
- package/testflows/b85795a24882502b/flows.json +1 -0
- package/testflows/b864b3e3510cd120/flows.json +205 -0
- package/testflows/b98d0b05a760ad79/flows.json +1 -0
- package/testflows/b9cd19107a5145c4/flows.json +1 -0
- package/testflows/bbb1fc2d47c3cd5f/flows.json +1 -0
- package/testflows/bdbe94d065f3b3a6/flows.json +143 -0
- package/testflows/bfced61e72c69715/flows.json +616 -0
- package/testflows/c0b1cf6656eac77f/flows.json +518 -0
- package/testflows/c1370220fd37968e/flows.json +1 -0
- package/testflows/c163c627bd2a37c7/flows.json +1 -0
- package/testflows/c274a07715a87ca4/flows.json +1 -0
- package/testflows/c28048d859db3773/flows.json +1 -0
- package/testflows/c381b5135cc947cd/flows.json +272 -0
- package/testflows/c4690c0a085d6ef5/flows.json +145 -0
- package/testflows/c5f513bc380bc30a/flows.json +696 -0
- package/testflows/c6ee6e89a51c98fc/flows.json +1 -0
- package/testflows/c843dacf753a19d8/flows.json +1 -0
- package/testflows/caccb020fce3f485/flows.json +1 -0
- package/testflows/cd44a688eb2ab3cf/flows.json +1 -0
- package/testflows/ce2f98273da05245/flows.json +1 -0
- package/testflows/ced70c769db825eb/flows.json +1 -0
- package/testflows/cef9dd7dc179b8b3/flows.json +1 -0
- package/testflows/cfa8595b2e2b8269/flows.json +255 -0
- package/testflows/cffa610c817beb43/flows.json +1 -0
- package/testflows/d17062b01c4a9435/flows.json +1 -0
- package/testflows/d17fb2553fe8013a/flows.json +157 -0
- package/testflows/d206746f9f2594a6/flows.json +1 -0
- package/testflows/d4ce58359ff6ff96/flows.json +1 -0
- package/testflows/d5424657b497d5af/flows.json +1 -0
- package/testflows/d595c53abba88f72/flows.json +1 -0
- package/testflows/d61182e87a604efa/flows.json +340 -0
- package/testflows/def45a46c8f4da72/flows.json +1 -0
- package/testflows/def56742e02f6a75/flows.json +1 -0
- package/testflows/e1ced3b16782f7c8/flows.json +1 -0
- package/testflows/e2a801d2b3e2143f/flows.json +1 -0
- package/testflows/e7184b7ca4f07907/flows.json +1 -0
- package/testflows/ea246f68766c8630/flows.json +1 -0
- package/testflows/eb447048178f6e16/flows.json +201 -0
- package/testflows/ec921769b3c24fb6/flows.json +1 -0
- package/testflows/ed621e03921c13de/flows.json +1 -0
- package/testflows/eed68dbcecd1431f/flows.json +204 -0
- package/testflows/ef61f644d5436dbe/flows.json +1 -0
- package/testflows/f19fdae0c02b4f03/flows.json +1008 -0
- package/testflows/f1e71f2ccb34fd6e/flows.json +1 -0
- package/testflows/f2f61ec9fc46a468/flows.json +575 -0
- package/testflows/f346d45c81f595e5/flows.json +501 -0
- package/testflows/f38603a59963386c/flows.json +1 -0
- package/testflows/f4c4a57e77439a1b/flows.json +1 -0
- package/testflows/f5bdafa844f6d280/flows.json +1 -0
- package/testflows/f5f82ca50317fda7/flows.json +121 -0
- package/testflows/f8727464c799eb22/flows.json +1 -0
- package/testflows/f946aafaebb1398d/flows.json +160 -0
- package/testflows/f94db507552f4934/flows.json +183 -0
- package/testflows/fb50bac16667fc54/flows.json +364 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## Unit testing nodes for Erlang-RED
|
|
1
|
+
## Unit testing nodes for Erlang-Red and Node-RED
|
|
2
2
|
|
|
3
3
|
Node-RED Nodes for testing flow correctness in the [Erlang-Red](https://github.com/gorenje/erlang-red) project.
|
|
4
4
|
|
|
@@ -38,6 +38,10 @@ This package also defines the following actions that can be mapped to keyboard s
|
|
|
38
38
|
- Send Halt To Test Server: (Erlang-Red only) restarts the server
|
|
39
39
|
- Run All Tests: (Erlang-Red only) run all defined unit tests.
|
|
40
40
|
|
|
41
|
+
## Forum Discussions
|
|
42
|
+
|
|
43
|
+
- [Flow testsuite for testing core node functionality - May 2025](https://discourse.nodered.org/t/flow-testsuite-for-testing-core-node-functionality/97106)
|
|
44
|
+
|
|
41
45
|
## Tee Time
|
|
42
46
|
|
|
43
47
|
<a href="https://www.buymeacoffee.com/gorenje" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Tee" style="height: 42px !important;width: 152px !important;" ></a>
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<script type="text/html" data-help-name="ut-assert-success">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<p>This node failes if, during flow execution, it is not reached.</p>
|
|
3
|
+
A success assertion meaning that this node <b>must</b> be reached when a flow is executed as part of a test.
|
|
4
4
|
|
|
5
|
+
<p>
|
|
5
6
|
If this node isn't reached, then the test will fail.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
A message count of zero is assumed to be <b>at least one message</b>, it's the same as minimum of one message.
|
|
11
|
+
</p>
|
|
6
12
|
</script>
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
"succeed": "assert succeed",
|
|
8
8
|
"count": "Msg Count",
|
|
9
9
|
"unsupported": "Unsupported",
|
|
10
|
-
"failed": "assert failed"
|
|
10
|
+
"failed": "assert failed",
|
|
11
|
+
"messages": "Msgs",
|
|
12
|
+
"limit": {
|
|
13
|
+
"upperlimit" : "Max",
|
|
14
|
+
"exactly": "Exactly",
|
|
15
|
+
"lowerlimit": "Min"
|
|
16
|
+
}
|
|
11
17
|
},
|
|
12
18
|
|
|
13
19
|
"status": {
|
package/nodes/ut-assert-debug.js
CHANGED
|
@@ -5,34 +5,418 @@ module.exports = function(RED) {
|
|
|
5
5
|
var node = this;
|
|
6
6
|
var cfg = config;
|
|
7
7
|
|
|
8
|
-
node.on('close', function() {
|
|
9
|
-
|
|
8
|
+
node.on('close', function (removed, done) {
|
|
9
|
+
if (removed) {
|
|
10
|
+
node.status({});
|
|
11
|
+
} else {
|
|
12
|
+
// use node.log(..) here because node.error(..) sends a message to the debug
|
|
13
|
+
// panel but that errors out because the frontend can't find the workspace:
|
|
14
|
+
// Uncaught TypeError: can't access property "label", RED.nodes.workspace(...) is undefined
|
|
15
|
+
// that has follow-on effects.
|
|
16
|
+
node.log(`UNSUPPORTED [${node.z}] debug is not supported`)
|
|
17
|
+
}
|
|
18
|
+
done()
|
|
10
19
|
});
|
|
11
20
|
|
|
12
21
|
/* msg handler, in this case pass the message on unchanged */
|
|
13
22
|
node.on("input", function(msg, send, done) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
// How to send a status update
|
|
24
|
+
node.status({ fill: "green", shape: "ring", text: RED._("ut-assert-debug.label.statusset") });
|
|
25
|
+
setTimeout(() => { node.status({}); }, 1000)
|
|
26
|
+
|
|
27
|
+
// Send a message and how to handle errors.
|
|
28
|
+
try {
|
|
18
29
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
done();
|
|
26
|
-
}
|
|
27
|
-
} catch (err) {
|
|
28
|
-
// use done if the node won't send anymore messages for the
|
|
29
|
-
// message it received.
|
|
30
|
-
msg.error = err
|
|
31
|
-
done(err.message, msg)
|
|
30
|
+
send(msg);
|
|
31
|
+
done();
|
|
32
|
+
} catch ( err ) {
|
|
33
|
+
// use node.error if the node might send subsequent messages
|
|
34
|
+
node.error("error occurred", { ...msg, error: err })
|
|
35
|
+
done();
|
|
32
36
|
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// use done if the node won't send anymore messages for the
|
|
39
|
+
// message it received.
|
|
40
|
+
msg.error = err
|
|
41
|
+
done(err.message, msg)
|
|
42
|
+
}
|
|
33
43
|
});
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
RED.nodes.registerType("ut-assert-debug", Coreut_assert_debugFunctionality);
|
|
37
47
|
|
|
48
|
+
// Backend endpoints
|
|
49
|
+
RED.httpAdmin.get("/UnitTesting/:flowid/runtest",
|
|
50
|
+
(req, res) => {
|
|
51
|
+
const path = require('path');
|
|
52
|
+
const fs = require('fs')
|
|
53
|
+
const jsonClone = require("rfdc")();
|
|
54
|
+
|
|
55
|
+
// API defined here --> https://github.com/node-red/node-red/blob/master/packages/node_modules/%40node-red/runtime/lib/index.js
|
|
56
|
+
const runtime = require("@node-red/runtime");
|
|
57
|
+
|
|
58
|
+
let testDir = path.resolve(path.dirname(__filename), "..", "testflows")
|
|
59
|
+
|
|
60
|
+
// if a test generates an uncaught exception, then captcha that and generate
|
|
61
|
+
// a failure - tests are considered failed if they generate uncaught exceptions/errors.
|
|
62
|
+
let createExtraCatchNode = () => {
|
|
63
|
+
let changeId = runtime.util.generateId()
|
|
64
|
+
let assertId = runtime.util.generateId()
|
|
65
|
+
let debugId = runtime.util.generateId()
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
"id": runtime.util.generateId(),
|
|
70
|
+
"type": "catch",
|
|
71
|
+
"name": "",
|
|
72
|
+
"scope": null,
|
|
73
|
+
"uncaught": true,
|
|
74
|
+
"wires": [
|
|
75
|
+
[
|
|
76
|
+
changeId
|
|
77
|
+
]
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": changeId,
|
|
82
|
+
"type": "change",
|
|
83
|
+
"name": "",
|
|
84
|
+
"rules": [
|
|
85
|
+
{
|
|
86
|
+
"t": "set",
|
|
87
|
+
"p": "_unittest_triggered",
|
|
88
|
+
"pt": "msg",
|
|
89
|
+
"to": "true",
|
|
90
|
+
"tot": "bool"
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"action": "",
|
|
94
|
+
"property": "",
|
|
95
|
+
"from": "",
|
|
96
|
+
"to": "",
|
|
97
|
+
"reg": false,
|
|
98
|
+
"wires": [
|
|
99
|
+
[
|
|
100
|
+
assertId,
|
|
101
|
+
debugId
|
|
102
|
+
]
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": debugId,
|
|
107
|
+
"type": "debug",
|
|
108
|
+
"name": "",
|
|
109
|
+
"active": true,
|
|
110
|
+
"tosidebar": false,
|
|
111
|
+
"console": true,
|
|
112
|
+
"tostatus": false,
|
|
113
|
+
"complete": "payload",
|
|
114
|
+
"targetType": "msg",
|
|
115
|
+
"statusVal": "",
|
|
116
|
+
"statusType": "auto",
|
|
117
|
+
"wires": []
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": assertId,
|
|
121
|
+
"type": "ut-assert-failure",
|
|
122
|
+
"name": "",
|
|
123
|
+
"wires": []
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let isTestPending = (tabDetails) => {
|
|
129
|
+
return tabDetails.env.filter(d => d.name == "NRED_PENDING")[0]?.value == "true"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// chunkify an array into chunks of `size` size
|
|
133
|
+
let chunkify = (array, size) => {
|
|
134
|
+
const chunkedArray = [];
|
|
135
|
+
for (let i = 0; i < array.length; i += size) {
|
|
136
|
+
chunkedArray.push(array.slice(i, i + size));
|
|
137
|
+
}
|
|
138
|
+
return chunkedArray;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// respond to request with a list of total tests to be done. The frontend then
|
|
142
|
+
// shows a progress indication of success / pending / failure
|
|
143
|
+
let respondWithCount = (res, count) => {
|
|
144
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
145
|
+
res.end(JSON.stringify({ status: "ok", todo: count }));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if (req.params.flowid == "all") {
|
|
150
|
+
//
|
|
151
|
+
// Run all tests. This is a little more complicated as this runs all tests in batches
|
|
152
|
+
// of ten tests at a time but these have to be added in serial and also removed in
|
|
153
|
+
// serial, so there has to be promise handling ... and that makes this stuff a little
|
|
154
|
+
// more complicated.
|
|
155
|
+
//
|
|
156
|
+
|
|
157
|
+
let allTests = fs.globSync(`${testDir}/**/*.json`)
|
|
158
|
+
|
|
159
|
+
// if a limit (i.e. a list of testFlow ids) is provided, only peform those tests.
|
|
160
|
+
if (req.query.limit) {
|
|
161
|
+
let testIds = req.query.limit.split(",")
|
|
162
|
+
|
|
163
|
+
allTests = allTests.filter( filename => {
|
|
164
|
+
let origFlowId = path.basename(path.parse(filename).dir)
|
|
165
|
+
return testIds.indexOf(origFlowId) > -1
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let chunkedFilenames = chunkify(allTests, 10)
|
|
170
|
+
|
|
171
|
+
respondWithCount(res, allTests.length)
|
|
172
|
+
|
|
173
|
+
let runTestsForFileNames = (fileIdx) => {
|
|
174
|
+
if (fileIdx >= chunkedFilenames.length) { return }
|
|
175
|
+
|
|
176
|
+
// test all is a multi step process, first we have to add all flows to the main show
|
|
177
|
+
// then we have to trigger them and finally we have to remove those that weren't
|
|
178
|
+
// there in the first place.
|
|
179
|
+
let flowIdsToBeRemoved = []
|
|
180
|
+
let injNodesToBeTriggered = {}
|
|
181
|
+
|
|
182
|
+
let timeoutValues = []
|
|
183
|
+
|
|
184
|
+
let flowsToAdd = chunkedFilenames[fileIdx].map((filename) => {
|
|
185
|
+
|
|
186
|
+
let origFlowId = path.basename(path.parse(filename).dir)
|
|
187
|
+
|
|
188
|
+
let flowDetails = JSON.parse(fs.readFileSync(filename))
|
|
189
|
+
let tabDetails = flowDetails.filter(d => d.type == "tab")[0]
|
|
190
|
+
let injNodesIds = flowDetails.filter(d => d.type == "inject").map(d => d.id)
|
|
191
|
+
|
|
192
|
+
RED.log.debug(`unittest: adding test case [${origFlowId}] - '${tabDetails.label}'`)
|
|
193
|
+
|
|
194
|
+
if (isTestPending(tabDetails) && req.query["testpend"] != "true") {
|
|
195
|
+
RED.comms.publish("unittesting:testresults", {
|
|
196
|
+
flowid: origFlowId,
|
|
197
|
+
status: "pending"
|
|
198
|
+
})
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// compute any timeout that is defined for the flow test
|
|
203
|
+
tabDetails.env.filter(d => d.name == "NRED_TIMEOUT").forEach( d => {
|
|
204
|
+
timeoutValues.push(parseInt(d.value) * 1000)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// add an catch all and trigger a assert false if any exceptions are
|
|
208
|
+
// raised by the test - if there isn't already a catch all node.
|
|
209
|
+
if (flowDetails.filter(d => d.type == "catch").filter(d => d.scope == null).length == 0) {
|
|
210
|
+
flowDetails = flowDetails.concat(createExtraCatchNode())
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (runtime._.flows.getFlow(origFlowId)) {
|
|
214
|
+
injNodesToBeTriggered[origFlowId] = injNodesIds.map(d => d)
|
|
215
|
+
return null
|
|
216
|
+
} else {
|
|
217
|
+
return {
|
|
218
|
+
injNodesIds: injNodesIds, origFlowId: origFlowId, nodes: flowDetails
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}).filter(d => !!d)
|
|
222
|
+
|
|
223
|
+
// addFlow is a promise but I want to add flows in series not parallel, so the
|
|
224
|
+
// promise returns a promise that adds the next flow after the initial flow
|
|
225
|
+
// has been added. It's promises all the way down - until the turtles start.
|
|
226
|
+
let addFlow = (idx) => {
|
|
227
|
+
if (idx >= flowsToAdd.length) { return }
|
|
228
|
+
var { injNodesIds, origFlowId, nodes } = flowsToAdd[idx]
|
|
229
|
+
|
|
230
|
+
var newConfig = jsonClone(runtime._.flows.getFlows().flows);
|
|
231
|
+
newConfig = newConfig.concat(nodes);
|
|
232
|
+
|
|
233
|
+
return runtime._.flows.setFlows(newConfig, null, 'flows', false, null, "root")
|
|
234
|
+
.then(() => {
|
|
235
|
+
let newFlowId = origFlowId
|
|
236
|
+
injNodesToBeTriggered[newFlowId] = injNodesIds.map(d => d)
|
|
237
|
+
flowIdsToBeRemoved.push(newFlowId)
|
|
238
|
+
})
|
|
239
|
+
.catch(e => { RED.log.error(e); RED.log.error(`Exception happened with ${origFlowId}`) })
|
|
240
|
+
.finally(() => addFlow(idx + 1))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
Promise.all([addFlow(0)]).then(() => {
|
|
244
|
+
RED.log.debug("unittest: done adding all flows")
|
|
245
|
+
|
|
246
|
+
runtime._.flows.startFlows("full", null, false, true).then(d => {
|
|
247
|
+
Object.keys(injNodesToBeTriggered).forEach(flowId => {
|
|
248
|
+
injNodesToBeTriggered[flowId].forEach(ndeId => {
|
|
249
|
+
RED.log.debug(`unittest: triggering inject node: ${ndeId}`)
|
|
250
|
+
RED.nodes.getNode(ndeId)?.receive({"_unittest_triggered": true})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
let removeFlow = (idx) => {
|
|
255
|
+
if (idx >= flowIdsToBeRemoved.length) { return }
|
|
256
|
+
let flowid = flowIdsToBeRemoved[idx]
|
|
257
|
+
|
|
258
|
+
return runtime._.flows.removeFlow(flowid, "root")
|
|
259
|
+
.then((_ignore) => {
|
|
260
|
+
RED.comms.publish("unittesting:testresults", {
|
|
261
|
+
flowid: flowid,
|
|
262
|
+
status: "stopped"
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
.catch(e => {
|
|
266
|
+
RED.log.error(e);
|
|
267
|
+
RED.log.error(`Error happened removing flow ${flowid}`)
|
|
268
|
+
RED.comms.publish("unittesting:testresults", {
|
|
269
|
+
flowid: flowid,
|
|
270
|
+
status: "stopped"
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
.finally(() => removeFlow(idx + 1))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
Promise.all([removeFlow(0)]).then(() => {
|
|
278
|
+
RED.log.debug("unittest: removed all flows")
|
|
279
|
+
runTestsForFileNames(fileIdx+1)
|
|
280
|
+
})
|
|
281
|
+
}, Math.max(...(timeoutValues.concat([5000]))))
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
runTestsForFileNames(0)
|
|
286
|
+
} else {
|
|
287
|
+
|
|
288
|
+
//
|
|
289
|
+
// run a single unit test but ensure that the test case does exist on disk
|
|
290
|
+
//
|
|
291
|
+
|
|
292
|
+
respondWithCount(res, 1)
|
|
293
|
+
|
|
294
|
+
fs.globSync(`${testDir}/**/*.json`).filter(d => d.includes(req.params.flowid)).forEach(filename => {
|
|
295
|
+
let flowDetails = JSON.parse(fs.readFileSync(filename))
|
|
296
|
+
let origFlowId = req.params.flowid
|
|
297
|
+
|
|
298
|
+
let tabDetails = flowDetails.filter(d => d.type == "tab")[0]
|
|
299
|
+
let injNodesIds = flowDetails.filter(d => d.type == "inject").map(d => d.id)
|
|
300
|
+
|
|
301
|
+
let timeoutValues = []
|
|
302
|
+
|
|
303
|
+
if (isTestPending(tabDetails) && req.query["testpend"] != "true") {
|
|
304
|
+
RED.comms.publish("unittesting:testresults", {
|
|
305
|
+
flowid: origFlowId,
|
|
306
|
+
status: "pending"
|
|
307
|
+
})
|
|
308
|
+
return null
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// compute any timeout that is defined for the flow test
|
|
312
|
+
tabDetails.env.filter(d => d.name == "NRED_TIMEOUT").forEach(d => {
|
|
313
|
+
timeoutValues.push(parseInt(d.value) * 1000)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// add an catch all and trigger a assert false if any exceptions are
|
|
317
|
+
// raised by the test - if there isn't already a catch all node.
|
|
318
|
+
if (flowDetails.filter(d => d.type == "catch").filter(d => d.scope == null).length == 0) {
|
|
319
|
+
flowDetails = flowDetails.concat(createExtraCatchNode())
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// if flow already exists, then just trigger the inject nodes
|
|
323
|
+
if (runtime._.flows.getFlow(origFlowId)) {
|
|
324
|
+
setTimeout(() => {
|
|
325
|
+
injNodesIds.forEach(ndeId => {
|
|
326
|
+
RED.nodes.getNode(ndeId)?.receive({"_unittest_triggered": true})
|
|
327
|
+
})
|
|
328
|
+
}, 500)
|
|
329
|
+
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
// tell the frontend that we've done with the test. If no
|
|
332
|
+
// status has been posted for the unit test, then it succeeds.
|
|
333
|
+
RED.comms.publish("unittesting:testresults", {
|
|
334
|
+
flowid: origFlowId,
|
|
335
|
+
status: "stopped"
|
|
336
|
+
})
|
|
337
|
+
}, Math.max(...(timeoutValues.concat([5000]))))
|
|
338
|
+
} else {
|
|
339
|
+
var newConfig = jsonClone(runtime._.flows.getFlows().flows);
|
|
340
|
+
newConfig = newConfig.concat(flowDetails);
|
|
341
|
+
|
|
342
|
+
runtime._.flows.setFlows(newConfig, null, 'flows', false, null, "root").then(() => {
|
|
343
|
+
runtime._.flows.startFlows("full", null, false, true).then(d => {
|
|
344
|
+
setTimeout(() => {
|
|
345
|
+
injNodesIds.forEach(ndeId => {
|
|
346
|
+
RED.nodes.getNode(ndeId)?.receive({"_unittest_triggered": true})
|
|
347
|
+
})
|
|
348
|
+
}, 500)
|
|
349
|
+
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
runtime._.flows.removeFlow(origFlowId, "root").then(result => {
|
|
352
|
+
// tell the frontend that we've done with the test. If no
|
|
353
|
+
// status has been posted for the unit test, then it succeeds.
|
|
354
|
+
RED.comms.publish("unittesting:testresults", {
|
|
355
|
+
flowid: origFlowId,
|
|
356
|
+
status: "stopped"
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
}, Math.max(...(timeoutValues.concat([5000]))))
|
|
360
|
+
})
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
RED.httpAdmin.get("/UnitTesting/halt",
|
|
368
|
+
(req, res) => {
|
|
369
|
+
var runtime = require("@node-red/runtime");
|
|
370
|
+
runtime._.flows.stopFlows()
|
|
371
|
+
res.sendStatus(200);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
RED.httpAdmin.get("/UnitTesting/:flowid/retrieve",
|
|
375
|
+
(req, res) => {
|
|
376
|
+
const path = require('path');
|
|
377
|
+
const fs = require('fs')
|
|
378
|
+
|
|
379
|
+
let fileName = path.resolve(path.dirname(__filename), "..", "testflows", req.params.flowid, "flows.json")
|
|
380
|
+
const data = fs.readFileSync(fileName);
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
384
|
+
res.end(JSON.stringify({ flowdata: JSON.parse(data) }));
|
|
385
|
+
} catch (err) {
|
|
386
|
+
res.sendStatus(500);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
RED.httpAdmin.get("/UnitTesting/tests.json",
|
|
391
|
+
(req, res) => {
|
|
392
|
+
let testData = {
|
|
393
|
+
"status": "ok",
|
|
394
|
+
"last_updated_at": "2025-04-09T14:54:01.665Z",
|
|
395
|
+
"data": {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const path = require('path');
|
|
400
|
+
const fs = require('fs')
|
|
401
|
+
|
|
402
|
+
let testDir = path.resolve(path.dirname(__filename), "..", "testflows")
|
|
403
|
+
|
|
404
|
+
fs.globSync(`${testDir}/**/*.json`).forEach(filename => {
|
|
405
|
+
let details = path.parse(filename)
|
|
406
|
+
const data = fs.readFileSync(filename);
|
|
407
|
+
|
|
408
|
+
testData.data[path.basename(details.dir)] = {
|
|
409
|
+
"id": path.basename(details.dir),
|
|
410
|
+
"name": JSON.parse(data).filter(d => d.type == "tab")[0].label
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
416
|
+
res.end(JSON.stringify(testData));
|
|
417
|
+
} catch (err) {
|
|
418
|
+
res.sendStatus(500);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
38
422
|
}
|
|
@@ -1,35 +1,46 @@
|
|
|
1
|
-
module.exports = function(RED) {
|
|
1
|
+
module.exports = function (RED) {
|
|
2
2
|
function CoreutassertfailureFunctionality(config) {
|
|
3
|
-
RED.nodes.createNode(this,config);
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
4
|
|
|
5
5
|
var node = this;
|
|
6
6
|
var cfg = config;
|
|
7
7
|
|
|
8
|
-
node.on('close', function() {
|
|
8
|
+
node.on('close', function () {
|
|
9
9
|
node.status({});
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
/* msg handler, in this case pass the message on unchanged */
|
|
13
|
-
node.on("input", function(msg, send, done) {
|
|
13
|
+
node.on("input", function (msg, send, done) {
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
RED.comms.publish("unittesting:testresults", {
|
|
16
|
+
flowid: node.z,
|
|
17
|
+
status: "failed"
|
|
18
|
+
})
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
// use node.log(..) here because node.error(..) sends a message to the debug
|
|
21
|
+
// panel but that errors out because the frontend can't find the workspace:
|
|
22
|
+
// Uncaught TypeError: can't access property "label", RED.nodes.workspace(...) is undefined
|
|
23
|
+
// that has follow-on effects.
|
|
24
|
+
// see https://nodered.org/docs/creating-nodes/node-js#logging-events for more details
|
|
25
|
+
node.log(`FAILED [${node.z}] assert failure was sent a message`, msg)
|
|
26
|
+
node.status({ fill: "red", shape: "dot", text: RED._("ut-assert-failure.label.failed") });
|
|
27
|
+
|
|
28
|
+
// Send a message and how to handle errors.
|
|
29
|
+
try {
|
|
18
30
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
done();
|
|
22
|
-
} catch ( err ) {
|
|
23
|
-
// use node.error if the node might send subsequent messages
|
|
24
|
-
node.error("error occurred", { ...msg, error: err })
|
|
25
|
-
done();
|
|
26
|
-
}
|
|
31
|
+
send(msg);
|
|
32
|
+
done();
|
|
27
33
|
} catch (err) {
|
|
28
|
-
// use
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
done(err.message, msg)
|
|
34
|
+
// use node.error if the node might send subsequent messages
|
|
35
|
+
node.error("error occurred", { ...msg, error: err })
|
|
36
|
+
done();
|
|
32
37
|
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// use done if the node won't send anymore messages for the
|
|
40
|
+
// message it received.
|
|
41
|
+
msg.error = err
|
|
42
|
+
done(err.message, msg)
|
|
43
|
+
}
|
|
33
44
|
});
|
|
34
45
|
}
|
|
35
46
|
|
|
@@ -5,31 +5,41 @@ module.exports = function(RED) {
|
|
|
5
5
|
var node = this;
|
|
6
6
|
var cfg = config;
|
|
7
7
|
|
|
8
|
-
node.on('close', function() {
|
|
9
|
-
|
|
8
|
+
node.on('close', function(removed, done) {
|
|
9
|
+
if ( removed) {
|
|
10
|
+
node.status({});
|
|
11
|
+
} else {
|
|
12
|
+
// use node.log(..) here because node.error(..) sends a message to the debug
|
|
13
|
+
// panel but that errors out because the frontend can't find the workspace:
|
|
14
|
+
// Uncaught TypeError: can't access property "label", RED.nodes.workspace(...) is undefined
|
|
15
|
+
// that has follow-on effects.
|
|
16
|
+
node.log(`UNSUPPORTED [${node.z}] status is not supported`)
|
|
17
|
+
}
|
|
18
|
+
done()
|
|
10
19
|
});
|
|
11
20
|
|
|
12
21
|
/* msg handler, in this case pass the message on unchanged */
|
|
13
22
|
node.on("input", function(msg, send, done) {
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
// How to send a status update
|
|
24
|
+
node.status({ fill: "green", shape: "ring", text: RED._("ut-assert-status.label.statusset") });
|
|
25
|
+
setTimeout(() => { node.status({}); }, 1000)
|
|
16
26
|
|
|
17
|
-
|
|
27
|
+
// Send a message and how to handle errors.
|
|
28
|
+
try {
|
|
18
29
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
done();
|
|
26
|
-
}
|
|
27
|
-
} catch (err) {
|
|
28
|
-
// use done if the node won't send anymore messages for the
|
|
29
|
-
// message it received.
|
|
30
|
-
msg.error = err
|
|
31
|
-
done(err.message, msg)
|
|
30
|
+
send(msg);
|
|
31
|
+
done();
|
|
32
|
+
} catch ( err ) {
|
|
33
|
+
// use node.error if the node might send subsequent messages
|
|
34
|
+
node.error("error occurred", { ...msg, error: err })
|
|
35
|
+
done();
|
|
32
36
|
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// use done if the node won't send anymore messages for the
|
|
39
|
+
// message it received.
|
|
40
|
+
msg.error = err
|
|
41
|
+
done(err.message, msg)
|
|
42
|
+
}
|
|
33
43
|
});
|
|
34
44
|
}
|
|
35
45
|
|