import { describe, expect, it, vi } from "vitest"; vi.mock("~/db.server", () => ({ prisma: {}, $replica: {}, })); import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server"; import type { MollifierBuffer, BufferEntry } from "@trigger.dev/redis-worker"; import { RunId } from "@trigger.dev/core/v3/isomorphic"; function fakeBuffer(entry: BufferEntry | null): MollifierBuffer { return { getEntry: vi.fn(async () => entry), } as unknown as MollifierBuffer; } const NOW = new Date("2026-05-11T12:00:00Z"); describe("findRunByIdWithMollifierFallback", () => { it("returns null when buffer is unavailable (mollifier disabled)", async () => { const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => null } ); expect(result).toBeNull(); }); it("returns null when no buffer entry exists", async () => { const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(null) } ); expect(result).toBeNull(); }); it("returns null when buffer entry envId does not match caller (auth mismatch)", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_OTHER", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).toBeNull(); }); it("returns null when buffer entry orgId does not match caller (auth mismatch)", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_OTHER", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).toBeNull(); }); it("returns synthesised QUEUED run when entry exists with matching auth", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "my-task" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.friendlyId).toBe("run_1"); expect(result!.status).toBe("QUEUED"); expect(result!.taskIdentifier).toBe("my-task"); expect(result!.createdAt).toEqual(NOW); }); it("returns synthesised QUEUED for DRAINING (internal state same externally)", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "DRAINING", attempts: 1, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("QUEUED"); }); it("returns FAILED state with structured error for FAILED entries", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "FAILED", attempts: 3, createdAt: NOW, lastError: { code: "VALIDATION", message: "task not found" }, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("FAILED"); expect(result!.error).toEqual({ code: "VALIDATION", message: "task not found" }); }); it("extracts snapshot-derived fields from the buffered payload", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "my-task", payload: '{"foo":"bar"}', payloadType: "application/json", metadata: '{"customer":"acme"}', metadataType: "application/json", idempotencyKey: "client-abc", idempotencyKeyOptions: { key: "client-abc", scope: "run" }, isTest: true, depth: 2, ttl: "1h", tags: ["tag-a", "tag-b"], // The engine.trigger snapshot stores the locked version string under // `taskVersion` (see triggerTask.server.ts#buildEngineTriggerInput). taskVersion: "20260511.1", resumeParentOnCompletion: false, parentTaskRunId: "run_parent", }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.payloadType).toBe("application/json"); expect(result!.metadata).toBe('{"customer":"acme"}'); expect(result!.metadataType).toBe("application/json"); expect(result!.idempotencyKey).toBe("client-abc"); expect(result!.idempotencyKeyOptions).toEqual({ key: "client-abc", scope: "run" }); expect(result!.isTest).toBe(true); expect(result!.depth).toBe(2); expect(result!.ttl).toBe("1h"); expect(result!.tags).toEqual(["tag-a", "tag-b"]); expect(result!.lockedToVersion).toBe("20260511.1"); expect(result!.resumeParentOnCompletion).toBe(false); expect(result!.parentTaskRunId).toBe("run_parent"); }); it("extracts gate-allocated trace context from the snapshot", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", traceId: "trace_abc", spanId: "span_xyz", parentSpanId: "span_parent", }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.traceId).toBe("trace_abc"); expect(result!.spanId).toBe("span_xyz"); expect(result!.parentSpanId).toBe("span_parent"); }); it("parses idempotencyKeyOptions in the canonical { key, scope } object shape (regression for the buffered-vs-PG API contract divergence)", async () => { // Regression for the bug where `readFallback` parsed // `idempotencyKeyOptions` via Array.isArray and rejected the // canonical object shape. The SDK and Prisma both serialise this // as `{ key, scope }`; the legacy array check would reject it, // returning `undefined` here, which downstream demoted the API's // `idempotencyKey` field to surface the *hash* (server-side // generated) instead of the user-supplied key — diverging from // how materialised runs render the same field, and creating a // silent contract flip at the drainer-materialisation boundary. // Pin the schema-parse path so the buffered response matches // PG-resident behaviour from the moment the run is buffered. const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", idempotencyKey: "", idempotencyKeyOptions: { key: "user-supplied-key", scope: "global" }, }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.idempotencyKeyOptions).toEqual({ key: "user-supplied-key", scope: "global", }); }); it("returns undefined for idempotencyKeyOptions when the snapshot carries a legacy/invalid shape", async () => { // The Zod schema parse rejects: // - array shape (the legacy bug we just fixed) // - object without required fields // - missing field entirely // In all these cases the field is left `undefined`. Downstream // `getUserProvidedIdempotencyKey` then falls back to the // `idempotencyKey` field, matching how PG-resident runs handle // malformed/missing options. const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", idempotencyKey: "", // Legacy array shape — must NOT be accepted. idempotencyKeyOptions: ["payload"], }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.idempotencyKeyOptions).toBeUndefined(); }); it("defaults snapshot-derived fields to safe values when absent", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.payloadType).toBeUndefined(); expect(result!.metadata).toBeUndefined(); expect(result!.idempotencyKey).toBeUndefined(); expect(result!.isTest).toBe(false); expect(result!.depth).toBe(0); expect(result!.tags).toEqual([]); expect(result!.resumeParentOnCompletion).toBe(false); expect(result!.traceId).toBeUndefined(); expect(result!.spanId).toBeUndefined(); }); it("populates replay-relevant fields from the snapshot", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "my-task", environment: { id: "env_a" }, workerQueue: "default", queue: "task/my-task", concurrencyKey: "tenant-42", machine: "medium-1x", realtimeStreamsVersion: "v2", seedMetadata: '{"k":"v"}', seedMetadataType: "application/json", tags: ["t1", "t2"], }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.id).toBeTypeOf("string"); expect(result!.id.length).toBeGreaterThan(0); expect(result!.engine).toBe("V2"); expect(result!.runtimeEnvironmentId).toBe("env_a"); expect(result!.workerQueue).toBe("default"); expect(result!.queue).toBe("task/my-task"); expect(result!.concurrencyKey).toBe("tenant-42"); expect(result!.machinePreset).toBe("medium-1x"); expect(result!.realtimeStreamsVersion).toBe("v2"); expect(result!.seedMetadata).toBe('{"k":"v"}'); expect(result!.seedMetadataType).toBe("application/json"); expect(result!.runTags).toEqual(["t1", "t2"]); }); it("extracts batchId from the snapshot's nested batch object (engine.trigger shape)", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", // The engine.trigger input nests the batch as `{ id, index }`, // where `id` is the batch's internal cuid (not a flat `batchId`). batch: { id: "batch_internal_cuid", index: 3 }, }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBe("batch_internal_cuid"); }); it("leaves batchId undefined when the snapshot has no batch (non-batched run)", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBeUndefined(); }); it("treats invalid date strings as undefined and does not mis-classify status as CANCELED", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", cancelledAt: "not-a-date", cancelReason: "user requested", delayUntil: "also-not-a-date", }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result).not.toBeNull(); expect(result!.status).toBe("QUEUED"); expect(result!.cancelledAt).toBeUndefined(); expect(result!.delayUntil).toBeUndefined(); }); it("parses valid ISO date strings on cancelledAt and delayUntil", async () => { const cancelledAtIso = "2026-05-11T13:00:00.000Z"; const delayUntilIso = "2026-05-11T14:00:00.000Z"; const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", cancelledAt: cancelledAtIso, cancelReason: "user requested", delayUntil: delayUntilIso, }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.status).toBe("CANCELED"); expect(result!.cancelledAt).toEqual(new Date(cancelledAtIso)); expect(result!.cancelReason).toBe("user requested"); expect(result!.delayUntil).toEqual(new Date(delayUntilIso)); }); it("falls back to entry.envId for runtimeEnvironmentId when snapshot lacks environment.id", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.runtimeEnvironmentId).toBe("env_a"); expect(result!.workerQueue).toBeUndefined(); expect(result!.queue).toBeUndefined(); }); it("extracts batchId from the nested snapshot.batch object (not the flat key)", async () => { // Regression for the field-name mismatch Devin flagged: // #buildEngineTriggerInput writes batch info as // `batch: { id, index }`, never as a flat `batchId`. readFallback // must read the nested key, otherwise SyntheticRun.batchId is always // undefined for buffered runs. const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", batch: { id: "batch_internal_xyz", index: 3 }, }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBe("batch_internal_xyz"); }); it("does NOT read a flat `batchId` key — only the nested batch.id", async () => { // Belt-and-braces: a payload with the wrong-shaped flat key should // resolve to undefined, not silently pick up the bogus value. const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", batchId: "should-be-ignored", }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.batchId).toBeUndefined(); }); it("converts internal parent/root IDs in the snapshot to friendlyIds", async () => { // Regression for Devin's structural-unfillable finding: the snapshot // only carries INTERNAL parent/root ids (engine.trigger consumes the // internal shape), while SyntheticRun exposes friendlyIds. Convert // here so consumers don't have to special-case the buffered path. // The conversion is deterministic via RunId.toFriendlyId — we drive // it through `RunId.generate()` to get a matching internal+friendly // pair and assert the round-trip. const parent = RunId.generate(); const root = RunId.generate(); const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t", parentTaskRunId: parent.id, rootTaskRunId: root.id, }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.parentTaskRunFriendlyId).toBe(parent.friendlyId); expect(result!.rootTaskRunFriendlyId).toBe(root.friendlyId); }); it("leaves parent/root friendlyIds undefined when the snapshot carries no parent context", async () => { const entry: BufferEntry = { runId: "run_1", envId: "env_a", orgId: "org_1", payload: JSON.stringify({ taskIdentifier: "t" }), status: "QUEUED", attempts: 0, createdAt: NOW, }; const result = await findRunByIdWithMollifierFallback( { runId: "run_1", environmentId: "env_a", organizationId: "org_1" }, { getBuffer: () => fakeBuffer(entry) } ); expect(result!.parentTaskRunFriendlyId).toBeUndefined(); expect(result!.rootTaskRunFriendlyId).toBeUndefined(); }); });