// Route-level regression for ApiBatchResultsPresenter: the /batches/:id/results route used to build // the presenter with no read-through deps, collapsing to a passthrough read off the control-plane // replica only, which 404s a NEW-resident (run-ops id) batch that lives on the dedicated run-ops DB. import { heteroPostgresTest } from "@internal/testcontainers"; import type { PrismaClient } from "@trigger.dev/database"; import { describe, expect, vi } from "vitest"; import type { PrismaReplicaClient } from "~/db.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { ApiBatchResultsPresenter } from "~/presenters/v3/ApiBatchResultsPresenter.server"; vi.setConfig({ testTimeout: 60_000 }); // 26-char v1 body (version "1" at index 25) → NEW residency. 25-char body → LEGACY residency (cuid analog). function newRunId(c: string) { return c.repeat(24) + "01"; } // A prisma handle that throws on any access — proves the split path never reads the passthrough // handles when the batch resolves off the NEW client. const throwingPrisma = new Proxy( {}, { get(_t, prop) { throw new Error( `passthrough handle must not be touched on the split path (got .${String(prop)})` ); }, } ) as unknown as PrismaReplicaClient; let seedCounter = 0; async function seedEnv(prisma: PrismaClient, slug: string) { const n = seedCounter++; const organization = await prisma.organization.create({ data: { title: `Org ${slug}`, slug: `org-${slug}-${n}` }, }); const project = await prisma.project.create({ data: { name: `Proj ${slug}`, slug: `proj-${slug}-${n}`, organizationId: organization.id, externalRef: `ext-${slug}-${n}`, }, }); const environment = await prisma.runtimeEnvironment.create({ data: { slug: `env-${slug}-${n}`, type: "PRODUCTION", projectId: project.id, organizationId: organization.id, apiKey: `api-${slug}-${n}`, pkApiKey: `pk-${slug}-${n}`, shortcode: `sc-${slug}-${n}`, }, }); return { organization, project, environment }; } type SeedCtx = Awaited>; // Mirror the same org/project/env ids onto the second DB so a passthrough read against the // control-plane replica has the environment row to filter on (but NOT the batch row). async function mirrorEnv(prisma: PrismaClient, ctx: SeedCtx, slug: string) { const n = seedCounter++; await prisma.organization.create({ data: { id: ctx.organization.id, title: `Org ${slug}`, slug: `org-${slug}-m-${n}` }, }); await prisma.project.create({ data: { id: ctx.project.id, name: `Proj ${slug}`, slug: `proj-${slug}-m-${n}`, organizationId: ctx.organization.id, externalRef: `ext-${slug}-m-${n}`, }, }); await prisma.runtimeEnvironment.create({ data: { id: ctx.environment.id, slug: `env-${slug}-m-${n}`, type: "PRODUCTION", projectId: ctx.project.id, organizationId: ctx.organization.id, apiKey: `api-${slug}-m-${n}`, pkApiKey: `pk-${slug}-m-${n}`, shortcode: `sc-${slug}-m-${n}`, }, }); } // Drop the per-DB TaskRunAttempt worker/queue FKs so we can seed an attempt (its output is what the // execution-result carries) without standing up BackgroundWorker/TaskQueue parents. async function relaxFks(prisma: PrismaClient) { for (const sql of [ `ALTER TABLE "TaskRunAttempt" DROP CONSTRAINT IF EXISTS "TaskRunAttempt_backgroundWorkerId_fkey"`, `ALTER TABLE "TaskRunAttempt" DROP CONSTRAINT IF EXISTS "TaskRunAttempt_backgroundWorkerTaskId_fkey"`, `ALTER TABLE "TaskRunAttempt" DROP CONSTRAINT IF EXISTS "TaskRunAttempt_queueId_fkey"`, ]) { await prisma.$executeRawUnsafe(sql); } } async function seedMember( prisma: PrismaClient, ctx: SeedCtx, m: { id: string; friendlyId: string; output: string } ) { const run = await prisma.taskRun.create({ data: { id: m.id, friendlyId: m.friendlyId, taskIdentifier: "my-task", status: "COMPLETED_SUCCESSFULLY", payload: JSON.stringify({}), payloadType: "application/json", traceId: m.id, spanId: m.id, queue: "main", runtimeEnvironmentId: ctx.environment.id, projectId: ctx.project.id, organizationId: ctx.organization.id, environmentType: "PRODUCTION", engine: "V2", }, }); await prisma.taskRunAttempt.create({ data: { friendlyId: `attempt_${m.id}`, number: 1, taskRunId: run.id, backgroundWorkerId: "bw", backgroundWorkerTaskId: "bwt", runtimeEnvironmentId: ctx.environment.id, queueId: "q", status: "COMPLETED", output: m.output, outputType: "application/json", }, }); return run; } async function seedBatch( prisma: PrismaClient, ctx: SeedCtx, friendlyId: string, memberIds: string[] ) { const batch = await prisma.batchTaskRun.create({ data: { friendlyId, runtimeEnvironmentId: ctx.environment.id, runCount: memberIds.length, runIds: [], batchVersion: "runengine:v2", }, }); for (const taskRunId of memberIds) { await prisma.batchTaskRunItem.create({ data: { batchTaskRunId: batch.id, taskRunId, status: "COMPLETED" }, }); } return batch; } const env = (ctx: SeedCtx) => ({ id: ctx.environment.id, type: ctx.environment.type, slug: ctx.environment.slug, organizationId: ctx.organization.id, organization: { slug: ctx.organization.slug, title: ctx.organization.title }, projectId: ctx.project.id, project: { name: ctx.project.name }, }) as unknown as AuthenticatedEnvironment; describe("ApiBatchResultsPresenter route wiring (the /batches/:id/results 404 regression)", () => { heteroPostgresTest( "a NEW-resident batch resolves with split deps but 404s (undefined) when built passthrough-only", async ({ prisma14, prisma17 }) => { const newDb = prisma17 as unknown as PrismaClient; // dedicated run-ops (NEW) DB analog const legacyDb = prisma14 as unknown as PrismaClient; // control-plane / legacy replica analog // Batch + members live ONLY on the NEW DB. The env is mirrored onto the legacy DB so the // passthrough read has an environment to filter on — but never the batch row. const ctx = await seedEnv(newDb, "route-new"); await mirrorEnv(legacyDb, ctx, "route-legacy"); await relaxFks(newDb); const memberId = newRunId("a"); await seedMember(newDb, ctx, { id: memberId, friendlyId: "run_route_a", output: JSON.stringify({ from: "new" }), }); await seedBatch(newDb, ctx, "batch_route_new", [memberId]); // Route wiring: splitEnabled + newClient + legacyReplica; passthrough handles throw. const splitPresenter = new ApiBatchResultsPresenter(throwingPrisma, throwingPrisma, { splitEnabled: true, newClient: prisma17 as unknown as PrismaReplicaClient, legacyReplica: prisma14 as unknown as PrismaReplicaClient, }); const resolved = await splitPresenter.call("batch_route_new", env(ctx)); expect(resolved).toBeDefined(); expect(resolved!.id).toBe("batch_route_new"); expect(resolved!.items).toHaveLength(1); expect(resolved!.items[0]).toEqual({ ok: true, id: "run_route_a", taskIdentifier: "my-task", output: JSON.stringify({ from: "new" }), outputType: "application/json", }); // Pre-fix route: no read-through deps => passthrough off the control-plane replica (the legacy // DB, which never received the batch) => undefined, i.e. the 404. const passthroughPresenter = new ApiBatchResultsPresenter(legacyDb, legacyDb); const missed = await passthroughPresenter.call("batch_route_new", env(ctx)); expect(missed).toBeUndefined(); } ); });