diff --git a/apps/sim/lib/workflow-tests/protocol.test.ts b/apps/sim/lib/workflow-tests/protocol.test.ts new file mode 100644 index 00000000000..72b23d6d016 --- /dev/null +++ b/apps/sim/lib/workflow-tests/protocol.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { testReplyArgsSchema } from '@/lib/workflow-tests/protocol' + +describe('testReplyArgsSchema', () => { + it('keeps the error of a mock that threw', () => { + // The sandbox sends no `output` key for a thrown mock; an optional `output` must not claim it. + expect( + testReplyArgsSchema.parse({ runId: 'r', callId: 'c', error: '409 version conflict' }) + ).toEqual({ + runId: 'r', + callId: 'c', + error: '409 version conflict', + }) + }) + + it('never reads an error past the cap as an empty output', () => { + const reply = { runId: 'r', callId: 'c', error: 'x'.repeat(10_001) } + expect(testReplyArgsSchema.safeParse(reply).success).toBe(false) + }) +}) diff --git a/apps/sim/lib/workflow-tests/protocol.ts b/apps/sim/lib/workflow-tests/protocol.ts index c8871149726..b5618e2e870 100644 --- a/apps/sim/lib/workflow-tests/protocol.ts +++ b/apps/sim/lib/workflow-tests/protocol.ts @@ -40,9 +40,17 @@ export const testStartArgsSchema = z.object({ targets: z.array(testTargetSchema).max(MAX_TARGETS), }) +/** + * A mock's answer: its output, or the error it threw. The error branch is tried first, because + * `output: z.unknown()` also accepts an object with no `output`, which would turn a thrown mock + * into an empty output and lose its message. + */ export const testReplyArgsSchema = z.union([ - z.object({ runId: z.string().min(1), callId: z.string().min(1), output: z.unknown() }), z.object({ runId: z.string().min(1), callId: z.string().min(1), error: z.string().max(10_000) }), + // Strict, so a reply carrying an `error` (one past the cap included) is never read as an output. + z + .object({ runId: z.string().min(1), callId: z.string().min(1), output: z.unknown() }) + .strict(), ]) export const testNextArgsSchema = z.object({ runId: z.string().min(1) })