agentic-qe 2.5.6 → 2.5.8

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 (210) hide show
  1. package/.claude/agents/n8n/n8n-base-agent.md +376 -0
  2. package/.claude/agents/n8n/n8n-bdd-scenario-tester.md +613 -0
  3. package/.claude/agents/n8n/n8n-chaos-tester.md +654 -0
  4. package/.claude/agents/n8n/n8n-ci-orchestrator.md +850 -0
  5. package/.claude/agents/n8n/n8n-compliance-validator.md +685 -0
  6. package/.claude/agents/n8n/n8n-expression-validator.md +560 -0
  7. package/.claude/agents/n8n/n8n-integration-test.md +602 -0
  8. package/.claude/agents/n8n/n8n-monitoring-validator.md +589 -0
  9. package/.claude/agents/n8n/n8n-node-validator.md +455 -0
  10. package/.claude/agents/n8n/n8n-performance-tester.md +630 -0
  11. package/.claude/agents/n8n/n8n-security-auditor.md +786 -0
  12. package/.claude/agents/n8n/n8n-trigger-test.md +500 -0
  13. package/.claude/agents/n8n/n8n-unit-tester.md +633 -0
  14. package/.claude/agents/n8n/n8n-version-comparator.md +567 -0
  15. package/.claude/agents/n8n/n8n-workflow-executor.md +392 -0
  16. package/.claude/skills/n8n-expression-testing/SKILL.md +434 -0
  17. package/.claude/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
  18. package/.claude/skills/n8n-security-testing/SKILL.md +599 -0
  19. package/.claude/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
  20. package/.claude/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
  21. package/CHANGELOG.md +127 -0
  22. package/README.md +7 -4
  23. package/dist/agents/BaseAgent.d.ts +142 -0
  24. package/dist/agents/BaseAgent.d.ts.map +1 -1
  25. package/dist/agents/BaseAgent.js +372 -2
  26. package/dist/agents/BaseAgent.js.map +1 -1
  27. package/dist/agents/TestGeneratorAgent.d.ts +5 -0
  28. package/dist/agents/TestGeneratorAgent.d.ts.map +1 -1
  29. package/dist/agents/TestGeneratorAgent.js +38 -0
  30. package/dist/agents/TestGeneratorAgent.js.map +1 -1
  31. package/dist/agents/index.d.ts +1 -1
  32. package/dist/agents/index.d.ts.map +1 -1
  33. package/dist/agents/index.js.map +1 -1
  34. package/dist/agents/n8n/N8nAPIClient.d.ts +121 -0
  35. package/dist/agents/n8n/N8nAPIClient.d.ts.map +1 -0
  36. package/dist/agents/n8n/N8nAPIClient.js +367 -0
  37. package/dist/agents/n8n/N8nAPIClient.js.map +1 -0
  38. package/dist/agents/n8n/N8nAuditPersistence.d.ts +120 -0
  39. package/dist/agents/n8n/N8nAuditPersistence.d.ts.map +1 -0
  40. package/dist/agents/n8n/N8nAuditPersistence.js +473 -0
  41. package/dist/agents/n8n/N8nAuditPersistence.js.map +1 -0
  42. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts +159 -0
  43. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.d.ts.map +1 -0
  44. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js +697 -0
  45. package/dist/agents/n8n/N8nBDDScenarioTesterAgent.js.map +1 -0
  46. package/dist/agents/n8n/N8nBaseAgent.d.ts +126 -0
  47. package/dist/agents/n8n/N8nBaseAgent.d.ts.map +1 -0
  48. package/dist/agents/n8n/N8nBaseAgent.js +446 -0
  49. package/dist/agents/n8n/N8nBaseAgent.js.map +1 -0
  50. package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts +164 -0
  51. package/dist/agents/n8n/N8nCIOrchestratorAgent.d.ts.map +1 -0
  52. package/dist/agents/n8n/N8nCIOrchestratorAgent.js +610 -0
  53. package/dist/agents/n8n/N8nCIOrchestratorAgent.js.map +1 -0
  54. package/dist/agents/n8n/N8nChaosTesterAgent.d.ts +205 -0
  55. package/dist/agents/n8n/N8nChaosTesterAgent.d.ts.map +1 -0
  56. package/dist/agents/n8n/N8nChaosTesterAgent.js +729 -0
  57. package/dist/agents/n8n/N8nChaosTesterAgent.js.map +1 -0
  58. package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts +228 -0
  59. package/dist/agents/n8n/N8nComplianceValidatorAgent.d.ts.map +1 -0
  60. package/dist/agents/n8n/N8nComplianceValidatorAgent.js +986 -0
  61. package/dist/agents/n8n/N8nComplianceValidatorAgent.js.map +1 -0
  62. package/dist/agents/n8n/N8nContractTesterAgent.d.ts +213 -0
  63. package/dist/agents/n8n/N8nContractTesterAgent.d.ts.map +1 -0
  64. package/dist/agents/n8n/N8nContractTesterAgent.js +989 -0
  65. package/dist/agents/n8n/N8nContractTesterAgent.js.map +1 -0
  66. package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts +99 -0
  67. package/dist/agents/n8n/N8nExpressionValidatorAgent.d.ts.map +1 -0
  68. package/dist/agents/n8n/N8nExpressionValidatorAgent.js +632 -0
  69. package/dist/agents/n8n/N8nExpressionValidatorAgent.js.map +1 -0
  70. package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts +238 -0
  71. package/dist/agents/n8n/N8nFailureModeTesterAgent.d.ts.map +1 -0
  72. package/dist/agents/n8n/N8nFailureModeTesterAgent.js +956 -0
  73. package/dist/agents/n8n/N8nFailureModeTesterAgent.js.map +1 -0
  74. package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts +242 -0
  75. package/dist/agents/n8n/N8nIdempotencyTesterAgent.d.ts.map +1 -0
  76. package/dist/agents/n8n/N8nIdempotencyTesterAgent.js +992 -0
  77. package/dist/agents/n8n/N8nIdempotencyTesterAgent.js.map +1 -0
  78. package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts +104 -0
  79. package/dist/agents/n8n/N8nIntegrationTestAgent.d.ts.map +1 -0
  80. package/dist/agents/n8n/N8nIntegrationTestAgent.js +653 -0
  81. package/dist/agents/n8n/N8nIntegrationTestAgent.js.map +1 -0
  82. package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts +210 -0
  83. package/dist/agents/n8n/N8nMonitoringValidatorAgent.d.ts.map +1 -0
  84. package/dist/agents/n8n/N8nMonitoringValidatorAgent.js +669 -0
  85. package/dist/agents/n8n/N8nMonitoringValidatorAgent.js.map +1 -0
  86. package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts +142 -0
  87. package/dist/agents/n8n/N8nNodeValidatorAgent.d.ts.map +1 -0
  88. package/dist/agents/n8n/N8nNodeValidatorAgent.js +1090 -0
  89. package/dist/agents/n8n/N8nNodeValidatorAgent.js.map +1 -0
  90. package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts +198 -0
  91. package/dist/agents/n8n/N8nPerformanceTesterAgent.d.ts.map +1 -0
  92. package/dist/agents/n8n/N8nPerformanceTesterAgent.js +653 -0
  93. package/dist/agents/n8n/N8nPerformanceTesterAgent.js.map +1 -0
  94. package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts +245 -0
  95. package/dist/agents/n8n/N8nReplayabilityTesterAgent.d.ts.map +1 -0
  96. package/dist/agents/n8n/N8nReplayabilityTesterAgent.js +952 -0
  97. package/dist/agents/n8n/N8nReplayabilityTesterAgent.js.map +1 -0
  98. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts +325 -0
  99. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.d.ts.map +1 -0
  100. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js +1187 -0
  101. package/dist/agents/n8n/N8nSecretsHygieneAuditorAgent.js.map +1 -0
  102. package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts +91 -0
  103. package/dist/agents/n8n/N8nSecurityAuditorAgent.d.ts.map +1 -0
  104. package/dist/agents/n8n/N8nSecurityAuditorAgent.js +825 -0
  105. package/dist/agents/n8n/N8nSecurityAuditorAgent.js.map +1 -0
  106. package/dist/agents/n8n/N8nTestHarness.d.ts +131 -0
  107. package/dist/agents/n8n/N8nTestHarness.d.ts.map +1 -0
  108. package/dist/agents/n8n/N8nTestHarness.js +456 -0
  109. package/dist/agents/n8n/N8nTestHarness.js.map +1 -0
  110. package/dist/agents/n8n/N8nTriggerTestAgent.d.ts +119 -0
  111. package/dist/agents/n8n/N8nTriggerTestAgent.d.ts.map +1 -0
  112. package/dist/agents/n8n/N8nTriggerTestAgent.js +652 -0
  113. package/dist/agents/n8n/N8nTriggerTestAgent.js.map +1 -0
  114. package/dist/agents/n8n/N8nUnitTesterAgent.d.ts +130 -0
  115. package/dist/agents/n8n/N8nUnitTesterAgent.d.ts.map +1 -0
  116. package/dist/agents/n8n/N8nUnitTesterAgent.js +522 -0
  117. package/dist/agents/n8n/N8nUnitTesterAgent.js.map +1 -0
  118. package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts +201 -0
  119. package/dist/agents/n8n/N8nVersionComparatorAgent.d.ts.map +1 -0
  120. package/dist/agents/n8n/N8nVersionComparatorAgent.js +645 -0
  121. package/dist/agents/n8n/N8nVersionComparatorAgent.js.map +1 -0
  122. package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts +120 -0
  123. package/dist/agents/n8n/N8nWorkflowExecutorAgent.d.ts.map +1 -0
  124. package/dist/agents/n8n/N8nWorkflowExecutorAgent.js +347 -0
  125. package/dist/agents/n8n/N8nWorkflowExecutorAgent.js.map +1 -0
  126. package/dist/agents/n8n/index.d.ts +119 -0
  127. package/dist/agents/n8n/index.d.ts.map +1 -0
  128. package/dist/agents/n8n/index.js +298 -0
  129. package/dist/agents/n8n/index.js.map +1 -0
  130. package/dist/agents/n8n/types.d.ts +486 -0
  131. package/dist/agents/n8n/types.d.ts.map +1 -0
  132. package/dist/agents/n8n/types.js +8 -0
  133. package/dist/agents/n8n/types.js.map +1 -0
  134. package/dist/cli/init/agents.d.ts.map +1 -1
  135. package/dist/cli/init/agents.js +29 -0
  136. package/dist/cli/init/agents.js.map +1 -1
  137. package/dist/cli/init/skills.d.ts.map +1 -1
  138. package/dist/cli/init/skills.js +7 -1
  139. package/dist/cli/init/skills.js.map +1 -1
  140. package/dist/core/memory/HNSWVectorMemory.js +1 -1
  141. package/dist/core/memory/RuVectorPatternStore.d.ts +90 -0
  142. package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -1
  143. package/dist/core/memory/RuVectorPatternStore.js +209 -0
  144. package/dist/core/memory/RuVectorPatternStore.js.map +1 -1
  145. package/dist/learning/FederatedManager.d.ts +232 -0
  146. package/dist/learning/FederatedManager.d.ts.map +1 -0
  147. package/dist/learning/FederatedManager.js +489 -0
  148. package/dist/learning/FederatedManager.js.map +1 -0
  149. package/dist/learning/HNSWPatternAdapter.d.ts +117 -0
  150. package/dist/learning/HNSWPatternAdapter.d.ts.map +1 -0
  151. package/dist/learning/HNSWPatternAdapter.js +262 -0
  152. package/dist/learning/HNSWPatternAdapter.js.map +1 -0
  153. package/dist/learning/LearningEngine.d.ts +27 -0
  154. package/dist/learning/LearningEngine.d.ts.map +1 -1
  155. package/dist/learning/LearningEngine.js +75 -1
  156. package/dist/learning/LearningEngine.js.map +1 -1
  157. package/dist/learning/PatternCurator.d.ts +217 -0
  158. package/dist/learning/PatternCurator.d.ts.map +1 -0
  159. package/dist/learning/PatternCurator.js +393 -0
  160. package/dist/learning/PatternCurator.js.map +1 -0
  161. package/dist/learning/index.d.ts +6 -0
  162. package/dist/learning/index.d.ts.map +1 -1
  163. package/dist/learning/index.js +16 -1
  164. package/dist/learning/index.js.map +1 -1
  165. package/dist/learning/types.d.ts +4 -0
  166. package/dist/learning/types.d.ts.map +1 -1
  167. package/dist/mcp/server-instructions.d.ts +1 -1
  168. package/dist/mcp/server-instructions.js +1 -1
  169. package/dist/memory/HNSWPatternStore.d.ts +176 -0
  170. package/dist/memory/HNSWPatternStore.d.ts.map +1 -0
  171. package/dist/memory/HNSWPatternStore.js +392 -0
  172. package/dist/memory/HNSWPatternStore.js.map +1 -0
  173. package/dist/memory/index.d.ts +8 -0
  174. package/dist/memory/index.d.ts.map +1 -0
  175. package/dist/memory/index.js +13 -0
  176. package/dist/memory/index.js.map +1 -0
  177. package/dist/providers/HybridRouter.d.ts +85 -4
  178. package/dist/providers/HybridRouter.d.ts.map +1 -1
  179. package/dist/providers/HybridRouter.js +332 -10
  180. package/dist/providers/HybridRouter.js.map +1 -1
  181. package/dist/providers/LLMBaselineTracker.d.ts +120 -0
  182. package/dist/providers/LLMBaselineTracker.d.ts.map +1 -0
  183. package/dist/providers/LLMBaselineTracker.js +305 -0
  184. package/dist/providers/LLMBaselineTracker.js.map +1 -0
  185. package/dist/providers/OpenRouterProvider.d.ts +26 -0
  186. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  187. package/dist/providers/OpenRouterProvider.js +75 -6
  188. package/dist/providers/OpenRouterProvider.js.map +1 -1
  189. package/dist/providers/RuVectorClient.d.ts +259 -0
  190. package/dist/providers/RuVectorClient.d.ts.map +1 -0
  191. package/dist/providers/RuVectorClient.js +416 -0
  192. package/dist/providers/RuVectorClient.js.map +1 -0
  193. package/dist/providers/RuvllmPatternCurator.d.ts +116 -0
  194. package/dist/providers/RuvllmPatternCurator.d.ts.map +1 -0
  195. package/dist/providers/RuvllmPatternCurator.js +323 -0
  196. package/dist/providers/RuvllmPatternCurator.js.map +1 -0
  197. package/dist/providers/RuvllmProvider.d.ts +233 -1
  198. package/dist/providers/RuvllmProvider.d.ts.map +1 -1
  199. package/dist/providers/RuvllmProvider.js +781 -11
  200. package/dist/providers/RuvllmProvider.js.map +1 -1
  201. package/dist/providers/index.d.ts +5 -1
  202. package/dist/providers/index.d.ts.map +1 -1
  203. package/dist/providers/index.js +12 -2
  204. package/dist/providers/index.js.map +1 -1
  205. package/dist/utils/ruvllm-loader.d.ts +98 -1
  206. package/dist/utils/ruvllm-loader.d.ts.map +1 -1
  207. package/dist/utils/ruvllm-loader.js.map +1 -1
  208. package/docs/reference/agents.md +91 -2
  209. package/docs/reference/skills.md +97 -2
  210. package/package.json +2 -2
@@ -0,0 +1,952 @@
1
+ "use strict";
2
+ /**
3
+ * N8nReplayabilityTesterAgent
4
+ *
5
+ * Determinism and replayability testing for n8n workflows:
6
+ * - Fixed timestamps and stable IDs
7
+ * - Consistent pagination handling
8
+ * - Controlled randomness detection
9
+ * - Execution replay from fixtures
10
+ * - Snapshot comparison testing
11
+ * - Idempotent execution verification
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.N8nReplayabilityTesterAgent = void 0;
15
+ const N8nBaseAgent_1 = require("./N8nBaseAgent");
16
+ const N8nTestHarness_1 = require("./N8nTestHarness");
17
+ // ============================================================================
18
+ // Non-Determinism Patterns
19
+ // ============================================================================
20
+ const NON_DETERMINISTIC_PATTERNS = {
21
+ // Timestamp-related patterns
22
+ timestamps: [
23
+ /Date\.now\(\)/,
24
+ /new Date\(\)/,
25
+ /\$now/,
26
+ /\$today/,
27
+ /timestamp/i,
28
+ /createdAt/i,
29
+ /updatedAt/i,
30
+ /\{\{\s*\$now\s*\}\}/,
31
+ ],
32
+ // Random value patterns
33
+ randomness: [
34
+ /Math\.random\(\)/,
35
+ /uuid/i,
36
+ /guid/i,
37
+ /nanoid/i,
38
+ /\$randomInt/,
39
+ /\$randomString/,
40
+ ],
41
+ // Unstable ID patterns
42
+ unstableIds: [
43
+ /execution\.id/,
44
+ /\$execution\.id/,
45
+ /runId/i,
46
+ /sessionId/i,
47
+ /requestId/i,
48
+ ],
49
+ // External state patterns
50
+ externalState: [
51
+ /process\.env/,
52
+ /\$env\./,
53
+ /\$vars\./,
54
+ ],
55
+ };
56
+ // Node types known to be non-deterministic
57
+ const NON_DETERMINISTIC_NODE_TYPES = [
58
+ 'n8n-nodes-base.httpRequest', // External API calls
59
+ 'n8n-nodes-base.executeCommand', // Shell commands
60
+ 'n8n-nodes-base.function', // Custom code
61
+ 'n8n-nodes-base.code', // Custom code
62
+ 'n8n-nodes-base.crypto', // Random generation
63
+ ];
64
+ // ============================================================================
65
+ // Agent Implementation
66
+ // ============================================================================
67
+ class N8nReplayabilityTesterAgent extends N8nBaseAgent_1.N8nBaseAgent {
68
+ constructor(config) {
69
+ const capabilities = [
70
+ {
71
+ name: 'determinism-testing',
72
+ version: '1.0.0',
73
+ description: 'Test workflow determinism across multiple runs',
74
+ parameters: {},
75
+ },
76
+ {
77
+ name: 'execution-replay',
78
+ version: '1.0.0',
79
+ description: 'Replay workflows from recorded fixtures',
80
+ parameters: {},
81
+ },
82
+ {
83
+ name: 'snapshot-comparison',
84
+ version: '1.0.0',
85
+ description: 'Compare execution snapshots for consistency',
86
+ parameters: {},
87
+ },
88
+ {
89
+ name: 'fixture-recording',
90
+ version: '1.0.0',
91
+ description: 'Record execution fixtures for replay testing',
92
+ parameters: {},
93
+ },
94
+ {
95
+ name: 'fixture-injection',
96
+ version: '1.0.0',
97
+ description: 'Inject fixtures into workflow for deterministic execution',
98
+ parameters: {},
99
+ },
100
+ {
101
+ name: 'service-mocking',
102
+ version: '1.0.0',
103
+ description: 'Mock external service calls with recorded responses',
104
+ parameters: {},
105
+ },
106
+ {
107
+ name: 'time-freezing',
108
+ version: '1.0.0',
109
+ description: 'Freeze time for deterministic timestamp operations',
110
+ parameters: {},
111
+ },
112
+ ];
113
+ super({
114
+ ...config,
115
+ type: 'n8n-replayability-tester',
116
+ capabilities: [...capabilities, ...(config.capabilities || [])],
117
+ });
118
+ this.fixtureStore = new Map();
119
+ }
120
+ async performTask(task) {
121
+ const replayTask = task;
122
+ if (replayTask.type !== 'replayability-test') {
123
+ throw new Error(`Unsupported task type: ${replayTask.type}`);
124
+ }
125
+ return this.testReplayability(replayTask.target, replayTask.options);
126
+ }
127
+ /**
128
+ * Test workflow replayability
129
+ */
130
+ async testReplayability(workflowId, options, providedWorkflow) {
131
+ const workflow = providedWorkflow || await this.getWorkflow(workflowId);
132
+ const issues = [];
133
+ const nonDeterministicNodes = [];
134
+ const recommendations = [];
135
+ let comparisonResults;
136
+ let recordedFixtures;
137
+ // 1. Static analysis for non-deterministic patterns
138
+ const staticIssues = this.analyzeStaticNonDeterminism(workflow);
139
+ issues.push(...staticIssues);
140
+ // 2. Check node types for known non-determinism
141
+ const nodeTypeIssues = this.checkNodeTypes(workflow);
142
+ issues.push(...nodeTypeIssues);
143
+ // 3. Run determinism check if requested
144
+ if (options?.checkDeterminism) {
145
+ const iterations = options.iterations || 3;
146
+ const determinismResult = await this.checkDeterminism(workflow, iterations);
147
+ nonDeterministicNodes.push(...determinismResult.nonDeterministicNodes);
148
+ issues.push(...determinismResult.issues);
149
+ }
150
+ // 4. Replay from fixture if provided
151
+ if (options?.replayExecutionId || options?.fixtures) {
152
+ const fixtures = options.fixtures || await this.loadFixtures(workflowId, options.replayExecutionId);
153
+ comparisonResults = await this.replayAndCompare(workflow, fixtures);
154
+ const replayIssues = this.analyzeReplayResults(comparisonResults);
155
+ issues.push(...replayIssues);
156
+ }
157
+ // 5. Record fixture if in record mode
158
+ if (options?.recordMode) {
159
+ recordedFixtures = await this.recordFixture(workflow);
160
+ this.storeFixtures(workflowId, recordedFixtures);
161
+ }
162
+ // NEW 6. Execute with mocking if requested
163
+ let mockedExecutionResult;
164
+ if (options?.injectFixtures || options?.mockExternalServices || options?.mockConfigs) {
165
+ mockedExecutionResult = await this.executeWithMocking(workflowId, workflow, options.fixtures || [], options.mockConfigs || [], options.mockExternalServices || false, options.freezeTime);
166
+ // Add issues from mocked execution
167
+ if (!mockedExecutionResult.deterministic) {
168
+ issues.push({
169
+ type: 'non-deterministic',
170
+ severity: 'high',
171
+ node: 'workflow',
172
+ message: 'Workflow produced non-deterministic output even with mocking',
173
+ suggestion: 'Review mocking configuration - some non-determinism may be internal',
174
+ });
175
+ }
176
+ }
177
+ // Generate recommendations
178
+ recommendations.push(...this.generateRecommendations(issues, nonDeterministicNodes));
179
+ // Calculate scores
180
+ const determinismScore = this.calculateDeterminismScore(nonDeterministicNodes, workflow.nodes.length);
181
+ const replayabilityScore = this.calculateReplayabilityScore(issues);
182
+ const score = Math.round((determinismScore + replayabilityScore) / 2);
183
+ const result = {
184
+ workflowId: workflow.id || workflowId,
185
+ workflowName: workflow.name,
186
+ testDate: new Date().toISOString(),
187
+ passed: issues.filter(i => i.severity === 'critical' || i.severity === 'high').length === 0,
188
+ score,
189
+ determinismScore,
190
+ replayabilityScore,
191
+ issues,
192
+ nonDeterministicNodes,
193
+ recommendations,
194
+ fixtures: recordedFixtures,
195
+ comparisonResults,
196
+ mockedExecutionResult,
197
+ };
198
+ // Store result
199
+ await this.storeTestResult(`replayability-test:${workflowId}`, result);
200
+ // Emit event
201
+ this.emitEvent('replayability.test.completed', {
202
+ workflowId,
203
+ passed: result.passed,
204
+ determinismScore,
205
+ replayabilityScore,
206
+ issueCount: issues.length,
207
+ });
208
+ return result;
209
+ }
210
+ /**
211
+ * Analyze workflow for static non-determinism patterns
212
+ */
213
+ analyzeStaticNonDeterminism(workflow) {
214
+ const issues = [];
215
+ for (const node of workflow.nodes) {
216
+ const nodeJson = JSON.stringify(node.parameters);
217
+ // Check timestamp patterns
218
+ for (const pattern of NON_DETERMINISTIC_PATTERNS.timestamps) {
219
+ if (pattern.test(nodeJson)) {
220
+ issues.push({
221
+ type: 'timestamp-dependent',
222
+ severity: 'medium',
223
+ node: node.name,
224
+ message: `Node uses timestamp-dependent expression`,
225
+ suggestion: 'Use fixed timestamps for testing or pass timestamp as input parameter',
226
+ });
227
+ break;
228
+ }
229
+ }
230
+ // Check randomness patterns
231
+ for (const pattern of NON_DETERMINISTIC_PATTERNS.randomness) {
232
+ if (pattern.test(nodeJson)) {
233
+ issues.push({
234
+ type: 'random-value',
235
+ severity: 'high',
236
+ node: node.name,
237
+ message: `Node uses random value generation`,
238
+ suggestion: 'Seed random generators or use deterministic IDs for testing',
239
+ });
240
+ break;
241
+ }
242
+ }
243
+ // Check unstable ID patterns
244
+ for (const pattern of NON_DETERMINISTIC_PATTERNS.unstableIds) {
245
+ if (pattern.test(nodeJson)) {
246
+ issues.push({
247
+ type: 'unstable-id',
248
+ severity: 'low',
249
+ node: node.name,
250
+ message: `Node uses execution-specific IDs`,
251
+ suggestion: 'Avoid using execution IDs in business logic',
252
+ });
253
+ break;
254
+ }
255
+ }
256
+ // Check external state patterns
257
+ for (const pattern of NON_DETERMINISTIC_PATTERNS.externalState) {
258
+ if (pattern.test(nodeJson)) {
259
+ issues.push({
260
+ type: 'external-state',
261
+ severity: 'medium',
262
+ node: node.name,
263
+ message: `Node depends on external state (environment variables)`,
264
+ suggestion: 'Use consistent environment or mock external state for testing',
265
+ });
266
+ break;
267
+ }
268
+ }
269
+ }
270
+ return issues;
271
+ }
272
+ /**
273
+ * Check node types for known non-determinism
274
+ */
275
+ checkNodeTypes(workflow) {
276
+ const issues = [];
277
+ for (const node of workflow.nodes) {
278
+ if (NON_DETERMINISTIC_NODE_TYPES.some(t => node.type.includes(t))) {
279
+ issues.push({
280
+ type: 'external-state',
281
+ severity: 'medium',
282
+ node: node.name,
283
+ message: `Node type "${node.type}" may produce non-deterministic results`,
284
+ suggestion: 'Mock external calls or use recorded responses for testing',
285
+ });
286
+ }
287
+ }
288
+ return issues;
289
+ }
290
+ /**
291
+ * Check determinism by running workflow multiple times
292
+ */
293
+ async checkDeterminism(workflow, iterations) {
294
+ const nonDeterministicNodes = [];
295
+ const issues = [];
296
+ // Collect execution results
297
+ const executionResults = new Map();
298
+ try {
299
+ for (let i = 0; i < iterations; i++) {
300
+ const execution = await this.executeWorkflow(workflow.id, {}, {
301
+ waitForCompletion: true,
302
+ timeout: 30000,
303
+ });
304
+ const runData = execution.data?.resultData?.runData;
305
+ if (runData) {
306
+ for (const [nodeName, nodeRuns] of Object.entries(runData)) {
307
+ const output = nodeRuns[0]?.data?.main?.[0]?.[0]?.json;
308
+ if (!executionResults.has(nodeName)) {
309
+ executionResults.set(nodeName, []);
310
+ }
311
+ executionResults.get(nodeName).push(output);
312
+ }
313
+ }
314
+ }
315
+ // Analyze variance
316
+ for (const [nodeName, outputs] of executionResults.entries()) {
317
+ const variance = this.calculateOutputVariance(outputs);
318
+ if (variance > 0) {
319
+ const node = workflow.nodes.find(n => n.name === nodeName);
320
+ nonDeterministicNodes.push({
321
+ nodeName,
322
+ nodeType: node?.type || 'unknown',
323
+ reason: 'Output varies between runs',
324
+ variance,
325
+ examples: outputs.slice(0, 3).map((value, i) => ({ run: i + 1, value })),
326
+ });
327
+ if (variance > 0.5) {
328
+ issues.push({
329
+ type: 'non-deterministic',
330
+ severity: 'high',
331
+ node: nodeName,
332
+ message: `Node output varies significantly between runs (${Math.round(variance * 100)}% variance)`,
333
+ suggestion: 'Review node configuration and mock external dependencies',
334
+ });
335
+ }
336
+ }
337
+ }
338
+ }
339
+ catch (error) {
340
+ issues.push({
341
+ type: 'non-deterministic',
342
+ severity: 'medium',
343
+ node: 'workflow',
344
+ message: `Could not complete determinism check: ${error.message}`,
345
+ suggestion: 'Ensure workflow can be executed multiple times',
346
+ });
347
+ }
348
+ return { nonDeterministicNodes, issues };
349
+ }
350
+ /**
351
+ * Calculate output variance between runs
352
+ */
353
+ calculateOutputVariance(outputs) {
354
+ if (outputs.length < 2)
355
+ return 0;
356
+ let differences = 0;
357
+ const firstOutput = JSON.stringify(outputs[0]);
358
+ for (let i = 1; i < outputs.length; i++) {
359
+ if (JSON.stringify(outputs[i]) !== firstOutput) {
360
+ differences++;
361
+ }
362
+ }
363
+ return differences / (outputs.length - 1);
364
+ }
365
+ /**
366
+ * Replay workflow and compare with fixtures
367
+ */
368
+ async replayAndCompare(workflow, fixtures) {
369
+ const comparisons = [];
370
+ for (const fixture of fixtures) {
371
+ try {
372
+ const execution = await this.executeWorkflow(workflow.id, fixture.inputData, {
373
+ waitForCompletion: true,
374
+ timeout: 60000,
375
+ });
376
+ const runData = execution.data?.resultData?.runData;
377
+ if (runData) {
378
+ for (const [nodeName, expectedSnapshot] of Object.entries(fixture.nodeSnapshots)) {
379
+ const actualOutput = runData[nodeName]?.[0]?.data?.main?.[0]?.[0]?.json;
380
+ const differences = this.compareValues(expectedSnapshot.outputData, actualOutput, '');
381
+ comparisons.push({
382
+ nodeName,
383
+ matched: differences.length === 0,
384
+ expectedOutput: expectedSnapshot.outputData,
385
+ actualOutput,
386
+ differences,
387
+ });
388
+ }
389
+ }
390
+ }
391
+ catch (error) {
392
+ // Add comparison failure
393
+ for (const nodeName of Object.keys(fixture.nodeSnapshots)) {
394
+ comparisons.push({
395
+ nodeName,
396
+ matched: false,
397
+ expectedOutput: fixture.nodeSnapshots[nodeName].outputData,
398
+ actualOutput: null,
399
+ differences: [{
400
+ path: '',
401
+ expected: 'execution',
402
+ actual: 'error',
403
+ type: 'changed',
404
+ }],
405
+ });
406
+ }
407
+ }
408
+ }
409
+ return comparisons;
410
+ }
411
+ /**
412
+ * Compare two values and find differences
413
+ */
414
+ compareValues(expected, actual, path) {
415
+ const differences = [];
416
+ // Null/undefined handling
417
+ if (expected === null || expected === undefined) {
418
+ if (actual !== expected) {
419
+ differences.push({ path, expected, actual, type: 'changed' });
420
+ }
421
+ return differences;
422
+ }
423
+ if (actual === null || actual === undefined) {
424
+ differences.push({ path, expected, actual, type: 'missing' });
425
+ return differences;
426
+ }
427
+ // Type check
428
+ if (typeof expected !== typeof actual) {
429
+ differences.push({ path, expected, actual, type: 'type-mismatch' });
430
+ return differences;
431
+ }
432
+ // Array comparison
433
+ if (Array.isArray(expected)) {
434
+ if (!Array.isArray(actual)) {
435
+ differences.push({ path, expected, actual, type: 'type-mismatch' });
436
+ return differences;
437
+ }
438
+ const maxLen = Math.max(expected.length, actual.length);
439
+ for (let i = 0; i < maxLen; i++) {
440
+ const itemPath = `${path}[${i}]`;
441
+ if (i >= expected.length) {
442
+ differences.push({ path: itemPath, expected: undefined, actual: actual[i], type: 'extra' });
443
+ }
444
+ else if (i >= actual.length) {
445
+ differences.push({ path: itemPath, expected: expected[i], actual: undefined, type: 'missing' });
446
+ }
447
+ else {
448
+ differences.push(...this.compareValues(expected[i], actual[i], itemPath));
449
+ }
450
+ }
451
+ return differences;
452
+ }
453
+ // Object comparison
454
+ if (typeof expected === 'object') {
455
+ const expectedObj = expected;
456
+ const actualObj = actual;
457
+ const allKeys = new Set([...Object.keys(expectedObj), ...Object.keys(actualObj)]);
458
+ for (const key of allKeys) {
459
+ const keyPath = path ? `${path}.${key}` : key;
460
+ if (!(key in expectedObj)) {
461
+ differences.push({ path: keyPath, expected: undefined, actual: actualObj[key], type: 'extra' });
462
+ }
463
+ else if (!(key in actualObj)) {
464
+ differences.push({ path: keyPath, expected: expectedObj[key], actual: undefined, type: 'missing' });
465
+ }
466
+ else {
467
+ differences.push(...this.compareValues(expectedObj[key], actualObj[key], keyPath));
468
+ }
469
+ }
470
+ return differences;
471
+ }
472
+ // Primitive comparison
473
+ if (expected !== actual) {
474
+ differences.push({ path, expected, actual, type: 'changed' });
475
+ }
476
+ return differences;
477
+ }
478
+ /**
479
+ * Record execution as fixture
480
+ */
481
+ async recordFixture(workflow) {
482
+ const fixtures = [];
483
+ try {
484
+ const execution = await this.executeWorkflow(workflow.id, {}, {
485
+ waitForCompletion: true,
486
+ timeout: 60000,
487
+ });
488
+ const nodeSnapshots = {};
489
+ const runData = execution.data?.resultData?.runData;
490
+ if (runData) {
491
+ for (const [nodeName, nodeRuns] of Object.entries(runData)) {
492
+ const run = nodeRuns[0];
493
+ if (run) {
494
+ nodeSnapshots[nodeName] = {
495
+ nodeName,
496
+ inputData: run.source?.[0] ? runData[run.source[0].previousNode]?.[0]?.data?.main?.[0]?.[0]?.json : null,
497
+ outputData: run.data?.main?.[0]?.[0]?.json,
498
+ executionTime: run.executionTime,
499
+ status: run.executionStatus,
500
+ };
501
+ }
502
+ }
503
+ }
504
+ fixtures.push({
505
+ id: `fixture-${Date.now()}`,
506
+ name: `${workflow.name} - Auto-recorded`,
507
+ inputData: {},
508
+ expectedOutput: nodeSnapshots[Object.keys(nodeSnapshots).pop() || '']?.outputData || {},
509
+ nodeSnapshots,
510
+ metadata: {
511
+ recordedAt: new Date().toISOString(),
512
+ workflowVersion: workflow.versionId,
513
+ },
514
+ });
515
+ }
516
+ catch (error) {
517
+ console.warn('Failed to record fixture:', error);
518
+ }
519
+ return fixtures;
520
+ }
521
+ /**
522
+ * Store fixtures for later use
523
+ */
524
+ storeFixtures(workflowId, fixtures) {
525
+ const existing = this.fixtureStore.get(workflowId) || [];
526
+ this.fixtureStore.set(workflowId, [...existing, ...fixtures]);
527
+ }
528
+ /**
529
+ * Load fixtures from store or execution history
530
+ */
531
+ async loadFixtures(workflowId, executionId) {
532
+ if (this.fixtureStore.has(workflowId)) {
533
+ const fixtures = this.fixtureStore.get(workflowId);
534
+ if (executionId) {
535
+ return fixtures.filter(f => f.id === executionId);
536
+ }
537
+ return fixtures;
538
+ }
539
+ return [];
540
+ }
541
+ /**
542
+ * Analyze replay results for issues
543
+ */
544
+ analyzeReplayResults(comparisons) {
545
+ const issues = [];
546
+ for (const comparison of comparisons) {
547
+ if (!comparison.matched) {
548
+ const severity = comparison.differences.length > 5 ? 'high' : 'medium';
549
+ issues.push({
550
+ type: 'non-deterministic',
551
+ severity,
552
+ node: comparison.nodeName,
553
+ message: `Node output differs from recorded fixture (${comparison.differences.length} differences)`,
554
+ suggestion: 'Review what changed - may indicate drift or non-determinism',
555
+ });
556
+ }
557
+ }
558
+ return issues;
559
+ }
560
+ /**
561
+ * Calculate determinism score
562
+ */
563
+ calculateDeterminismScore(nonDeterministicNodes, totalNodes) {
564
+ if (totalNodes === 0)
565
+ return 100;
566
+ const deterministicNodes = totalNodes - nonDeterministicNodes.length;
567
+ return Math.round((deterministicNodes / totalNodes) * 100);
568
+ }
569
+ /**
570
+ * Calculate replayability score
571
+ */
572
+ calculateReplayabilityScore(issues) {
573
+ const criticalCount = issues.filter(i => i.severity === 'critical').length;
574
+ const highCount = issues.filter(i => i.severity === 'high').length;
575
+ const mediumCount = issues.filter(i => i.severity === 'medium').length;
576
+ return Math.max(0, 100 - (criticalCount * 25) - (highCount * 15) - (mediumCount * 5));
577
+ }
578
+ /**
579
+ * Generate recommendations
580
+ */
581
+ generateRecommendations(issues, nonDeterministicNodes) {
582
+ const recommendations = [];
583
+ if (nonDeterministicNodes.length > 0) {
584
+ recommendations.push(`${nonDeterministicNodes.length} nodes produce varying outputs - consider mocking external dependencies`);
585
+ }
586
+ const timestampIssues = issues.filter(i => i.type === 'timestamp-dependent');
587
+ if (timestampIssues.length > 0) {
588
+ recommendations.push('Use fixed timestamps (e.g., $input.timestamp) instead of $now for reproducible tests');
589
+ }
590
+ const randomIssues = issues.filter(i => i.type === 'random-value');
591
+ if (randomIssues.length > 0) {
592
+ recommendations.push('Seed random generators or use deterministic ID generation for testing');
593
+ }
594
+ const externalStateIssues = issues.filter(i => i.type === 'external-state');
595
+ if (externalStateIssues.length > 0) {
596
+ recommendations.push('Create a test environment with consistent external state for replay testing');
597
+ }
598
+ return recommendations;
599
+ }
600
+ /**
601
+ * Quick replayability check
602
+ */
603
+ async quickCheck(workflowId) {
604
+ const result = await this.testReplayability(workflowId, {
605
+ checkDeterminism: false, // Skip actual execution for quick check
606
+ });
607
+ return {
608
+ replayable: result.passed,
609
+ determinismScore: result.determinismScore,
610
+ topIssue: result.issues[0]?.message || null,
611
+ };
612
+ }
613
+ // ============================================================================
614
+ // Active Fixture Injection & Service Mocking
615
+ // ============================================================================
616
+ /**
617
+ * Execute workflow with mocked nodes and time freezing
618
+ */
619
+ async executeWithMocking(workflowId, workflow, fixtures, mockConfigs, mockAllExternal, freezeTime) {
620
+ const startTime = Date.now();
621
+ const harness = new N8nTestHarness_1.N8nTestHarness(this.n8nConfig);
622
+ const mockedNodes = [];
623
+ const mockedCalls = [];
624
+ try {
625
+ // Build mock configurations
626
+ const mocks = this.buildMockConfigurations(workflow, fixtures, mockConfigs, mockAllExternal);
627
+ mockedNodes.push(...mocks.map(m => m.targetNode));
628
+ // Create mocked workflow
629
+ const { workflow: mockedWorkflow, cleanup } = await harness.createMockedWorkflow(workflowId, mocks);
630
+ try {
631
+ // Execute with optional time simulation
632
+ let execution;
633
+ if (freezeTime) {
634
+ const timeConfig = {
635
+ freezeTime,
636
+ mockDateNodes: true,
637
+ };
638
+ execution = await harness.executeWithTimeSimulation(mockedWorkflow.id, timeConfig, {});
639
+ }
640
+ else {
641
+ // Activate and execute
642
+ await this.n8nClient.activateWorkflow(mockedWorkflow.id);
643
+ const exec = await this.n8nClient.executeWorkflow(mockedWorkflow.id, {});
644
+ execution = {
645
+ originalWorkflowId: workflowId,
646
+ testWorkflowId: mockedWorkflow.id,
647
+ execution: exec,
648
+ cleanedUp: false,
649
+ };
650
+ }
651
+ // Analyze results
652
+ const deterministic = await this.checkMockedDeterminism(harness, mockedWorkflow.id, mocks, freezeTime);
653
+ // Check if output matches expected (from fixtures)
654
+ const outputMatchesExpected = this.checkOutputMatchesExpected(execution.execution, fixtures);
655
+ // Record mocked call info
656
+ for (const mock of mocks) {
657
+ mockedCalls.push({
658
+ nodeName: mock.targetNode,
659
+ originalType: this.getOriginalNodeType(workflow, mock.targetNode),
660
+ mockResponseUsed: true,
661
+ executionTime: 0, // Would need detailed execution timing
662
+ });
663
+ }
664
+ const summary = this.generateMockedExecutionSummary(mockedNodes.length, deterministic, outputMatchesExpected, freezeTime, Date.now() - startTime);
665
+ // Emit event
666
+ this.emitEvent('replayability.mocked-execution.completed', {
667
+ workflowId,
668
+ mockedNodes: mockedNodes.length,
669
+ deterministic,
670
+ outputMatchesExpected,
671
+ });
672
+ return {
673
+ executed: true,
674
+ mockedNodes,
675
+ frozenTime: freezeTime?.toISOString(),
676
+ deterministic,
677
+ outputMatchesExpected,
678
+ executionTime: Date.now() - startTime,
679
+ mockedCalls,
680
+ summary,
681
+ };
682
+ }
683
+ finally {
684
+ await cleanup();
685
+ }
686
+ }
687
+ catch (error) {
688
+ return {
689
+ executed: false,
690
+ mockedNodes,
691
+ deterministic: false,
692
+ outputMatchesExpected: false,
693
+ executionTime: Date.now() - startTime,
694
+ mockedCalls,
695
+ summary: `Mocked execution failed: ${error.message}`,
696
+ };
697
+ }
698
+ finally {
699
+ await harness.cleanup();
700
+ }
701
+ }
702
+ /**
703
+ * Build mock configurations from fixtures and explicit configs
704
+ */
705
+ buildMockConfigurations(workflow, fixtures, explicitMocks, mockAllExternal) {
706
+ const mocks = [];
707
+ // Add explicit mocks
708
+ for (const mock of explicitMocks) {
709
+ mocks.push({
710
+ targetNode: mock.nodeName,
711
+ mockResponse: mock.mockResponse,
712
+ statusCode: mock.statusCode,
713
+ headers: mock.headers,
714
+ delay: mock.delay,
715
+ });
716
+ }
717
+ // Add mocks from fixtures
718
+ for (const fixture of fixtures) {
719
+ for (const [nodeName, snapshot] of Object.entries(fixture.nodeSnapshots)) {
720
+ // Don't double-mock
721
+ if (mocks.some(m => m.targetNode === nodeName))
722
+ continue;
723
+ mocks.push({
724
+ targetNode: nodeName,
725
+ mockResponse: snapshot.outputData,
726
+ });
727
+ }
728
+ }
729
+ // Auto-mock all external nodes if requested
730
+ if (mockAllExternal) {
731
+ for (const node of workflow.nodes) {
732
+ // Skip if already mocked
733
+ if (mocks.some(m => m.targetNode === node.name))
734
+ continue;
735
+ // Mock HTTP requests and other external nodes
736
+ if (NON_DETERMINISTIC_NODE_TYPES.some(t => node.type.includes(t))) {
737
+ // Look for fixture data first
738
+ const fixtureData = this.findFixtureDataForNode(fixtures, node.name);
739
+ mocks.push({
740
+ targetNode: node.name,
741
+ mockResponse: fixtureData || this.generateDefaultMock(node),
742
+ });
743
+ }
744
+ }
745
+ }
746
+ return mocks;
747
+ }
748
+ /**
749
+ * Find fixture data for a specific node
750
+ */
751
+ findFixtureDataForNode(fixtures, nodeName) {
752
+ for (const fixture of fixtures) {
753
+ const snapshot = fixture.nodeSnapshots[nodeName];
754
+ if (snapshot) {
755
+ return snapshot.outputData;
756
+ }
757
+ }
758
+ return null;
759
+ }
760
+ /**
761
+ * Generate a default mock response for a node
762
+ */
763
+ generateDefaultMock(node) {
764
+ const nodeType = node.type.toLowerCase();
765
+ if (nodeType.includes('httprequest')) {
766
+ return {
767
+ __mocked: true,
768
+ status: 200,
769
+ data: {},
770
+ message: 'Mocked HTTP response',
771
+ };
772
+ }
773
+ if (nodeType.includes('executecommand')) {
774
+ return {
775
+ __mocked: true,
776
+ stdout: '',
777
+ stderr: '',
778
+ exitCode: 0,
779
+ };
780
+ }
781
+ return {
782
+ __mocked: true,
783
+ data: null,
784
+ };
785
+ }
786
+ /**
787
+ * Get original node type before mocking
788
+ */
789
+ getOriginalNodeType(workflow, nodeName) {
790
+ const node = workflow.nodes.find(n => n.name === nodeName);
791
+ return node?.type || 'unknown';
792
+ }
793
+ /**
794
+ * Check if mocked execution is deterministic by running twice
795
+ */
796
+ async checkMockedDeterminism(harness, workflowId, mocks, freezeTime) {
797
+ try {
798
+ // Execute twice with same mocks
799
+ const config = {
800
+ concurrency: 2,
801
+ staggerMs: 100,
802
+ timeout: 30000,
803
+ };
804
+ const result = await harness.executeConcurrently(workflowId, config);
805
+ // Check if outputs are identical
806
+ return result.allIdentical;
807
+ }
808
+ catch {
809
+ // If we can't verify, assume not deterministic
810
+ return false;
811
+ }
812
+ }
813
+ /**
814
+ * Check if execution output matches fixture expectations
815
+ */
816
+ checkOutputMatchesExpected(execution, fixtures) {
817
+ if (!execution || fixtures.length === 0) {
818
+ return true; // No comparison to make
819
+ }
820
+ const runData = execution.data?.resultData?.runData;
821
+ if (!runData)
822
+ return false;
823
+ for (const fixture of fixtures) {
824
+ // Get final output
825
+ const nodeNames = Object.keys(runData);
826
+ const lastNode = nodeNames[nodeNames.length - 1];
827
+ const actualOutput = runData[lastNode]?.[0]?.data?.main?.[0]?.[0]?.json;
828
+ // Compare with expected
829
+ const differences = this.compareValues(fixture.expectedOutput, actualOutput, '');
830
+ // Allow for some differences (timestamps, IDs that may change)
831
+ const significantDifferences = differences.filter(d => !this.isAllowedDifference(d.path));
832
+ if (significantDifferences.length > 0) {
833
+ return false;
834
+ }
835
+ }
836
+ return true;
837
+ }
838
+ /**
839
+ * Check if a difference is allowed (timestamps, IDs, etc)
840
+ */
841
+ isAllowedDifference(path) {
842
+ const allowedPatterns = [
843
+ /timestamp/i,
844
+ /createdAt/i,
845
+ /updatedAt/i,
846
+ /id$/i,
847
+ /executionId/i,
848
+ /requestId/i,
849
+ /sessionId/i,
850
+ ];
851
+ return allowedPatterns.some(pattern => pattern.test(path));
852
+ }
853
+ /**
854
+ * Generate summary of mocked execution
855
+ */
856
+ generateMockedExecutionSummary(mockedCount, deterministic, outputMatches, freezeTime, executionTime) {
857
+ const parts = [];
858
+ parts.push(`Executed with ${mockedCount} mocked node(s)`);
859
+ if (freezeTime) {
860
+ parts.push(`Time frozen to ${freezeTime.toISOString()}`);
861
+ }
862
+ if (deterministic) {
863
+ parts.push('✓ Execution is deterministic');
864
+ }
865
+ else {
866
+ parts.push('⚠️ Execution shows non-deterministic behavior');
867
+ }
868
+ if (outputMatches) {
869
+ parts.push('✓ Output matches expected fixtures');
870
+ }
871
+ else {
872
+ parts.push('⚠️ Output differs from expected fixtures');
873
+ }
874
+ if (executionTime) {
875
+ parts.push(`Completed in ${executionTime}ms`);
876
+ }
877
+ return parts.join('. ');
878
+ }
879
+ /**
880
+ * Record and store fixtures with mocking support
881
+ */
882
+ async recordFixtureWithMocking(workflowId, inputData = {}) {
883
+ const workflow = await this.getWorkflow(workflowId);
884
+ const execution = await this.executeWorkflow(workflowId, inputData, {
885
+ waitForCompletion: true,
886
+ timeout: 60000,
887
+ });
888
+ const nodeSnapshots = {};
889
+ const runData = execution.data?.resultData?.runData;
890
+ if (runData) {
891
+ for (const [nodeName, nodeRuns] of Object.entries(runData)) {
892
+ const run = nodeRuns[0];
893
+ if (run) {
894
+ nodeSnapshots[nodeName] = {
895
+ nodeName,
896
+ inputData: run.source?.[0] ? runData[run.source[0].previousNode]?.[0]?.data?.main?.[0]?.[0]?.json : null,
897
+ outputData: run.data?.main?.[0]?.[0]?.json,
898
+ executionTime: run.executionTime,
899
+ status: run.executionStatus,
900
+ };
901
+ }
902
+ }
903
+ }
904
+ const fixture = {
905
+ id: `fixture-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
906
+ name: `${workflow.name} - ${new Date().toISOString()}`,
907
+ inputData,
908
+ expectedOutput: nodeSnapshots[Object.keys(nodeSnapshots).pop() || '']?.outputData || {},
909
+ nodeSnapshots,
910
+ metadata: {
911
+ recordedAt: new Date().toISOString(),
912
+ workflowVersion: workflow.versionId,
913
+ environment: process.env.NODE_ENV || 'development',
914
+ },
915
+ };
916
+ // Store fixture
917
+ this.storeFixtures(workflowId, [fixture]);
918
+ // Also persist to memory store
919
+ await this.storeTestResult(`fixture:${workflowId}:${fixture.id}`, fixture);
920
+ return fixture;
921
+ }
922
+ /**
923
+ * Load fixtures from persistent storage
924
+ */
925
+ async loadPersistedFixtures(workflowId) {
926
+ const fixtures = [];
927
+ // Load from in-memory store
928
+ const inMemory = this.fixtureStore.get(workflowId) || [];
929
+ fixtures.push(...inMemory);
930
+ // Would also load from persistent storage here
931
+ // const persisted = await this.retrieveTestResult(`fixtures:${workflowId}`);
932
+ return fixtures;
933
+ }
934
+ /**
935
+ * Execute replay test with fixtures from storage
936
+ */
937
+ async executeReplayTest(workflowId, fixtureId) {
938
+ const fixtures = await this.loadPersistedFixtures(workflowId);
939
+ const targetFixtures = fixtureId
940
+ ? fixtures.filter(f => f.id === fixtureId)
941
+ : fixtures;
942
+ return this.testReplayability(workflowId, {
943
+ fixtures: targetFixtures,
944
+ injectFixtures: true,
945
+ mockExternalServices: true,
946
+ checkDeterminism: true,
947
+ iterations: 2,
948
+ });
949
+ }
950
+ }
951
+ exports.N8nReplayabilityTesterAgent = N8nReplayabilityTesterAgent;
952
+ //# sourceMappingURL=N8nReplayabilityTesterAgent.js.map