* Generate a test file from a trace (trace-to-test). * Creates a Jest-compatible test that replays the tool calls.
(traceId)
| 168 | * Creates a Jest-compatible test that replays the tool calls. |
| 169 | */ |
| 170 | generateTest(traceId) { |
| 171 | const trace = this.load(traceId); |
| 172 | if (!trace) return null; |
| 173 | |
| 174 | const toolSteps = trace.steps.filter(s => s.type === 'tool_call'); |
| 175 | if (toolSteps.length === 0) return null; |
| 176 | |
| 177 | const testLines = [ |
| 178 | `// Auto-generated from trace ${trace.id}`, |
| 179 | `// Original prompt: "${trace.prompt.slice(0, 80).replace(/"/g, '\\"')}"`, |
| 180 | `// Model: ${trace.model} | Steps: ${trace.steps.length} | Tokens: ${trace.tokens.prompt + trace.tokens.completion}`, |
| 181 | ``, |
| 182 | `const { execSync } = require('child_process');`, |
| 183 | `const fs = require('fs');`, |
| 184 | `const path = require('path');`, |
| 185 | ``, |
| 186 | `describe('Trace ${trace.id}: ${trace.prompt.slice(0, 40).replace(/'/g, "\\'")}', () => {`, |
| 187 | ]; |
| 188 | |
| 189 | for (let i = 0; i < toolSteps.length; i++) { |
| 190 | const step = toolSteps[i]; |
| 191 | if (step.name === 'write_file' || step.name === 'patch') { |
| 192 | const args = typeof step.args === 'string' ? JSON.parse(step.args) : step.args; |
| 193 | testLines.push(` test('step ${i + 1}: ${step.name} ${(args.path || '').slice(0, 30)}', () => {`); |
| 194 | testLines.push(` // Tool: ${step.name} took ${step.durationMs}ms`); |
| 195 | if (step.name === 'write_file') { |
| 196 | testLines.push(` const filePath = path.resolve('${args.path}');`); |
| 197 | testLines.push(` // Verify file was created/exists after agent run`); |
| 198 | testLines.push(` expect(fs.existsSync(filePath)).toBe(true);`); |
| 199 | } |
| 200 | testLines.push(` });`); |
| 201 | testLines.push(``); |
| 202 | } else if (step.name === 'bash') { |
| 203 | const args = typeof step.args === 'string' ? JSON.parse(step.args) : step.args; |
| 204 | const cmd = String(args.command || ''); |
| 205 | // Use JSON.stringify to escape the command for embedding in a JS |
| 206 | // string literal — the prior `'${...}'` interpolation broke whenever |
| 207 | // the command contained quotes, backticks, or backslashes, and could |
| 208 | // produce invalid (or worse, injectable) test code. |
| 209 | const cmdLiteral = JSON.stringify(cmd); |
| 210 | testLines.push(` test('step ${i + 1}: bash ${cmd.slice(0, 40).replace(/['`\\\r\n]/g, ' ')}', () => {`); |
| 211 | testLines.push(` // Verify command succeeds`); |
| 212 | testLines.push(` const result = execSync(${cmdLiteral}, { encoding: 'utf-8', timeout: 15000 });`); |
| 213 | testLines.push(` expect(result).toBeDefined();`); |
| 214 | testLines.push(` });`); |
| 215 | testLines.push(``); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | testLines.push(`});`); |
| 220 | return testLines.join('\n'); |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | module.exports = { TraceRecorder }; |