import { describe, expect, vi } from "vitest"; // The presenter graph imports `~/services/clickhouse/clickhouseFactoryInstance.server` and, via // NextRunListPresenter -> RunsRepository / runStore, reaches `~/db.server`'s `prisma`/`$replica` // singletons (through findDisplayableEnvironment + getTaskIdentifiers). We stub those two wiring // boundaries so the module loads + the connected-runs hydrate can be pointed at the per-test REAL // containers. The DB is NEVER mocked: every assertion runs against real Postgres/ClickHouse. // // * `~/db.server` — `prisma`/`$replica`/`runOpsNewPrisma` singletons. The read-through cases pass // explicit client handles to the presenter ctor so these proxies are never read on those paths. // For the connected-runs hydrate the proxies forward lazily to the test's real legacy handle. // The run-ops new/legacy clients forward to the new/legacy per-test holders respectively. // * clickhouseFactory singleton — overridden to hand back the per-test ClickHouse so the // connected-runs hydrate uses a real CH container (a wiring override, not a DB mock). const legacyReplicaHolder = vi.hoisted(() => ({ client: undefined as any })); const newClientHolder = vi.hoisted(() => ({ client: undefined as any })); const clickhouseHolder = vi.hoisted(() => ({ client: undefined as any })); vi.mock("~/db.server", async () => { const { Prisma } = await import("@trigger.dev/database"); const lazyProxy = (holder: { client: any }, label: string) => new Proxy( {}, { get(_t, prop) { if (!holder.client) { throw new Error(`${label} not set for this test`); } return holder.client[prop]; }, } ); const replicaProxy = lazyProxy(legacyReplicaHolder, "legacyReplicaHolder.client"); return { prisma: replicaProxy, $replica: replicaProxy, runOpsNewPrisma: lazyProxy(newClientHolder, "newClientHolder.client"), runOpsNewReplica: lazyProxy(newClientHolder, "newClientHolder.client"), runOpsLegacyPrisma: replicaProxy, runOpsLegacyReplica: replicaProxy, sqlDatabaseSchema: Prisma.sql([`public`]), }; }); vi.mock("~/services/clickhouse/clickhouseFactoryInstance.server", () => ({ clickhouseFactory: { getClickhouseForOrganization: async () => { if (!clickhouseHolder.client) { throw new Error("clickhouseHolder.client not set for this test"); } return clickhouseHolder.client; }, }, })); import { createPostgresContainer, heteroPostgresTest, replicationContainerTest, } from "@internal/testcontainers"; import type { PrismaClient, WaitpointType } from "@trigger.dev/database"; import { PrismaClient as PrismaClientCtor } from "@trigger.dev/database"; import { setTimeout } from "node:timers/promises"; import type { PrismaReplicaClient } from "~/db.server"; import { WaitpointPresenter } from "~/presenters/v3/WaitpointPresenter.server"; import { setupClickhouseReplication } from "./utils/replicationUtils"; vi.setConfig({ testTimeout: 90_000 }); // A read client whose waitpoint.findFirst is recorded; throws if used after being marked // forbidden, so we can prove a store was NEVER read. Every other access forwards to the real // client (so the inlined `environment` join + connectedRuns relation still resolve). function recording(client: PrismaClient, opts: { forbidden?: boolean } = {}) { const calls: unknown[] = []; return { handle: new Proxy(client, { get(target, prop) { if (prop === "waitpoint") { return new Proxy((target as any).waitpoint, { get(wpTarget, wpProp) { if (wpProp === "findFirst") { return (args: unknown) => { calls.push(args); if (opts.forbidden) { throw new Error("this store must never be read"); } return (wpTarget as any).findFirst(args); }; } return (wpTarget as any)[wpProp]; }, }); } return (target as any)[prop]; }, }) as unknown as PrismaReplicaClient, calls, }; } type SeedContext = { organizationId: string; projectId: string; environmentId: string; }; async function seedParents(prisma: PrismaClient, slug: string): Promise { const organization = await prisma.organization.create({ data: { title: `org-${slug}`, slug: `org-${slug}` }, }); const project = await prisma.project.create({ data: { name: `proj-${slug}`, slug: `proj-${slug}`, organizationId: organization.id, externalRef: `proj-${slug}`, }, }); const runtimeEnvironment = await prisma.runtimeEnvironment.create({ data: { slug: `env-${slug}`, type: "DEVELOPMENT", projectId: project.id, organizationId: organization.id, apiKey: `tr_dev_${slug}`, pkApiKey: `pk_dev_${slug}`, shortcode: `sc-${slug}`, }, }); return { organizationId: organization.id, projectId: project.id, environmentId: runtimeEnvironment.id, }; } /** Mirrors the org/project/env parents onto a second DB with the SAME ids (FKs need them). */ async function mirrorParents(prisma: PrismaClient, ctx: SeedContext, slug: string): Promise { await prisma.organization.create({ data: { id: ctx.organizationId, title: `org-${slug}`, slug: `org-${slug}` }, }); await prisma.project.create({ data: { id: ctx.projectId, name: `proj-${slug}`, slug: `proj-${slug}`, organizationId: ctx.organizationId, externalRef: `proj-${slug}`, }, }); await prisma.runtimeEnvironment.create({ data: { id: ctx.environmentId, slug: `env-${slug}`, type: "DEVELOPMENT", projectId: ctx.projectId, organizationId: ctx.organizationId, apiKey: `tr_dev_${slug}_b`, pkApiKey: `pk_dev_${slug}_b`, shortcode: `sc-${slug}-b`, }, }); } async function seedWaitpoint( prisma: PrismaClient, ctx: SeedContext, friendlyId: string, overrides: Partial<{ type: WaitpointType; tags: string[]; output: string; connectedRunFriendlyIds: string[]; }> = {} ) { return prisma.waitpoint.create({ data: { friendlyId, type: overrides.type ?? "MANUAL", status: "COMPLETED", idempotencyKey: `idem-${friendlyId}`, userProvidedIdempotencyKey: false, output: overrides.output ?? JSON.stringify({ hello: "world" }), outputType: "application/json", outputIsError: false, completedAt: new Date(), tags: overrides.tags ?? ["a", "b"], projectId: ctx.projectId, environmentId: ctx.environmentId, ...(overrides.connectedRunFriendlyIds ? { connectedRuns: { connect: overrides.connectedRunFriendlyIds.map((friendlyId) => ({ friendlyId })), }, } : {}), }, }); } async function createRun( prisma: PrismaClient, ctx: SeedContext, run: { friendlyId: string; taskIdentifier?: string } ) { return prisma.taskRun.create({ data: { friendlyId: run.friendlyId, taskIdentifier: run.taskIdentifier ?? "my-task", status: "PENDING", payload: JSON.stringify({ foo: run.friendlyId }), traceId: run.friendlyId, spanId: run.friendlyId, queue: "test", runtimeEnvironmentId: ctx.environmentId, projectId: ctx.projectId, organizationId: ctx.organizationId, environmentType: "DEVELOPMENT", engine: "V2", }, }); } const callArgs = (ctx: SeedContext, friendlyId: string) => ({ friendlyId, environmentId: ctx.environmentId, projectId: ctx.projectId, }); describe("WaitpointPresenter dual-DB read-through (hetero PG14 + PG17, no connected runs)", () => { // new-DB short-circuit. Waitpoint on NEW (PG17), legacy // wrapped so its waitpoint.findFirst throws if invoked. The lookup answers from NEW and must // NEVER fall through to legacy. heteroPostgresTest( "waitpoint on the new DB resolves without touching the legacy replica", async ({ prisma14, prisma17 }) => { const ctx = await seedParents(prisma17, "newonly"); const seeded = await seedWaitpoint(prisma17, ctx, "waitpoint_newonly"); // The env lives on the new DB here; the resolver singleton reads it through the // `~/db.server` proxy. legacyReplicaHolder.client = prisma17; const newClient = recording(prisma17); const legacy = recording(prisma14, { forbidden: true }); const presenter = new WaitpointPresenter(undefined, undefined, { splitEnabled: true, newClient: newClient.handle, legacyReplica: legacy.handle, }); const result = await presenter.call(callArgs(ctx, seeded.friendlyId)); expect(result?.id).toBe(seeded.friendlyId); // New-first short-circuit: legacy never probed (the throwing handle proves it). expect(newClient.calls.length).toBe(1); expect(legacy.calls.length).toBe(0); } ); // single-DB passthrough. No read-through deps -> exactly one plain // findFirst against the single `_replica` handle; the split branch is structurally never entered // (no second handle is injected). The connected-runs hydrate forwards `undefined` deps. heteroPostgresTest( "no read-through deps -> one plain findFirst on the single replica (passthrough)", async ({ prisma14, prisma17 }) => { const ctx = await seedParents(prisma14, "passthrough"); const seeded = await seedWaitpoint(prisma14, ctx, "waitpoint_passthrough", { tags: ["one"], }); const single = recording(prisma14); const second = recording(prisma17, { forbidden: true }); legacyReplicaHolder.client = single.handle; newClientHolder.client = second.handle; // No readThroughDeps -> ctor defaults _replica to the (mocked) `$replica` singleton, which // forwards to `single.handle`. The split branch needs an injected second handle to fire, so // it cannot: passthrough is structural. const presenter = new WaitpointPresenter(); const result = await presenter.call(callArgs(ctx, seeded.friendlyId)); expect(result?.id).toBe(seeded.friendlyId); expect(result?.tags).toEqual(["one"]); // Exactly one read on the single client; the second handle is never touched. expect(single.calls.length).toBe(1); expect(second.calls.length).toBe(0); } ); }); describe("WaitpointPresenter connected-runs hydrate routed through read-through (PG14 + PG17 + CH)", () => { // Waitpoint detail + connected runs resolve on run-ops NEW. The // waitpoint + its 2 connected runs live on the new (PG17) DB; CH gets the run id-set so the // threaded NextRunListPresenter hydrate returns them. Proves the read-through deps are forwarded // so the connected-runs hydrate flows through the routed store. replicationContainerTest( "waitpoint + 2 connected runs resolve on the new DB via the routed hydrate", async ({ clickhouseContainer, redisOptions, postgresContainer, prisma, network }) => { // `prisma`/`postgresContainer` is the PG14 legacy + CH replication source. The new DB (PG17) // is created alongside; we seed the waitpoint + runs on it so CH replicates from PG14 — so we // mirror the runs onto PG14 (replication source) and the waitpoint+runs onto PG17 (authoritative). const { clickhouse } = await setupClickhouseReplication({ prisma, databaseUrl: postgresContainer.getConnectionUri(), clickhouseUrl: clickhouseContainer.getConnectionUrl(), redisOptions, }); const { url: newUrl } = await createPostgresContainer(network, { imageTag: "docker.io/postgres:17", }); const prismaNew = new PrismaClientCtor({ datasources: { db: { url: newUrl } } }); legacyReplicaHolder.client = prisma; newClientHolder.client = prismaNew; clickhouseHolder.client = clickhouse; try { const ctx = await seedParents(prisma, "connectednew"); await mirrorParents(prismaNew, ctx, "connectednew"); // The connected runs land on PG14 (CH replication source) AND on PG17 (authoritative, // same ids/friendlyIds) so the routed hydrate resolves them from NEW. const runA = await createRun(prisma, ctx, { friendlyId: "run_connA" }); const runB = await createRun(prisma, ctx, { friendlyId: "run_connB" }); const newRunA = await createRun(prismaNew, ctx, { friendlyId: "run_connA", taskIdentifier: "my-task-NEW", }); const newRunB = await createRun(prismaNew, ctx, { friendlyId: "run_connB", taskIdentifier: "my-task-NEW", }); await prismaNew.taskRun.update({ where: { friendlyId: "run_connA" }, data: { id: runA.id }, }); await prismaNew.taskRun.update({ where: { friendlyId: "run_connB" }, data: { id: runB.id }, }); // Waitpoint authoritative on NEW, connected to the 2 runs. const seeded = await seedWaitpoint(prismaNew, ctx, "waitpoint_connectednew", { connectedRunFriendlyIds: ["run_connA", "run_connB"], }); // Wait for CH replication so the connected-run id-set page is non-empty. await setTimeout(1500); const presenter = new WaitpointPresenter(prisma, prisma, { splitEnabled: true, newClient: prismaNew, legacyReplica: prisma, }); const result = await presenter.call(callArgs(ctx, seeded.friendlyId)); expect(result?.id).toBe(seeded.friendlyId); expect(result?.connectedRuns.map((r) => r.friendlyId).sort()).toEqual([ "run_connA", "run_connB", ]); // The connected runs carry the PG17-only taskIdentifier -> they hydrated from the threaded // newClient (PG17), proving the routed store is armed. expect(result?.connectedRuns.every((r) => r.taskIdentifier === "my-task-NEW")).toBe(true); void newRunA; void newRunB; } finally { await prismaNew.$disconnect(); } } ); }); describe("WaitpointPresenter bare-ctor production default activates readThroughRun", () => { heteroPostgresTest( "run-ops waitpoint on the new DB resolves via readThroughRun production defaults", async ({ prisma14, prisma17 }) => { const ctx = await seedParents(prisma17, "proddefault"); const seeded = await seedWaitpoint(prisma17, ctx, "waitpoint_proddefault", { tags: ["p", "q"], }); // runOpsNewReplica default -> PG17; env resolver reads via legacyReplicaHolder. newClientHolder.client = prisma17; legacyReplicaHolder.client = prisma17; // No newClient/legacyReplica injected — production ctor shape. const presenter = new WaitpointPresenter(undefined, undefined, { splitEnabled: true }); const result = await presenter.call(callArgs(ctx, seeded.friendlyId)); expect(result?.id).toBe(seeded.friendlyId); expect(result?.tags).toEqual(["p", "q"]); } ); });