import { containerTest } from "@internal/testcontainers"; import type { Organization, PrismaClient, Project, RuntimeEnvironment, } from "@trigger.dev/database"; import { customAlphabet } from "nanoid"; import { expect, vi } from "vitest"; import { ApiBatchResultsPresenter } from "~/presenters/v3/ApiBatchResultsPresenter.server"; import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { seedTestEnvironment } from "../helpers/seedTestEnvironment"; // Neutralize the db.server singleton so importing the presenter (via BasePresenter) does not try // to connect to the env database; the test uses the injected testcontainer prisma for all reads. vi.mock("~/db.server", () => ({ prisma: {}, $replica: {} })); vi.setConfig({ testTimeout: 60_000 }); const idGenerator = customAlphabet("123456789abcdefghijkmnopqrstuvwxyz", 21); function authEnv( environment: RuntimeEnvironment, project: Project, organization: Organization ): AuthenticatedEnvironment { return { ...environment, project, organization, orgMember: null } as AuthenticatedEnvironment; } type SeedContext = { environmentId: string; projectId: string; organizationId: string; backgroundWorkerId: string; backgroundWorkerTaskId: string; queueId: string; }; async function seedWorker( prisma: PrismaClient, ctx: Omit ) { const queue = await prisma.taskQueue.create({ data: { friendlyId: `queue_${idGenerator()}`, name: "task/test-task", projectId: ctx.projectId, runtimeEnvironmentId: ctx.environmentId, }, }); const worker = await prisma.backgroundWorker.create({ data: { friendlyId: `worker_${idGenerator()}`, contentHash: "hash", projectId: ctx.projectId, runtimeEnvironmentId: ctx.environmentId, version: "20240101.1", metadata: {}, }, }); const task = await prisma.backgroundWorkerTask.create({ data: { friendlyId: `task_${idGenerator()}`, slug: "test-task", filePath: "src/test.ts", exportName: "testTask", workerId: worker.id, projectId: ctx.projectId, runtimeEnvironmentId: ctx.environmentId, }, }); return { queueId: queue.id, backgroundWorkerId: worker.id, backgroundWorkerTaskId: task.id }; } async function seedRunWithAttempt( prisma: PrismaClient, ctx: SeedContext, opts: { status: "COMPLETED_SUCCESSFULLY" | "COMPLETED_WITH_ERRORS" | "CANCELED" | "EXECUTING"; attempt?: { status: "COMPLETED" | "FAILED"; output?: string; outputType?: string; error?: unknown; }; } ) { const runInternalId = idGenerator(); const run = await prisma.taskRun.create({ data: { id: runInternalId, friendlyId: `run_${runInternalId}`, taskIdentifier: "test-task", payload: "{}", payloadType: "application/json", traceId: idGenerator(), spanId: idGenerator(), queue: "task/test-task", runtimeEnvironmentId: ctx.environmentId, projectId: ctx.projectId, status: opts.status, }, }); if (opts.attempt) { await prisma.taskRunAttempt.create({ data: { friendlyId: `attempt_${idGenerator()}`, taskRunId: run.id, backgroundWorkerId: ctx.backgroundWorkerId, backgroundWorkerTaskId: ctx.backgroundWorkerTaskId, runtimeEnvironmentId: ctx.environmentId, queueId: ctx.queueId, status: opts.attempt.status, output: opts.attempt.output, outputType: opts.attempt.outputType ?? "application/json", error: opts.attempt.error as any, }, }); } return run; } containerTest( "ApiBatchResultsPresenter returns ordered results matching pre-decompose behavior", async ({ prisma }) => { const { environment, project, organization } = await seedTestEnvironment(prisma); const worker = await seedWorker(prisma, { environmentId: environment.id, projectId: project.id, organizationId: organization.id, }); const ctx: SeedContext = { environmentId: environment.id, projectId: project.id, organizationId: organization.id, ...worker, }; // A successful run, a failed run, and an executing run (no terminal attempt → undefined). const successRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_SUCCESSFULLY", attempt: { status: "COMPLETED", output: '"hello"', outputType: "application/json" }, }); const failedRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_WITH_ERRORS", attempt: { status: "FAILED", error: { type: "BUILT_IN_ERROR", name: "Error", message: "boom", stackTrace: "boom" }, }, }); const executingRun = await seedRunWithAttempt(prisma, ctx, { status: "EXECUTING", }); const batchInternalId = idGenerator(); const batchFriendlyId = `batch_${batchInternalId}`; await prisma.batchTaskRun.create({ data: { id: batchInternalId, friendlyId: batchFriendlyId, runtimeEnvironmentId: environment.id, }, }); // Items inserted in a deterministic order: success, failed, executing. for (const run of [successRun, failedRun, executingRun]) { await prisma.batchTaskRunItem.create({ data: { batchTaskRunId: batchInternalId, taskRunId: run.id, }, }); } // Pass the testcontainer prisma as both the primary and the replica: the passthrough path // reads the batch row off the replica and the member runs off the primary (via the run store). const presenter = new ApiBatchResultsPresenter(prisma, prisma); const result = await presenter.call( batchFriendlyId, authEnv(environment, project, organization) ); expect(result).toBeDefined(); expect(result?.id).toBe(batchFriendlyId); // executing run yields no execution result → filtered out. Order preserved: success then failed. expect(result?.items).toHaveLength(2); const [first, second] = result!.items; expect(first.ok).toBe(true); expect(first.id).toBe(successRun.friendlyId); if (first.ok) { expect(first.output).toBe('"hello"'); expect(first.taskIdentifier).toBe("test-task"); } expect(second.ok).toBe(false); expect(second.id).toBe(failedRun.friendlyId); } ); containerTest( "ApiBatchResultsPresenter filters runs without an execution result but keeps order", async ({ prisma }) => { const { environment, project, organization } = await seedTestEnvironment(prisma); const worker = await seedWorker(prisma, { environmentId: environment.id, projectId: project.id, organizationId: organization.id, }); const ctx: SeedContext = { environmentId: environment.id, projectId: project.id, organizationId: organization.id, ...worker, }; // Pending run → executionResultForTaskRun returns undefined → filtered out, like the // pre-decompose code did via `.filter(Boolean)`. const pendingRun = await seedRunWithAttempt(prisma, ctx, { status: "EXECUTING" }); const successRun = await seedRunWithAttempt(prisma, ctx, { status: "COMPLETED_SUCCESSFULLY", attempt: { status: "COMPLETED", output: '"ok"', outputType: "application/json" }, }); const batchInternalId = idGenerator(); const batchFriendlyId = `batch_${batchInternalId}`; await prisma.batchTaskRun.create({ data: { id: batchInternalId, friendlyId: batchFriendlyId, runtimeEnvironmentId: environment.id, }, }); // pending first, success second — only the success result should survive, in order. for (const run of [pendingRun, successRun]) { await prisma.batchTaskRunItem.create({ data: { batchTaskRunId: batchInternalId, taskRunId: run.id }, }); } // Pass the testcontainer prisma as both the primary and the replica: the passthrough path // reads the batch row off the replica and the member runs off the primary (via the run store). const presenter = new ApiBatchResultsPresenter(prisma, prisma); const result = await presenter.call( batchFriendlyId, authEnv(environment, project, organization) ); expect(result?.items).toHaveLength(1); expect(result?.items[0]?.id).toBe(successRun.friendlyId); } ); containerTest("ApiBatchResultsPresenter short-circuits an empty batch", async ({ prisma }) => { const { environment, project, organization } = await seedTestEnvironment(prisma); const batchInternalId = idGenerator(); const batchFriendlyId = `batch_${batchInternalId}`; await prisma.batchTaskRun.create({ data: { id: batchInternalId, friendlyId: batchFriendlyId, runtimeEnvironmentId: environment.id, }, }); // Pass the testcontainer prisma as both the primary and the replica: the passthrough path // reads the batch row off the replica and the member runs off the primary (via the run store). const presenter = new ApiBatchResultsPresenter(prisma, prisma); const result = await presenter.call(batchFriendlyId, authEnv(environment, project, organization)); expect(result).toEqual({ id: batchFriendlyId, items: [] }); });