@gadmin2n/schematics 0.0.136 → 0.0.138

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 (94) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile.server +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/compose-ctl.sh +1 -1
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/canvas.prisma +1 -0
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +14 -3
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/User.ts +4 -4
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.md +2 -2
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/docker-compose.yml +13 -92
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/readme.md +105 -7
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/server/ensure-database.js +1 -1
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +5 -5
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +9 -1
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/notify-dev-servers.js +9 -8
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/scripts/wait-for-nest-ready.js +5 -5
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +43 -2
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow.seed.ts +9 -1
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflows.ts +24 -0
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.dto.ts +27 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.spec.ts +430 -0
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas-export.service.ts +203 -0
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.controller.ts +36 -3
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/canvas/canvas.module.ts +2 -1
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.spec.ts +54 -0
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.controller.ts +245 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.module.ts +10 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.spec.ts +366 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/temporalTenantConfig/temporalTenantConfig.service.ts +110 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-execution.dto.ts +19 -1
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.controller.ts +15 -0
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.spec.ts +79 -0
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +41 -19
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +5 -5
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/README.md +37 -573
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/web/ecosystem.dev.config.cjs +12 -1
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +2 -1
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/header.tsx +3 -2
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +39 -26
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +3 -1
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/common.json +3 -0
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/common.json +3 -0
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasEditPage.tsx +23 -5
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasListPage.tsx +66 -10
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasPage.tsx +16 -7
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/CanvasToolbar.tsx +21 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/canvasApi.ts +30 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ExportModal.tsx +84 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/components/ImportModal.tsx +166 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/canvas/hooks/useCanvasHistory.ts +52 -13
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/DslView.tsx +6 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/components/NodePropertyPanel.tsx +6 -1
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/editor.tsx +17 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/instance-detail.tsx +18 -6
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/temporal-tenant/index.tsx +225 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/lazy-with-reload.ts +31 -0
  54. package/package.json +1 -1
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.spec.ts +0 -220
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-dsl-validate.ts +0 -132
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/TODO.md +0 -152
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/config/development-sql.yaml +0 -5
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/docker-compose.yml +0 -25
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/package.json +0 -13
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.dockerignore +0 -12
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/.env +0 -7
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/Dockerfile +0 -79
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/GRACEFUL-DEPLOYMENT.md +0 -270
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/SANDBOX.md +0 -122
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package-lock.json +0 -4285
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/package.json +0 -28
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/code-execute.ts +0 -51
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-execute.ts +0 -85
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/db-query.ts +0 -35
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/http-request.ts +0 -54
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/index.ts +0 -6
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/reporting.ts +0 -85
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/activities/send-notification.ts +0 -47
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/config.ts +0 -13
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/condition.ts +0 -101
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/context.ts +0 -58
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/graph.ts +0 -184
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/helpers.ts +0 -133
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/node-types.ts +0 -96
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/types.ts +0 -77
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/dsl/validate.ts +0 -109
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/index.ts +0 -101
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/outbox-poller.ts +0 -382
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/code-execute.test.ts +0 -44
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/activities/http-request.test.ts +0 -87
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/cron-trigger-workflow.test.ts +0 -20
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/helpers.test.ts +0 -225
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/node-type-consistency.test.ts +0 -101
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/tests/validate.test.ts +0 -205
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/src/workflows/dsl-workflow.ts +0 -499
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/tsconfig.json +0 -19
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/vitest.config.ts +0 -8
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/temporal/worker/yarn.lock +0 -1905
@@ -1,225 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- resolveTemplates,
4
- resolveExpression,
5
- getNestedValue,
6
- evaluateConditions,
7
- evaluateRule,
8
- getSuccessorsFromNodes,
9
- getNextNodes,
10
- } from '../dsl/helpers';
11
- import type { WorkflowDSL, DslNode } from '../dsl/types';
12
-
13
- // ─── getNestedValue ──────────────────────────────────────────────────────────
14
-
15
- describe('getNestedValue', () => {
16
- it('resolves simple path', () => {
17
- expect(getNestedValue({ a: 1 }, 'a')).toBe(1);
18
- });
19
-
20
- it('resolves nested path', () => {
21
- expect(getNestedValue({ a: { b: { c: 42 } } }, 'a.b.c')).toBe(42);
22
- });
23
-
24
- it('returns undefined for missing path', () => {
25
- expect(getNestedValue({ a: 1 }, 'b')).toBeUndefined();
26
- });
27
-
28
- it('returns undefined for null intermediate', () => {
29
- expect(getNestedValue({ a: null }, 'a.b')).toBeUndefined();
30
- });
31
-
32
- it('resolves array index via dot notation', () => {
33
- expect(getNestedValue({ items: ['x', 'y'] }, 'items.0')).toBe('x');
34
- });
35
- });
36
-
37
- // ─── resolveTemplates ────────────────────────────────────────────────────────
38
-
39
- describe('resolveTemplates', () => {
40
- const ctx = { name: 'Alice', node1: { status: 200, data: { id: 5 } } };
41
-
42
- it('replaces simple template', () => {
43
- expect(resolveTemplates('Hello {{name}}', ctx)).toBe('Hello Alice');
44
- });
45
-
46
- it('replaces nested path template', () => {
47
- expect(resolveTemplates('Status: {{node1.status}}', ctx)).toBe('Status: 200');
48
- });
49
-
50
- it('replaces multiple templates in one string', () => {
51
- expect(resolveTemplates('{{name}} got {{node1.status}}', ctx)).toBe('Alice got 200');
52
- });
53
-
54
- it('replaces missing path with empty string', () => {
55
- expect(resolveTemplates('{{missing}}', ctx)).toBe('');
56
- });
57
-
58
- it('recursively resolves objects', () => {
59
- const obj = { url: 'https://api.com/{{node1.data.id}}', method: 'GET' };
60
- expect(resolveTemplates(obj, ctx)).toEqual({ url: 'https://api.com/5', method: 'GET' });
61
- });
62
-
63
- it('recursively resolves arrays', () => {
64
- expect(resolveTemplates(['{{name}}', '{{node1.status}}'], ctx)).toEqual(['Alice', '200']);
65
- });
66
-
67
- it('passes through non-string primitives', () => {
68
- expect(resolveTemplates(42, ctx)).toBe(42);
69
- expect(resolveTemplates(null, ctx)).toBe(null);
70
- expect(resolveTemplates(true, ctx)).toBe(true);
71
- });
72
- });
73
-
74
- // ─── resolveExpression ───────────────────────────────────────────────────────
75
-
76
- describe('resolveExpression', () => {
77
- it('resolves existing path', () => {
78
- expect(resolveExpression('a.b', { a: { b: 10 } })).toBe(10);
79
- });
80
-
81
- it('returns raw string for unresolvable expression', () => {
82
- expect(resolveExpression('literal_value', {})).toBe('literal_value');
83
- });
84
- });
85
-
86
- // ─── evaluateRule ────────────────────────────────────────────────────────────
87
-
88
- describe('evaluateRule', () => {
89
- it('equals / ==', () => {
90
- expect(evaluateRule('hello', 'equals', 'hello')).toBe(true);
91
- expect(evaluateRule('hello', '==', 'world')).toBe(false);
92
- });
93
-
94
- it('notEquals / !=', () => {
95
- expect(evaluateRule('a', 'notEquals', 'b')).toBe(true);
96
- expect(evaluateRule('a', '!=', 'a')).toBe(false);
97
- });
98
-
99
- it('contains / notContains', () => {
100
- expect(evaluateRule('hello world', 'contains', 'world')).toBe(true);
101
- expect(evaluateRule('hello world', 'notContains', 'xyz')).toBe(true);
102
- });
103
-
104
- it('numeric comparisons', () => {
105
- expect(evaluateRule(10, 'greaterThan', '5')).toBe(true);
106
- expect(evaluateRule(10, '>', '15')).toBe(false);
107
- expect(evaluateRule(10, 'lessThan', '15')).toBe(true);
108
- expect(evaluateRule(10, '<', '5')).toBe(false);
109
- expect(evaluateRule(10, '>=', '10')).toBe(true);
110
- expect(evaluateRule(10, '<=', '10')).toBe(true);
111
- });
112
-
113
- it('isEmpty / isNotEmpty', () => {
114
- expect(evaluateRule(null, 'isEmpty', '')).toBe(true);
115
- expect(evaluateRule('', 'isEmpty', '')).toBe(true);
116
- expect(evaluateRule('x', 'isEmpty', '')).toBe(false);
117
- expect(evaluateRule('x', 'isNotEmpty', '')).toBe(true);
118
- expect(evaluateRule(null, 'isNotEmpty', '')).toBe(false);
119
- });
120
-
121
- it('unknown operator returns false', () => {
122
- expect(evaluateRule('a', 'unknownOp', 'b')).toBe(false);
123
- });
124
- });
125
-
126
- // ─── evaluateConditions ──────────────────────────────────────────────────────
127
-
128
- describe('evaluateConditions', () => {
129
- const ctx = { status: 'active', count: '10' };
130
-
131
- it('returns true for empty conditions', () => {
132
- expect(evaluateConditions([], ctx, 'AND')).toBe(true);
133
- });
134
-
135
- it('AND: all must pass', () => {
136
- const conds = [
137
- { field: 'status', operator: 'equals', value: 'active' },
138
- { field: 'count', operator: 'greaterThan', value: '5' },
139
- ];
140
- expect(evaluateConditions(conds, ctx, 'AND')).toBe(true);
141
- });
142
-
143
- it('AND: one fails → false', () => {
144
- const conds = [
145
- { field: 'status', operator: 'equals', value: 'active' },
146
- { field: 'count', operator: 'greaterThan', value: '100' },
147
- ];
148
- expect(evaluateConditions(conds, ctx, 'AND')).toBe(false);
149
- });
150
-
151
- it('OR: one passes → true', () => {
152
- const conds = [
153
- { field: 'status', operator: 'equals', value: 'inactive' },
154
- { field: 'count', operator: 'equals', value: '10' },
155
- ];
156
- expect(evaluateConditions(conds, ctx, 'OR')).toBe(true);
157
- });
158
-
159
- it('OR: all fail → false', () => {
160
- const conds = [
161
- { field: 'status', operator: 'equals', value: 'inactive' },
162
- { field: 'count', operator: 'greaterThan', value: '100' },
163
- ];
164
- expect(evaluateConditions(conds, ctx, 'OR')).toBe(false);
165
- });
166
- });
167
-
168
- // ─── Graph Navigation ────────────────────────────────────────────────────────
169
-
170
- describe('getSuccessorsFromNodes', () => {
171
- const dsl: WorkflowDSL = {
172
- nodes: [
173
- { id: 'a', type: 'manual_trigger', label: 'A', position: { x: 0, y: 0 }, config: {} },
174
- { id: 'b', type: 'http_request', label: 'B', position: { x: 0, y: 0 }, config: {} },
175
- { id: 'c', type: 'db_query', label: 'C', position: { x: 0, y: 0 }, config: {} },
176
- ],
177
- edges: [
178
- { id: 'e1', source: 'a', target: 'b' },
179
- { id: 'e2', source: 'a', target: 'c' },
180
- { id: 'e3', source: 'b', target: 'c' },
181
- ],
182
- };
183
-
184
- it('finds direct successors', () => {
185
- expect(getSuccessorsFromNodes(dsl, ['a'])).toEqual(['b', 'c']);
186
- });
187
-
188
- it('finds successors from multiple sources', () => {
189
- expect(getSuccessorsFromNodes(dsl, ['a', 'b'])).toEqual(['b', 'c', 'c']);
190
- });
191
-
192
- it('returns empty for leaf nodes', () => {
193
- expect(getSuccessorsFromNodes(dsl, ['c'])).toEqual([]);
194
- });
195
- });
196
-
197
- describe('getNextNodes', () => {
198
- const dsl: WorkflowDSL = {
199
- nodes: [
200
- { id: 'cond', type: 'if_else', label: 'Condition', position: { x: 0, y: 0 }, config: {} },
201
- { id: 'yes', type: 'http_request', label: 'Yes', position: { x: 0, y: 0 }, config: {} },
202
- { id: 'no', type: 'send_notification', label: 'No', position: { x: 0, y: 0 }, config: {} },
203
- ],
204
- edges: [
205
- { id: 'e1', source: 'cond', target: 'yes', sourceHandle: 'true' },
206
- { id: 'e2', source: 'cond', target: 'no', sourceHandle: 'false' },
207
- ],
208
- };
209
-
210
- it('follows branch edge when branch specified', () => {
211
- const node = dsl.nodes[0];
212
- expect(getNextNodes(dsl, node, 'true')).toEqual(['yes']);
213
- expect(getNextNodes(dsl, node, 'false')).toEqual(['no']);
214
- });
215
-
216
- it('follows all edges when no branch', () => {
217
- const node = dsl.nodes[0];
218
- expect(getNextNodes(dsl, node, undefined)).toEqual(['yes', 'no']);
219
- });
220
-
221
- it('falls back to all edges if branch has no matching edge', () => {
222
- const node = dsl.nodes[0];
223
- expect(getNextNodes(dsl, node, 'nonexistent')).toEqual(['yes', 'no']);
224
- });
225
- });
@@ -1,101 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { ALL_NODE_TYPES, ACTIVITY_NODE_TYPES, NodeType } from '../dsl/node-types';
3
- import * as path from 'path';
4
- import * as fs from 'fs';
5
-
6
- // ─── Consistency with seed file ──────────────────────────────────────────────
7
-
8
- describe('Node Type Consistency', () => {
9
- // Read the seed file and extract types
10
- const seedFilePath = path.resolve(__dirname, '../../../../server/seed/workflow-node-types.ts');
11
- const seedContent = fs.readFileSync(seedFilePath, 'utf-8');
12
-
13
- // Extract only top-level node type identifiers (at 4-space indent, not JSON Schema "type" fields)
14
- // Pattern: lines like ` type: 'cron_trigger',` (4 spaces indent, contains underscore or known names)
15
- const typeRegex = /^\s{4}type:\s*'([a-z][a-z_]+)'/gm;
16
- const seedTypes: string[] = [];
17
- let match: RegExpExecArray | null;
18
- while ((match = typeRegex.exec(seedContent)) !== null) {
19
- // Exclude JSON Schema type values like 'object', 'string', 'number', 'array', 'boolean'
20
- const jsonSchemaTypes = ['object', 'string', 'number', 'array', 'boolean', 'integer'];
21
- if (!jsonSchemaTypes.includes(match[1])) {
22
- seedTypes.push(match[1]);
23
- }
24
- }
25
-
26
- it('seed file has node types defined', () => {
27
- expect(seedTypes.length).toBeGreaterThan(0);
28
- });
29
-
30
- it('every seed node type exists in ALL_NODE_TYPES', () => {
31
- const allTypes = ALL_NODE_TYPES as readonly string[];
32
- const missing = seedTypes.filter((t) => !allTypes.includes(t));
33
- expect(missing).toEqual([]);
34
- });
35
-
36
- it('every ALL_NODE_TYPES entry exists in seed file', () => {
37
- const missing = ALL_NODE_TYPES.filter((t) => !seedTypes.includes(t));
38
- expect(missing).toEqual([]);
39
- });
40
-
41
- it('no duplicates in ALL_NODE_TYPES', () => {
42
- const set = new Set(ALL_NODE_TYPES);
43
- expect(set.size).toBe(ALL_NODE_TYPES.length);
44
- });
45
-
46
- it('ACTIVITY_NODE_TYPES is a subset of ALL_NODE_TYPES', () => {
47
- const allTypes = ALL_NODE_TYPES as readonly string[];
48
- for (const t of ACTIVITY_NODE_TYPES) {
49
- expect(allTypes).toContain(t);
50
- }
51
- });
52
- });
53
-
54
- // ─── Consistency with executeNode switch ─────────────────────────────────────
55
-
56
- describe('executeNode switch coverage', () => {
57
- const workflowFilePath = path.resolve(__dirname, '../workflows/dsl-workflow.ts');
58
- const workflowContent = fs.readFileSync(workflowFilePath, 'utf-8');
59
-
60
- it('every NodeType has a case in executeNode', () => {
61
- const missingCases: string[] = [];
62
- for (const nodeType of ALL_NODE_TYPES) {
63
- // Check for case 'nodeType': pattern
64
- if (!workflowContent.includes(`case '${nodeType}'`)) {
65
- missingCases.push(nodeType);
66
- }
67
- }
68
- expect(missingCases).toEqual([]);
69
- });
70
- });
71
-
72
- // ─── Consistency with activity files ─────────────────────────────────────────
73
-
74
- describe('Activity file coverage', () => {
75
- const activitiesIndexPath = path.resolve(__dirname, '../activities/index.ts');
76
- const activitiesContent = fs.readFileSync(activitiesIndexPath, 'utf-8');
77
-
78
- // Map node types to expected activity function names
79
- const nodeTypeToActivity: Record<string, string> = {
80
- http_request: 'httpRequest',
81
- db_query: 'dbQuery',
82
- db_execute: 'dbExecute',
83
- send_notification: 'sendNotification',
84
- code: 'codeExecute',
85
- };
86
-
87
- it('every ACTIVITY_NODE_TYPE has a corresponding exported activity', () => {
88
- const missing: string[] = [];
89
- for (const nodeType of ACTIVITY_NODE_TYPES) {
90
- const expectedFn = nodeTypeToActivity[nodeType];
91
- if (!expectedFn) {
92
- missing.push(`${nodeType}: no mapping defined in test`);
93
- continue;
94
- }
95
- if (!activitiesContent.includes(expectedFn)) {
96
- missing.push(`${nodeType}: activity "${expectedFn}" not found in activities/index.ts`);
97
- }
98
- }
99
- expect(missing).toEqual([]);
100
- });
101
- });
@@ -1,205 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { validateDslHandles } from '../dsl/validate';
3
- import type { WorkflowDSL } from '../dsl/types';
4
-
5
- // ─── helpers ─────────────────────────────────────────────────────────────────
6
-
7
- function makeNode(
8
- id: string,
9
- type: string,
10
- config: Record<string, any> = {},
11
- ): WorkflowDSL['nodes'][number] {
12
- return {
13
- id,
14
- type: type as any,
15
- label: id,
16
- position: { x: 0, y: 0 },
17
- config,
18
- };
19
- }
20
-
21
- function makeEdge(
22
- id: string,
23
- source: string,
24
- target: string,
25
- sourceHandle?: string,
26
- ): WorkflowDSL['edges'][number] {
27
- return sourceHandle === undefined
28
- ? { id, source, target }
29
- : { id, source, target, sourceHandle };
30
- }
31
-
32
- // ─── if_else (enumerated outputs) ────────────────────────────────────────────
33
-
34
- describe('validateDslHandles — if_else', () => {
35
- it('accepts sourceHandle "true"', () => {
36
- const dsl: WorkflowDSL = {
37
- nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
38
- edges: [makeEdge('e1', 'a', 'b', 'true')],
39
- };
40
- expect(validateDslHandles(dsl)).toEqual([]);
41
- });
42
-
43
- it('accepts sourceHandle "false"', () => {
44
- const dsl: WorkflowDSL = {
45
- nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
46
- edges: [makeEdge('e1', 'a', 'b', 'false')],
47
- };
48
- expect(validateDslHandles(dsl)).toEqual([]);
49
- });
50
-
51
- it('rejects unknown sourceHandle "maybe"', () => {
52
- const dsl: WorkflowDSL = {
53
- nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
54
- edges: [makeEdge('e1', 'a', 'b', 'maybe')],
55
- };
56
- const errors = validateDslHandles(dsl);
57
- expect(errors).toHaveLength(1);
58
- expect(errors[0].edgeId).toBe('e1');
59
- expect(errors[0].code).toBe('UNKNOWN_HANDLE');
60
- expect(errors[0].reason).toMatch(/not in declared outputs/);
61
- });
62
-
63
- it('rejects missing sourceHandle', () => {
64
- const dsl: WorkflowDSL = {
65
- nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
66
- edges: [makeEdge('e1', 'a', 'b')],
67
- };
68
- const errors = validateDslHandles(dsl);
69
- expect(errors).toHaveLength(1);
70
- expect(errors[0].edgeId).toBe('e1');
71
- expect(errors[0].code).toBe('MISSING_HANDLE');
72
- expect(errors[0].reason).toMatch(/requires a sourceHandle/);
73
- });
74
-
75
- it('rejects empty-string sourceHandle as missing', () => {
76
- const dsl: WorkflowDSL = {
77
- nodes: [makeNode('a', 'if_else'), makeNode('b', 'http_request')],
78
- edges: [makeEdge('e1', 'a', 'b', '')],
79
- };
80
- const errors = validateDslHandles(dsl);
81
- expect(errors).toHaveLength(1);
82
- expect(errors[0].edgeId).toBe('e1');
83
- expect(errors[0].code).toBe('MISSING_HANDLE');
84
- });
85
- });
86
-
87
- // ─── cron_trigger (no named outputs) ─────────────────────────────────────────
88
-
89
- describe('validateDslHandles — cron_trigger (no named outputs)', () => {
90
- it('rejects sourceHandle "foo" on a node with no named handles', () => {
91
- const dsl: WorkflowDSL = {
92
- nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
93
- edges: [makeEdge('e1', 't', 'a', 'foo')],
94
- };
95
- const errors = validateDslHandles(dsl);
96
- expect(errors).toHaveLength(1);
97
- expect(errors[0].edgeId).toBe('e1');
98
- expect(errors[0].code).toBe('EXTRA_HANDLE');
99
- expect(errors[0].reason).toMatch(/no named output handles/);
100
- });
101
-
102
- it('accepts no sourceHandle', () => {
103
- const dsl: WorkflowDSL = {
104
- nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
105
- edges: [makeEdge('e1', 't', 'a')],
106
- };
107
- expect(validateDslHandles(dsl)).toEqual([]);
108
- });
109
-
110
- it('accepts empty-string sourceHandle (treated as absent for [] nodes)', () => {
111
- const dsl: WorkflowDSL = {
112
- nodes: [makeNode('t', 'cron_trigger'), makeNode('a', 'http_request')],
113
- edges: [makeEdge('e1', 't', 'a', '')],
114
- };
115
- expect(validateDslHandles(dsl)).toEqual([]);
116
- });
117
- });
118
-
119
- // ─── switch (dynamic outputs) ────────────────────────────────────────────────
120
-
121
- describe('validateDslHandles — switch (dynamic outputs)', () => {
122
- it('accepts sourceHandle matching a case value', () => {
123
- const dsl: WorkflowDSL = {
124
- nodes: [
125
- makeNode('s', 'switch', { cases: [{ value: 'a' }, { value: 'b' }] }),
126
- makeNode('x', 'http_request'),
127
- ],
128
- edges: [makeEdge('e1', 's', 'x', 'a')],
129
- };
130
- expect(validateDslHandles(dsl)).toEqual([]);
131
- });
132
-
133
- it('rejects sourceHandle not in case values', () => {
134
- const dsl: WorkflowDSL = {
135
- nodes: [
136
- makeNode('s', 'switch', { cases: [{ value: 'a' }] }),
137
- makeNode('x', 'http_request'),
138
- ],
139
- edges: [makeEdge('e1', 's', 'x', 'c')],
140
- };
141
- const errors = validateDslHandles(dsl);
142
- expect(errors).toHaveLength(1);
143
- expect(errors[0].edgeId).toBe('e1');
144
- expect(errors[0].code).toBe('UNKNOWN_HANDLE');
145
- });
146
-
147
- it('skips validation when switch config has no cases (in-progress editor)', () => {
148
- const dsl: WorkflowDSL = {
149
- nodes: [makeNode('s', 'switch'), makeNode('x', 'http_request')],
150
- edges: [makeEdge('e1', 's', 'x', 'anything')],
151
- };
152
- expect(validateDslHandles(dsl)).toEqual([]);
153
- });
154
- });
155
-
156
- // ─── out-of-scope (orphan / unknown) ────────────────────────────────────────
157
-
158
- describe('validateDslHandles — out-of-scope', () => {
159
- it('ignores edge referencing missing source node', () => {
160
- const dsl: WorkflowDSL = {
161
- nodes: [makeNode('b', 'http_request')],
162
- edges: [makeEdge('e1', 'ghost', 'b', 'true')],
163
- };
164
- expect(validateDslHandles(dsl)).toEqual([]);
165
- });
166
-
167
- it('ignores edges from unknown node types', () => {
168
- const dsl: WorkflowDSL = {
169
- nodes: [makeNode('u', 'wholly_made_up_type'), makeNode('b', 'http_request')],
170
- edges: [makeEdge('e1', 'u', 'b', 'whatever')],
171
- };
172
- expect(validateDslHandles(dsl)).toEqual([]);
173
- });
174
- });
175
-
176
- // ─── multiple errors collected ──────────────────────────────────────────────
177
-
178
- describe('validateDslHandles — multiple errors', () => {
179
- it('collects every error in one pass', () => {
180
- const dsl: WorkflowDSL = {
181
- nodes: [
182
- makeNode('if', 'if_else'),
183
- makeNode('cron', 'cron_trigger'),
184
- makeNode('sw', 'switch', { cases: [{ value: 'a' }] }),
185
- makeNode('x', 'http_request'),
186
- ],
187
- edges: [
188
- makeEdge('e1', 'if', 'x', 'maybe'), // bad enumerated
189
- makeEdge('e2', 'if', 'x'), // missing required
190
- makeEdge('e3', 'cron', 'x', 'foo'), // forbidden handle
191
- makeEdge('e4', 'sw', 'x', 'c'), // bad dynamic
192
- ],
193
- };
194
- const errors = validateDslHandles(dsl);
195
- expect(errors).toHaveLength(4);
196
- expect(errors.map((e) => e.edgeId).sort()).toEqual(['e1', 'e2', 'e3', 'e4']);
197
- const byEdge = Object.fromEntries(errors.map((e) => [e.edgeId, e.code]));
198
- expect(byEdge).toEqual({
199
- e1: 'UNKNOWN_HANDLE',
200
- e2: 'MISSING_HANDLE',
201
- e3: 'EXTRA_HANDLE',
202
- e4: 'UNKNOWN_HANDLE',
203
- });
204
- });
205
- });