chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
// Real heterogeneous legacy + new Postgres proof for the public waitpoint retrieve read.
|
||||
// The DB is never mocked: reads hit the two real containers. Only pure boundaries
|
||||
// (splitEnabled, isPastRetention) and recording client wrappers are
|
||||
// injected. heteroPostgresTest runs the legacy and new databases on different major versions.
|
||||
import {
|
||||
heteroPostgresTest,
|
||||
heteroRunOpsPostgresTest,
|
||||
postgresTest,
|
||||
} from "@internal/testcontainers";
|
||||
import type { RunOpsPrismaClient } from "@internal/run-ops-database";
|
||||
import type { PrismaClient, WaitpointType } from "@trigger.dev/database";
|
||||
import { generateRunOpsId } from "@trigger.dev/core/v3/isomorphic";
|
||||
import { describe, expect, vi } from "vitest";
|
||||
import type { PrismaReplicaClient } from "~/db.server";
|
||||
import { ApiWaitpointPresenter } from "~/presenters/v3/ApiWaitpointPresenter.server";
|
||||
|
||||
vi.setConfig({ testTimeout: 60_000 });
|
||||
|
||||
// 25-char cuid body (no v1 version marker) → LEGACY residency.
|
||||
function generateLegacyCuid() {
|
||||
const suffix = Array.from(
|
||||
{ length: 24 },
|
||||
() => "0123456789abcdefghijklmnopqrstuvwxyz"[Math.floor(Math.random() * 36)]
|
||||
).join("");
|
||||
return `c${suffix}`;
|
||||
}
|
||||
|
||||
// A read client whose waitpoint.findFirst is recorded; throws if used after being marked
|
||||
// forbidden, so we can prove a store was NEVER read.
|
||||
function recording(client: PrismaClient | RunOpsPrismaClient, opts: { forbidden?: boolean } = {}) {
|
||||
const calls: unknown[] = [];
|
||||
const waitpoint = {
|
||||
findFirst: (args: unknown) => {
|
||||
calls.push(args);
|
||||
if (opts.forbidden) {
|
||||
throw new Error("this store must never be read");
|
||||
}
|
||||
return (client as unknown as PrismaReplicaClient).waitpoint.findFirst(args as never);
|
||||
},
|
||||
};
|
||||
return { handle: { ...client, waitpoint } as unknown as PrismaReplicaClient, calls };
|
||||
}
|
||||
|
||||
async function seedOrgProjectEnv(prisma: PrismaClient, suffix: string) {
|
||||
const organization = await prisma.organization.create({
|
||||
data: { title: `test-${suffix}`, slug: `test-${suffix}` },
|
||||
});
|
||||
const project = await prisma.project.create({
|
||||
data: {
|
||||
name: `test-${suffix}`,
|
||||
slug: `test-${suffix}`,
|
||||
organizationId: organization.id,
|
||||
externalRef: `test-${suffix}`,
|
||||
},
|
||||
});
|
||||
const environment = await prisma.runtimeEnvironment.create({
|
||||
data: {
|
||||
slug: `test-${suffix}`,
|
||||
type: "PRODUCTION",
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
apiKey: `apikey-${suffix}`,
|
||||
pkApiKey: `pk-${suffix}`,
|
||||
shortcode: `test-${suffix}`,
|
||||
},
|
||||
});
|
||||
return { organization, project, environment };
|
||||
}
|
||||
|
||||
async function seedWaitpoint(
|
||||
prisma: PrismaClient,
|
||||
id: string,
|
||||
env: { id: string; projectId: string },
|
||||
overrides: Partial<{
|
||||
status: "PENDING" | "COMPLETED";
|
||||
type: WaitpointType;
|
||||
output: string;
|
||||
outputType: string;
|
||||
outputIsError: boolean;
|
||||
completedAt: Date;
|
||||
completedAfter: Date;
|
||||
tags: string[];
|
||||
}> = {}
|
||||
) {
|
||||
return prisma.waitpoint.create({
|
||||
data: {
|
||||
id,
|
||||
friendlyId: `waitpoint_${id}`,
|
||||
type: overrides.type ?? "MANUAL",
|
||||
status: overrides.status ?? "COMPLETED",
|
||||
idempotencyKey: `idem-${id}`,
|
||||
userProvidedIdempotencyKey: false,
|
||||
output: overrides.output ?? JSON.stringify({ hello: "world" }),
|
||||
outputType: overrides.outputType ?? "application/json",
|
||||
outputIsError: overrides.outputIsError ?? false,
|
||||
completedAt: overrides.completedAt ?? new Date(),
|
||||
completedAfter: overrides.completedAfter,
|
||||
tags: overrides.tags ?? ["a", "b"],
|
||||
projectId: env.projectId,
|
||||
environmentId: env.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const environmentArg = (env: { id: string; projectId: string }) => ({
|
||||
id: env.id,
|
||||
type: "PRODUCTION" as const,
|
||||
project: { id: env.projectId, engine: "V2" as const },
|
||||
apiKey: "tr_test_apikey",
|
||||
});
|
||||
|
||||
describe("ApiWaitpointPresenter read-through (heterogeneous legacy + new Postgres)", () => {
|
||||
heteroPostgresTest(
|
||||
"resolves on run-ops NEW (run-ops id), legacy replica never touched",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
const id = generateRunOpsId();
|
||||
expect(id.length).toBe(26);
|
||||
|
||||
const { project, environment } = await seedOrgProjectEnv(prisma17, "new");
|
||||
const seeded = await seedWaitpoint(
|
||||
prisma17,
|
||||
id,
|
||||
{ id: environment.id, projectId: project.id },
|
||||
{ tags: ["x", "y", "z"], output: JSON.stringify({ n: 42 }) }
|
||||
);
|
||||
|
||||
const newClient = recording(prisma17);
|
||||
const legacy = recording(prisma14, { forbidden: true });
|
||||
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: newClient.handle,
|
||||
legacyReplica: legacy.handle,
|
||||
});
|
||||
|
||||
const result = await presenter.call(environmentArg(environment), id);
|
||||
|
||||
expect(result.id).toBe(seeded.friendlyId);
|
||||
expect(result.tags).toEqual(["x", "y", "z"]);
|
||||
expect(result.output).toBe(JSON.stringify({ n: 42 }));
|
||||
expect(result.type).toBe("MANUAL");
|
||||
// run-ops id → NEW: new store served the read, legacy never touched (fast-path).
|
||||
expect(newClient.calls.length).toBe(1);
|
||||
expect(legacy.calls.length).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"resolves off the LEGACY replica (cuid), never a legacy primary",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
const id = generateLegacyCuid();
|
||||
expect(id.length).toBe(25);
|
||||
|
||||
const { project, environment } = await seedOrgProjectEnv(prisma14, "legacy");
|
||||
const seeded = await seedWaitpoint(prisma14, id, {
|
||||
id: environment.id,
|
||||
projectId: project.id,
|
||||
});
|
||||
|
||||
const newClient = recording(prisma17);
|
||||
// The deps expose only legacyReplica — there is NO legacy-primary handle at all.
|
||||
const legacy = recording(prisma14);
|
||||
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: newClient.handle,
|
||||
legacyReplica: legacy.handle,
|
||||
});
|
||||
|
||||
const result = await presenter.call(environmentArg(environment), id);
|
||||
|
||||
expect(result.id).toBe(seeded.friendlyId);
|
||||
expect(result.tags).toEqual(["a", "b"]);
|
||||
// NEW probed first (miss) → resolved off the LEGACY REPLICA handle.
|
||||
expect(newClient.calls.length).toBe(1);
|
||||
expect(legacy.calls.length).toBe(1);
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"not-found maps to the existing ServiceValidationError surface",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
const id = generateLegacyCuid();
|
||||
const { environment } = await seedOrgProjectEnv(prisma14, "nf");
|
||||
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: recording(prisma17).handle,
|
||||
legacyReplica: recording(prisma14).handle,
|
||||
});
|
||||
|
||||
await expect(presenter.call(environmentArg(environment), id)).rejects.toThrow(
|
||||
"Waitpoint not found"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"past-retention maps to the same not-found surface",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
const id = generateLegacyCuid();
|
||||
const { environment } = await seedOrgProjectEnv(prisma14, "pr");
|
||||
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: recording(prisma17).handle,
|
||||
legacyReplica: recording(prisma14).handle,
|
||||
isPastRetention: () => true,
|
||||
});
|
||||
|
||||
await expect(presenter.call(environmentArg(environment), id)).rejects.toThrow(
|
||||
"Waitpoint not found"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"cross-seam — new-resident served from NEW (legacy untouched); in-retention served from legacy",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
// New-resident waitpoint: lives on NEW, the new probe hits, legacy must never be touched.
|
||||
const newId = generateRunOpsId();
|
||||
const newEnv = await seedOrgProjectEnv(prisma17, "x2new");
|
||||
await seedWaitpoint(prisma17, newId, {
|
||||
id: newEnv.environment.id,
|
||||
projectId: newEnv.project.id,
|
||||
});
|
||||
const newLegacy = recording(prisma14, { forbidden: true });
|
||||
const migratedPresenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: recording(prisma17).handle,
|
||||
legacyReplica: newLegacy.handle,
|
||||
});
|
||||
const migratedResult = await migratedPresenter.call(
|
||||
environmentArg(newEnv.environment),
|
||||
newId
|
||||
);
|
||||
expect(migratedResult.id).toBe(`waitpoint_${newId}`);
|
||||
expect(newLegacy.calls.length).toBe(0);
|
||||
|
||||
// In-retention waitpoint: lives on the legacy replica, served from it.
|
||||
const oldId = generateLegacyCuid();
|
||||
const oldEnv = await seedOrgProjectEnv(prisma14, "x2old");
|
||||
await seedWaitpoint(prisma14, oldId, {
|
||||
id: oldEnv.environment.id,
|
||||
projectId: oldEnv.project.id,
|
||||
});
|
||||
const retentionPresenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: recording(prisma17).handle,
|
||||
legacyReplica: recording(prisma14).handle,
|
||||
});
|
||||
const retentionResult = await retentionPresenter.call(
|
||||
environmentArg(oldEnv.environment),
|
||||
oldId
|
||||
);
|
||||
expect(retentionResult.id).toBe(`waitpoint_${oldId}`);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Regression: the split-mode NEW client is the REAL scalar-only run-ops client (prisma17). A cuid
|
||||
// classifies LEGACY, so readThroughRun probes NEW first — a relation in hydrate() (connectedRuns)
|
||||
// throws PrismaClientValidationError there (the 500) before the legacy fallback runs.
|
||||
describe("ApiWaitpointPresenter read-through (dedicated scalar-only run-ops NEW client)", () => {
|
||||
heteroRunOpsPostgresTest(
|
||||
"cuid token: hydrate() select is valid against the scalar-only run-ops client, resolves via legacy",
|
||||
async ({ prisma17, prisma14 }) => {
|
||||
const id = generateLegacyCuid();
|
||||
expect(id.length).toBe(25);
|
||||
|
||||
const { project, environment } = await seedOrgProjectEnv(prisma14, "scalar-legacy");
|
||||
const seeded = await seedWaitpoint(
|
||||
prisma14,
|
||||
id,
|
||||
{ id: environment.id, projectId: project.id },
|
||||
{ tags: ["p", "q"], output: JSON.stringify({ ok: true }) }
|
||||
);
|
||||
|
||||
const newClient = recording(prisma17);
|
||||
const legacy = recording(prisma14);
|
||||
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
splitEnabled: true,
|
||||
newClient: newClient.handle,
|
||||
legacyReplica: legacy.handle,
|
||||
});
|
||||
|
||||
// Must NOT throw PrismaClientValidationError; resolves the token off the legacy side.
|
||||
const result = await presenter.call(environmentArg(environment), id);
|
||||
|
||||
expect(result.id).toBe(seeded.friendlyId);
|
||||
expect(result.tags).toEqual(["p", "q"]);
|
||||
expect(result.output).toBe(JSON.stringify({ ok: true }));
|
||||
expect(newClient.calls.length).toBe(1);
|
||||
expect(legacy.calls.length).toBe(1);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("ApiWaitpointPresenter passthrough (single-DB)", () => {
|
||||
postgresTest(
|
||||
"no read-through deps → one plain replica read; legacy never touched",
|
||||
async ({ prisma }) => {
|
||||
const id = generateRunOpsId();
|
||||
const { project, environment } = await seedOrgProjectEnv(prisma, "pt");
|
||||
const seeded = await seedWaitpoint(
|
||||
prisma,
|
||||
id,
|
||||
{ id: environment.id, projectId: project.id },
|
||||
{ tags: ["one"], output: JSON.stringify({ ok: true }) }
|
||||
);
|
||||
|
||||
const single = recording(prisma);
|
||||
const legacy = recording(prisma, { forbidden: true });
|
||||
|
||||
// No splitEnabled → passthrough. newClient defaults to the single recording handle so we
|
||||
// can assert exactly one read against it; legacy must never fire.
|
||||
const presenter = new ApiWaitpointPresenter(undefined, undefined, {
|
||||
newClient: single.handle,
|
||||
legacyReplica: legacy.handle,
|
||||
});
|
||||
|
||||
const result = await presenter.call(environmentArg(environment), id);
|
||||
|
||||
expect(result.id).toBe(seeded.friendlyId);
|
||||
expect(result.tags).toEqual(["one"]);
|
||||
expect(result.output).toBe(JSON.stringify({ ok: true }));
|
||||
// Passthrough: exactly one read on the single client; legacy untouched.
|
||||
expect(single.calls.length).toBe(1);
|
||||
expect(legacy.calls.length).toBe(0);
|
||||
}
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user