chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
// Real-PG proof for the RunPresenter run-detail read seam. The DB is never mocked:
|
||||
// the only vi.mock redirects the `~/db.server` module handle (captured by the
|
||||
// `runStore` singleton at load) to the live testcontainer client via a delegating
|
||||
// Proxy. Seeding `logsDeletedAt` + `showDeletedLogs: false` makes `showLogs` false,
|
||||
// so the presenter returns the header EARLY — before the trace path and the
|
||||
// `user.findFirst` admin read — keeping the test off ClickHouse.
|
||||
import { postgresTest } from "@internal/testcontainers";
|
||||
import type { PrismaClient } from "@trigger.dev/database";
|
||||
import { generateRunOpsId } from "@trigger.dev/core/v3/isomorphic";
|
||||
import { describe, expect, vi } from "vitest";
|
||||
|
||||
vi.setConfig({ testTimeout: 60_000 });
|
||||
|
||||
// Hoisted alongside the vi.mock factory. `setCurrentPrisma` points the delegating
|
||||
// Proxy at each test's container. The RunStore singleton is built ONCE at import and
|
||||
// its error-normalizing wrapper memoizes each Prisma model delegate on first access;
|
||||
// returning `current.taskRun` directly would freeze the store onto the first test's
|
||||
// container ("Database test_0 does not exist" on later tests). So object-valued
|
||||
// delegates return a STABLE per-key sub-proxy the store can safely cache, which
|
||||
// re-delegates to the live `current[key]` on every access; functions/scalars pass
|
||||
// through to the live client.
|
||||
const { delegating, setCurrentPrisma } = vi.hoisted(() => {
|
||||
let current: any = undefined;
|
||||
const subProxyCache = new Map<string, unknown>();
|
||||
|
||||
const getSubProxy = (prop: string) => {
|
||||
const cached = subProxyCache.get(prop);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const subProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_st, key) {
|
||||
if (!current) {
|
||||
throw new Error("currentPrisma not set");
|
||||
}
|
||||
const delegate = current[prop];
|
||||
const value = delegate?.[key];
|
||||
return typeof value === "function" ? value.bind(delegate) : value;
|
||||
},
|
||||
}
|
||||
);
|
||||
subProxyCache.set(prop, subProxy);
|
||||
return subProxy;
|
||||
};
|
||||
|
||||
const proxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_t, prop) {
|
||||
if (!current) {
|
||||
throw new Error("currentPrisma not set");
|
||||
}
|
||||
if (typeof prop === "string") {
|
||||
const value = current[prop];
|
||||
if (value != null && typeof value === "object") {
|
||||
return getSubProxy(prop);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return current[prop];
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
delegating: proxy,
|
||||
setCurrentPrisma: (p: unknown) => {
|
||||
current = p;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("~/db.server", () => ({
|
||||
prisma: delegating,
|
||||
$replica: delegating,
|
||||
}));
|
||||
|
||||
// Imported AFTER the hoisted vi.mock so the singleton (built from `~/db.server`)
|
||||
// captures the Proxy as its `readOnlyPrisma`.
|
||||
import {
|
||||
RunEnvironmentMismatchError,
|
||||
RunNotInPgError,
|
||||
RunPresenter,
|
||||
} from "~/presenters/v3/RunPresenter.server";
|
||||
|
||||
let suffixCounter = 0;
|
||||
function uniqueSuffix(prefix: string) {
|
||||
suffixCounter += 1;
|
||||
return `${prefix}-${suffixCounter}-${Date.now()}`;
|
||||
}
|
||||
|
||||
async function seedOrgProjectEnvMember(prisma: PrismaClient, suffix: string) {
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
email: `${suffix}@test.com`,
|
||||
authenticationMethod: "MAGIC_LINK",
|
||||
},
|
||||
});
|
||||
|
||||
const organization = await prisma.organization.create({
|
||||
data: {
|
||||
title: `org-${suffix}`,
|
||||
slug: `org-${suffix}`,
|
||||
members: { create: { userId: user.id, role: "ADMIN" } },
|
||||
},
|
||||
include: { members: true },
|
||||
});
|
||||
|
||||
const project = await prisma.project.create({
|
||||
data: {
|
||||
name: `proj-${suffix}`,
|
||||
slug: `proj-${suffix}`,
|
||||
organizationId: organization.id,
|
||||
externalRef: `ext-${suffix}`,
|
||||
},
|
||||
});
|
||||
|
||||
const runtimeEnvironment = await prisma.runtimeEnvironment.create({
|
||||
data: {
|
||||
slug: `env-${suffix}`,
|
||||
type: "DEVELOPMENT",
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
orgMemberId: organization.members[0]!.id,
|
||||
apiKey: `tr_dev_${suffix}`,
|
||||
pkApiKey: `pk_dev_${suffix}`,
|
||||
shortcode: `sc-${suffix}`,
|
||||
},
|
||||
});
|
||||
|
||||
return { user, organization, project, runtimeEnvironment };
|
||||
}
|
||||
|
||||
async function seedRun(
|
||||
prisma: PrismaClient,
|
||||
ids: { id: string; friendlyId: string },
|
||||
env: { runtimeEnvironmentId: string; projectId: string; organizationId: string }
|
||||
) {
|
||||
return prisma.taskRun.create({
|
||||
data: {
|
||||
id: ids.id,
|
||||
friendlyId: ids.friendlyId,
|
||||
taskIdentifier: "my-task",
|
||||
payload: JSON.stringify({ foo: "bar" }),
|
||||
payloadType: "application/json",
|
||||
traceId: "trace-1234",
|
||||
spanId: "span-1234",
|
||||
queue: "test",
|
||||
runtimeEnvironmentId: env.runtimeEnvironmentId,
|
||||
projectId: env.projectId,
|
||||
organizationId: env.organizationId,
|
||||
environmentType: "DEVELOPMENT",
|
||||
engine: "V2",
|
||||
status: "COMPLETED_SUCCESSFULLY",
|
||||
// logsDeletedAt set → showLogs is false → presenter returns the header
|
||||
// EARLY, before the trace path and before the `user.findFirst` admin read.
|
||||
logsDeletedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("RunPresenter run read seam (single-DB, real PG)", () => {
|
||||
postgresTest(
|
||||
"passthrough resolves the run-detail header via the singleton",
|
||||
async ({ prisma }) => {
|
||||
setCurrentPrisma(prisma);
|
||||
|
||||
const suffix = uniqueSuffix("pt");
|
||||
const { user, organization, project, runtimeEnvironment } = await seedOrgProjectEnvMember(
|
||||
prisma,
|
||||
suffix
|
||||
);
|
||||
|
||||
const id = generateRunOpsId();
|
||||
const friendlyId = `run_${id}`;
|
||||
const run = await seedRun(
|
||||
prisma,
|
||||
{ id, friendlyId },
|
||||
{
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
}
|
||||
);
|
||||
|
||||
const presenter = new RunPresenter(prisma);
|
||||
const result = await presenter.call({
|
||||
userId: user.id,
|
||||
projectSlug: project.slug,
|
||||
environmentSlug: runtimeEnvironment.slug,
|
||||
runFriendlyId: friendlyId,
|
||||
showDeletedLogs: false,
|
||||
showDebug: false,
|
||||
});
|
||||
|
||||
// Header served through the store seam (singleton → Proxy → real container).
|
||||
expect(result.run.id).toBe(id);
|
||||
expect(result.run.friendlyId).toBe(friendlyId);
|
||||
expect(result.run.number).toBe(run.number);
|
||||
expect(result.run.status).toBe("COMPLETED_SUCCESSFULLY");
|
||||
expect(result.run.environment.slug).toBe(runtimeEnvironment.slug);
|
||||
// logsDeletedAt is set → early return, no trace.
|
||||
expect(result.trace).toBeUndefined();
|
||||
}
|
||||
);
|
||||
|
||||
postgresTest(
|
||||
"run read is NOT pinned to the constructor client (served via the seam)",
|
||||
async ({ prisma }) => {
|
||||
setCurrentPrisma(prisma);
|
||||
|
||||
const suffix = uniqueSuffix("unpinned");
|
||||
const { user, organization, project, runtimeEnvironment } = await seedOrgProjectEnvMember(
|
||||
prisma,
|
||||
suffix
|
||||
);
|
||||
|
||||
const id = generateRunOpsId();
|
||||
const friendlyId = `run_${id}`;
|
||||
await seedRun(
|
||||
prisma,
|
||||
{ id, friendlyId },
|
||||
{
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
}
|
||||
);
|
||||
|
||||
// Sentinel constructor client whose taskRun.findFirst THROWS: if the run read
|
||||
// were pinned to this.#prismaClient the call would reject — it resolves because
|
||||
// the run read flows through the store seam/singleton instead. The control-plane
|
||||
// lookups (project-scope + membership auth; the trace-path admin user.findFirst)
|
||||
// are NOT split and correctly use the constructor client, so they are stubbed to
|
||||
// succeed while taskRun stays throwing.
|
||||
const sentinel = {
|
||||
taskRun: {
|
||||
findFirst: () => {
|
||||
throw new Error("run read must not use constructor client");
|
||||
},
|
||||
},
|
||||
project: {
|
||||
findFirst: async () => ({ id: project.id }),
|
||||
},
|
||||
user: {
|
||||
findFirst: async () => user,
|
||||
},
|
||||
} as unknown as PrismaClient;
|
||||
|
||||
const presenter = new RunPresenter(sentinel);
|
||||
const result = await presenter.call({
|
||||
userId: user.id,
|
||||
projectSlug: project.slug,
|
||||
environmentSlug: runtimeEnvironment.slug,
|
||||
runFriendlyId: friendlyId,
|
||||
showDeletedLogs: false,
|
||||
showDebug: false,
|
||||
});
|
||||
|
||||
expect(result.run.id).toBe(id);
|
||||
expect(result.run.friendlyId).toBe(friendlyId);
|
||||
expect(result.trace).toBeUndefined();
|
||||
}
|
||||
);
|
||||
|
||||
postgresTest("missing run maps to RunNotInPgError", async ({ prisma }) => {
|
||||
setCurrentPrisma(prisma);
|
||||
|
||||
const suffix = uniqueSuffix("notfound");
|
||||
const { user, project, runtimeEnvironment } = await seedOrgProjectEnvMember(prisma, suffix);
|
||||
|
||||
const missingFriendlyId = `run_${generateRunOpsId()}`;
|
||||
|
||||
const presenter = new RunPresenter(prisma);
|
||||
await expect(
|
||||
presenter.call({
|
||||
userId: user.id,
|
||||
projectSlug: project.slug,
|
||||
environmentSlug: runtimeEnvironment.slug,
|
||||
runFriendlyId: missingFriendlyId,
|
||||
showDeletedLogs: false,
|
||||
showDebug: false,
|
||||
})
|
||||
).rejects.toThrow(RunNotInPgError);
|
||||
});
|
||||
|
||||
postgresTest("environment mismatch maps to RunEnvironmentMismatchError", async ({ prisma }) => {
|
||||
setCurrentPrisma(prisma);
|
||||
|
||||
const suffix = uniqueSuffix("mismatch");
|
||||
const { user, organization, project, runtimeEnvironment } = await seedOrgProjectEnvMember(
|
||||
prisma,
|
||||
suffix
|
||||
);
|
||||
|
||||
const id = generateRunOpsId();
|
||||
const friendlyId = `run_${id}`;
|
||||
await seedRun(
|
||||
prisma,
|
||||
{ id, friendlyId },
|
||||
{
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
}
|
||||
);
|
||||
|
||||
const presenter = new RunPresenter(prisma);
|
||||
await expect(
|
||||
presenter.call({
|
||||
userId: user.id,
|
||||
projectSlug: project.slug,
|
||||
// Seeded env slug is `env-${suffix}`; pass a different slug.
|
||||
environmentSlug: `other-${suffix}`,
|
||||
runFriendlyId: friendlyId,
|
||||
showDeletedLogs: false,
|
||||
showDebug: false,
|
||||
})
|
||||
).rejects.toThrow(RunEnvironmentMismatchError);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user