@blucca/n8n-check 0.0.0-stage → 0.1.7

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.
Files changed (78) hide show
  1. package/Dockerfile +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +323 -2
  4. package/bin/n8n-check.mjs +62 -0
  5. package/examples/agregado-enrichment/README.md +68 -0
  6. package/examples/agregado-enrichment/n8n-synthetic +11 -0
  7. package/examples/agregado-enrichment/observed-results.json +806 -0
  8. package/examples/agregado-enrichment/prepare.mjs +39 -0
  9. package/examples/identity-projection/README.md +39 -0
  10. package/examples/identity-projection/case.json +33 -0
  11. package/examples/identity-projection/observed-results.json +117 -0
  12. package/examples/identity-projection/workflow.json +71 -0
  13. package/examples/identity-projection/wrong-id.json +71 -0
  14. package/examples/morsof-invoice-history/README.md +77 -0
  15. package/examples/morsof-invoice-history/UPSTREAM-LICENSE +21 -0
  16. package/examples/morsof-invoice-history/n8n-datatable.cjs +28 -0
  17. package/examples/morsof-invoice-history/observed-results.json +113 -0
  18. package/examples/morsof-invoice-history/prepare.mjs +57 -0
  19. package/examples/morsof-invoice-history/replay.mjs +35 -0
  20. package/examples/morsof-invoice-history/run.sh +19 -0
  21. package/examples/object-body/broken.json +74 -0
  22. package/examples/object-body/case.json +81 -0
  23. package/examples/object-body/fixed.json +74 -0
  24. package/examples/object-body/observed-results.json +449 -0
  25. package/examples/object-body/retry-case.json +112 -0
  26. package/examples/object-body/retry.json +77 -0
  27. package/examples/propfind-body/README.md +75 -0
  28. package/examples/propfind-body/case.json +51 -0
  29. package/examples/propfind-body/no-body-control.json +96 -0
  30. package/examples/propfind-body/observed-results.json +307 -0
  31. package/examples/propfind-body/workflow.json +96 -0
  32. package/examples/raw-response/README.md +71 -0
  33. package/examples/raw-response/file-case.json +54 -0
  34. package/examples/raw-response/json-body.json +58 -0
  35. package/examples/raw-response/json-case.json +52 -0
  36. package/examples/raw-response/observed-results.json +383 -0
  37. package/examples/raw-response/raw-file.json +88 -0
  38. package/examples/raw-response/raw-json.json +58 -0
  39. package/examples/raw-response/raw-text.json +58 -0
  40. package/examples/raw-response/text-case.json +50 -0
  41. package/examples/retry-isolation/README.md +71 -0
  42. package/examples/retry-isolation/direct-case.json +112 -0
  43. package/examples/retry-isolation/direct.json +101 -0
  44. package/examples/retry-isolation/editor-template.json +168 -0
  45. package/examples/retry-isolation/http-batching-case.json +112 -0
  46. package/examples/retry-isolation/http-batching.json +107 -0
  47. package/examples/retry-isolation/loop-case.json +117 -0
  48. package/examples/retry-isolation/loop.json +133 -0
  49. package/examples/retry-isolation/observed-results.json +155 -0
  50. package/examples/silent-filter/README.md +57 -0
  51. package/examples/silent-filter/broken.json +96 -0
  52. package/examples/silent-filter/case.json +120 -0
  53. package/examples/silent-filter/fixed.json +96 -0
  54. package/examples/silent-filter/observed-results.json +273 -0
  55. package/examples/stop-after/README.md +66 -0
  56. package/examples/stop-after/broken.json +134 -0
  57. package/examples/stop-after/case.json +34 -0
  58. package/examples/stop-after/fixed.json +134 -0
  59. package/examples/stop-after/observed-results.json +162 -0
  60. package/examples/vapi-tool-contract/README.md +67 -0
  61. package/examples/vapi-tool-contract/backend-503.case.json +113 -0
  62. package/examples/vapi-tool-contract/empty-orders.case.json +113 -0
  63. package/examples/vapi-tool-contract/observed-results.json +234 -0
  64. package/examples/vapi-tool-contract/success.case.json +118 -0
  65. package/examples/vapi-tool-contract/workflow.json +143 -0
  66. package/examples/vapi-tool-contract/wrong-id.json +143 -0
  67. package/examples/version-matrix/README.md +65 -0
  68. package/examples/version-matrix/observed-results.json +307 -0
  69. package/examples/version-matrix/upgrade-check.yml +32 -0
  70. package/package.json +41 -4
  71. package/src/action.mjs +108 -0
  72. package/src/case-draft.mjs +163 -0
  73. package/src/local-bundle.mjs +154 -0
  74. package/src/mock.mjs +72 -0
  75. package/src/report.mjs +73 -0
  76. package/src/runner.mjs +111 -0
  77. package/src/version.mjs +3 -0
  78. package/src/workflow.mjs +140 -0
@@ -0,0 +1,273 @@
1
+ {
2
+ "description": "Synthetic retrieval regression; same fixture and exact output assertions for both workflow versions. Recorded by blucca in a loopback-only Linux network namespace.",
3
+ "caseSha256": "2457e77b96dd0ebef55ff38cad44251710363cb97d1e4b413a677fa0a3c0cf2e",
4
+ "runs": [
5
+ {
6
+ "workflow": "broken.json",
7
+ "workflowSha256": "5663d261bd4995451550ee3c8f48793299074d523866dc9c8bea04740a7a43b7",
8
+ "runnerVersion": "0.1.2",
9
+ "n8nVersion": "2.41.7",
10
+ "startedAt": "2026-10-06T10:48:52.503Z",
11
+ "durationMs": 9159,
12
+ "network": {
13
+ "mode": "loopback-only",
14
+ "enforced": true,
15
+ "interfaces": [
16
+ "lo"
17
+ ]
18
+ },
19
+ "status": "failed",
20
+ "exitCode": 1,
21
+ "checks": [
22
+ {
23
+ "name": "n8n execution succeeds",
24
+ "passed": true,
25
+ "actual": {
26
+ "cliExitCode": 0,
27
+ "lastNode": "Confidence Gate",
28
+ "executionFound": true
29
+ }
30
+ },
31
+ {
32
+ "name": "Confidence Gate: output 0",
33
+ "passed": false,
34
+ "expected": [
35
+ {
36
+ "id": "policy-01",
37
+ "score": 0.9,
38
+ "source": "current-index"
39
+ },
40
+ {
41
+ "id": "policy-02",
42
+ "score": 0.9,
43
+ "source": "current-index"
44
+ },
45
+ {
46
+ "id": "policy-03",
47
+ "score": 0.9,
48
+ "source": "current-index"
49
+ },
50
+ {
51
+ "id": "policy-04",
52
+ "score": 0.9,
53
+ "source": "current-index"
54
+ },
55
+ {
56
+ "id": "policy-05",
57
+ "score": 0.9,
58
+ "source": "current-index"
59
+ },
60
+ {
61
+ "id": "policy-06",
62
+ "score": 0.9,
63
+ "source": "current-index"
64
+ },
65
+ {
66
+ "id": "policy-07",
67
+ "score": 0.9,
68
+ "source": "current-index"
69
+ },
70
+ {
71
+ "id": "policy-08",
72
+ "score": 0.9,
73
+ "source": "current-index"
74
+ }
75
+ ],
76
+ "actual": []
77
+ },
78
+ {
79
+ "name": "Build context: output 0",
80
+ "passed": false,
81
+ "expected": [
82
+ {
83
+ "count": 8,
84
+ "ids": [
85
+ "policy-01",
86
+ "policy-02",
87
+ "policy-03",
88
+ "policy-04",
89
+ "policy-05",
90
+ "policy-06",
91
+ "policy-07",
92
+ "policy-08"
93
+ ]
94
+ }
95
+ ],
96
+ "actual": []
97
+ },
98
+ {
99
+ "name": "mock: all requests match routes",
100
+ "passed": true,
101
+ "actual": []
102
+ },
103
+ {
104
+ "name": "mock: request limits and server",
105
+ "passed": true,
106
+ "actual": []
107
+ }
108
+ ],
109
+ "requests": []
110
+ },
111
+ {
112
+ "workflow": "fixed.json",
113
+ "workflowSha256": "849879b71262f8a03a96116f01c1791cd6bbe6d76d42b5836dbd780fb045d844",
114
+ "runnerVersion": "0.1.2",
115
+ "n8nVersion": "2.41.7",
116
+ "startedAt": "2026-10-06T10:49:01.779Z",
117
+ "durationMs": 8976,
118
+ "network": {
119
+ "mode": "loopback-only",
120
+ "enforced": true,
121
+ "interfaces": [
122
+ "lo"
123
+ ]
124
+ },
125
+ "status": "passed",
126
+ "exitCode": 0,
127
+ "checks": [
128
+ {
129
+ "name": "n8n execution succeeds",
130
+ "passed": true,
131
+ "actual": {
132
+ "cliExitCode": 0,
133
+ "lastNode": "Build context",
134
+ "executionFound": true
135
+ }
136
+ },
137
+ {
138
+ "name": "Confidence Gate: output 0",
139
+ "passed": true,
140
+ "expected": [
141
+ {
142
+ "id": "policy-01",
143
+ "score": 0.9,
144
+ "source": "current-index"
145
+ },
146
+ {
147
+ "id": "policy-02",
148
+ "score": 0.9,
149
+ "source": "current-index"
150
+ },
151
+ {
152
+ "id": "policy-03",
153
+ "score": 0.9,
154
+ "source": "current-index"
155
+ },
156
+ {
157
+ "id": "policy-04",
158
+ "score": 0.9,
159
+ "source": "current-index"
160
+ },
161
+ {
162
+ "id": "policy-05",
163
+ "score": 0.9,
164
+ "source": "current-index"
165
+ },
166
+ {
167
+ "id": "policy-06",
168
+ "score": 0.9,
169
+ "source": "current-index"
170
+ },
171
+ {
172
+ "id": "policy-07",
173
+ "score": 0.9,
174
+ "source": "current-index"
175
+ },
176
+ {
177
+ "id": "policy-08",
178
+ "score": 0.9,
179
+ "source": "current-index"
180
+ }
181
+ ],
182
+ "actual": [
183
+ {
184
+ "id": "policy-01",
185
+ "score": 0.9,
186
+ "source": "current-index"
187
+ },
188
+ {
189
+ "id": "policy-02",
190
+ "score": 0.9,
191
+ "source": "current-index"
192
+ },
193
+ {
194
+ "id": "policy-03",
195
+ "score": 0.9,
196
+ "source": "current-index"
197
+ },
198
+ {
199
+ "id": "policy-04",
200
+ "score": 0.9,
201
+ "source": "current-index"
202
+ },
203
+ {
204
+ "id": "policy-05",
205
+ "score": 0.9,
206
+ "source": "current-index"
207
+ },
208
+ {
209
+ "id": "policy-06",
210
+ "score": 0.9,
211
+ "source": "current-index"
212
+ },
213
+ {
214
+ "id": "policy-07",
215
+ "score": 0.9,
216
+ "source": "current-index"
217
+ },
218
+ {
219
+ "id": "policy-08",
220
+ "score": 0.9,
221
+ "source": "current-index"
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ "name": "Build context: output 0",
227
+ "passed": true,
228
+ "expected": [
229
+ {
230
+ "count": 8,
231
+ "ids": [
232
+ "policy-01",
233
+ "policy-02",
234
+ "policy-03",
235
+ "policy-04",
236
+ "policy-05",
237
+ "policy-06",
238
+ "policy-07",
239
+ "policy-08"
240
+ ]
241
+ }
242
+ ],
243
+ "actual": [
244
+ {
245
+ "count": 8,
246
+ "ids": [
247
+ "policy-01",
248
+ "policy-02",
249
+ "policy-03",
250
+ "policy-04",
251
+ "policy-05",
252
+ "policy-06",
253
+ "policy-07",
254
+ "policy-08"
255
+ ]
256
+ }
257
+ ]
258
+ },
259
+ {
260
+ "name": "mock: all requests match routes",
261
+ "passed": true,
262
+ "actual": []
263
+ },
264
+ {
265
+ "name": "mock: request limits and server",
266
+ "passed": true,
267
+ "actual": []
268
+ }
269
+ ],
270
+ "requests": []
271
+ }
272
+ ]
273
+ }
@@ -0,0 +1,66 @@
1
+ # Test the transformation between Google Sheets and Slack
2
+
3
+ **Keep the complete export. Inject two invoice rows. Check both notifications before delivery.**
4
+
5
+ This synthetic workflow includes `Start → Read invoices (Google Sheets) → Prepare notification (Code) → Notify team (Slack)`. The Sheets and Slack nodes carry synthetic credential references. The case replaces Sheets with fixture items and sets `"stopAfter": "Prepare notification"`, so the real Code node executes and the Slack node stays outside the run.
6
+
7
+ The original export stays unchanged. The prepared workflow, omitted nodes, and boundary change appear in the run artifacts.
8
+
9
+ ## Recorded result
10
+
11
+ Real n8n **2.41.7**, n8n-check **0.1.5**, synthetic invoice fixtures, and loopback-only networking:
12
+
13
+ | Export | n8n execution | Notification items | Contract |
14
+ |---|---|---:|---|
15
+ | Fixed transformation | Success | 2 | 4/4 checks, exit 0 |
16
+ | Deliberate `slice(0, 1)` regression | Success | 1 | Exact output fails, exit 1 |
17
+
18
+ Both runs omit the Slack delivery node and make zero HTTP requests. [Read the versioned results, file hashes, and expected/actual items](observed-results.json).
19
+
20
+ ## Run both shapes
21
+
22
+ From an n8n-check checkout with Docker installed:
23
+
24
+ ```sh
25
+ docker build -t n8n-check .
26
+ docker run --rm --network none -v "$PWD:/work" n8n-check \
27
+ examples/stop-after/fixed.json examples/stop-after/case.json \
28
+ --out /work/results/stop-after-fixed
29
+ # Expected: exit 0; both complete notification items match.
30
+
31
+ docker run --rm --network none -v "$PWD:/work" n8n-check \
32
+ examples/stop-after/broken.json examples/stop-after/case.json \
33
+ --out /work/results/stop-after-broken
34
+ # Expected: exit 1; the Code node succeeds but drops the second invoice.
35
+ ```
36
+
37
+ For an installed runtime, use `node bin/n8n-check.mjs` with the same two files, plus `--n8n /path/to/n8n --allow-network --out results/stop-after-fixed` for trusted local development.
38
+
39
+ ## Start with your own full export
40
+
41
+ In the [case builder](https://blucca.github.io/n8n-check/), import the export, choose the input and expected-output nodes, then enable **Run through selected output**. Review the selected execution slice and finish the fixture JSON. This example supplies pins for both selected nodes.
42
+
43
+ The equivalent draft command is:
44
+
45
+ ```sh
46
+ node bin/n8n-check.mjs init examples/stop-after/fixed.json \
47
+ --input "Read invoices" --assert "Prepare notification" \
48
+ --stop-after "Prepare notification" --out invoice.case.json
49
+ ```
50
+
51
+ n8n-check **v0.1.5+** supports one optional top-level field:
52
+
53
+ ```json
54
+ { "stopAfter": "Prepare notification" }
55
+ ```
56
+
57
+ Add that field to a complete case containing `version`, `name`, `input`, `mocks`, and `assertions`, as shown in [`case.json`](case.json).
58
+
59
+ ## Execution scope
60
+
61
+ - `stopAfter` cuts every outgoing connection at that node. Independently reachable sibling branches continue to execute and retain their fixture/mock requirements. Review the displayed slice before running.
62
+ - The named node must exist and be reachable from the fixture input. Choose a node after a loop's completed output; nodes inside directed cycles produce a setup error.
63
+ - Original incoming dependencies, references to omitted nodes, and assertion-node membership still receive structural checks. Keep the slice self-contained.
64
+ - With the field omitted, all reachable downstream nodes execute, preserving the existing case behavior.
65
+ - This contract covers invoice-to-notification transformation. Sheets reads and Slack delivery are separate integration checks. The connector nodes here contain synthetic placeholders for the bounded-slice example.
66
+ - Date-dependent logic uses the runtime's clock. Fixed-time scheduling and calendar tests require their own clock-aware fixture design.
@@ -0,0 +1,134 @@
1
+ {
2
+ "name": "Invoice notifications — complete synthetic export",
3
+ "nodes": [
4
+ {
5
+ "id": "start",
6
+ "name": "Start",
7
+ "type": "n8n-nodes-base.manualTrigger",
8
+ "typeVersion": 1,
9
+ "position": [
10
+ 0,
11
+ 0
12
+ ],
13
+ "parameters": {}
14
+ },
15
+ {
16
+ "id": "read-invoices",
17
+ "name": "Read invoices",
18
+ "type": "n8n-nodes-base.googleSheets",
19
+ "typeVersion": 4.7,
20
+ "position": [
21
+ 220,
22
+ 0
23
+ ],
24
+ "parameters": {},
25
+ "credentials": {
26
+ "googleSheetsOAuth2Api": {
27
+ "id": "synthetic-sheets",
28
+ "name": "Synthetic Sheets"
29
+ }
30
+ }
31
+ },
32
+ {
33
+ "id": "prepare-notification",
34
+ "name": "Prepare notification",
35
+ "type": "n8n-nodes-base.code",
36
+ "typeVersion": 2,
37
+ "position": [
38
+ 440,
39
+ 0
40
+ ],
41
+ "parameters": {
42
+ "mode": "runOnceForAllItems",
43
+ "jsCode": "return $input.all().slice(0, 1).map(({ json }) => ({ json: { invoiceId: json.invoiceId, text: `Invoice ${json.invoiceId}: $${(json.totalCents / 100).toFixed(2)}` } }));"
44
+ }
45
+ },
46
+ {
47
+ "id": "notify-team",
48
+ "name": "Notify team",
49
+ "type": "n8n-nodes-base.slack",
50
+ "typeVersion": 2.3,
51
+ "position": [
52
+ 660,
53
+ 0
54
+ ],
55
+ "parameters": {},
56
+ "credentials": {
57
+ "slackApi": {
58
+ "id": "synthetic-slack",
59
+ "name": "Synthetic Slack"
60
+ }
61
+ }
62
+ }
63
+ ],
64
+ "connections": {
65
+ "Start": {
66
+ "main": [
67
+ [
68
+ {
69
+ "node": "Read invoices",
70
+ "type": "main",
71
+ "index": 0
72
+ }
73
+ ]
74
+ ]
75
+ },
76
+ "Read invoices": {
77
+ "main": [
78
+ [
79
+ {
80
+ "node": "Prepare notification",
81
+ "type": "main",
82
+ "index": 0
83
+ }
84
+ ]
85
+ ]
86
+ },
87
+ "Prepare notification": {
88
+ "main": [
89
+ [
90
+ {
91
+ "node": "Notify team",
92
+ "type": "main",
93
+ "index": 0
94
+ }
95
+ ]
96
+ ]
97
+ }
98
+ },
99
+ "pinData": {
100
+ "Read invoices": [
101
+ {
102
+ "json": {
103
+ "invoiceId": "INV-42",
104
+ "totalCents": 65000
105
+ }
106
+ },
107
+ {
108
+ "json": {
109
+ "invoiceId": "INV-43",
110
+ "totalCents": 12050
111
+ }
112
+ }
113
+ ],
114
+ "Prepare notification": [
115
+ {
116
+ "json": {
117
+ "invoiceId": "INV-42",
118
+ "text": "Invoice INV-42: $650.00"
119
+ }
120
+ },
121
+ {
122
+ "json": {
123
+ "invoiceId": "INV-43",
124
+ "text": "Invoice INV-43: $120.50"
125
+ }
126
+ }
127
+ ]
128
+ },
129
+ "settings": {
130
+ "executionOrder": "v1",
131
+ "timezone": "UTC"
132
+ },
133
+ "active": false
134
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "version": 1,
3
+ "name": "Preserve both invoice notifications before Slack delivery",
4
+ "input": {
5
+ "node": "Read invoices",
6
+ "items": [
7
+ {
8
+ "invoiceId": "INV-42",
9
+ "totalCents": 65000
10
+ },
11
+ {
12
+ "invoiceId": "INV-43",
13
+ "totalCents": 12050
14
+ }
15
+ ]
16
+ },
17
+ "stopAfter": "Prepare notification",
18
+ "mocks": [],
19
+ "assertions": [
20
+ {
21
+ "node": "Prepare notification",
22
+ "equals": [
23
+ {
24
+ "invoiceId": "INV-42",
25
+ "text": "Invoice INV-42: $650.00"
26
+ },
27
+ {
28
+ "invoiceId": "INV-43",
29
+ "text": "Invoice INV-43: $120.50"
30
+ }
31
+ ]
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,134 @@
1
+ {
2
+ "name": "Invoice notifications — complete synthetic export",
3
+ "nodes": [
4
+ {
5
+ "id": "start",
6
+ "name": "Start",
7
+ "type": "n8n-nodes-base.manualTrigger",
8
+ "typeVersion": 1,
9
+ "position": [
10
+ 0,
11
+ 0
12
+ ],
13
+ "parameters": {}
14
+ },
15
+ {
16
+ "id": "read-invoices",
17
+ "name": "Read invoices",
18
+ "type": "n8n-nodes-base.googleSheets",
19
+ "typeVersion": 4.7,
20
+ "position": [
21
+ 220,
22
+ 0
23
+ ],
24
+ "parameters": {},
25
+ "credentials": {
26
+ "googleSheetsOAuth2Api": {
27
+ "id": "synthetic-sheets",
28
+ "name": "Synthetic Sheets"
29
+ }
30
+ }
31
+ },
32
+ {
33
+ "id": "prepare-notification",
34
+ "name": "Prepare notification",
35
+ "type": "n8n-nodes-base.code",
36
+ "typeVersion": 2,
37
+ "position": [
38
+ 440,
39
+ 0
40
+ ],
41
+ "parameters": {
42
+ "mode": "runOnceForAllItems",
43
+ "jsCode": "return $input.all().map(({ json }) => ({ json: { invoiceId: json.invoiceId, text: `Invoice ${json.invoiceId}: $${(json.totalCents / 100).toFixed(2)}` } }));"
44
+ }
45
+ },
46
+ {
47
+ "id": "notify-team",
48
+ "name": "Notify team",
49
+ "type": "n8n-nodes-base.slack",
50
+ "typeVersion": 2.3,
51
+ "position": [
52
+ 660,
53
+ 0
54
+ ],
55
+ "parameters": {},
56
+ "credentials": {
57
+ "slackApi": {
58
+ "id": "synthetic-slack",
59
+ "name": "Synthetic Slack"
60
+ }
61
+ }
62
+ }
63
+ ],
64
+ "connections": {
65
+ "Start": {
66
+ "main": [
67
+ [
68
+ {
69
+ "node": "Read invoices",
70
+ "type": "main",
71
+ "index": 0
72
+ }
73
+ ]
74
+ ]
75
+ },
76
+ "Read invoices": {
77
+ "main": [
78
+ [
79
+ {
80
+ "node": "Prepare notification",
81
+ "type": "main",
82
+ "index": 0
83
+ }
84
+ ]
85
+ ]
86
+ },
87
+ "Prepare notification": {
88
+ "main": [
89
+ [
90
+ {
91
+ "node": "Notify team",
92
+ "type": "main",
93
+ "index": 0
94
+ }
95
+ ]
96
+ ]
97
+ }
98
+ },
99
+ "pinData": {
100
+ "Read invoices": [
101
+ {
102
+ "json": {
103
+ "invoiceId": "INV-42",
104
+ "totalCents": 65000
105
+ }
106
+ },
107
+ {
108
+ "json": {
109
+ "invoiceId": "INV-43",
110
+ "totalCents": 12050
111
+ }
112
+ }
113
+ ],
114
+ "Prepare notification": [
115
+ {
116
+ "json": {
117
+ "invoiceId": "INV-42",
118
+ "text": "Invoice INV-42: $650.00"
119
+ }
120
+ },
121
+ {
122
+ "json": {
123
+ "invoiceId": "INV-43",
124
+ "text": "Invoice INV-43: $120.50"
125
+ }
126
+ }
127
+ ]
128
+ },
129
+ "settings": {
130
+ "executionOrder": "v1",
131
+ "timezone": "UTC"
132
+ },
133
+ "active": false
134
+ }