chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
import { heteroPostgresTest } from "@internal/testcontainers";
|
||||
import type { PrismaClient } from "@trigger.dev/database";
|
||||
import { generateRunOpsId } from "@trigger.dev/core/v3/isomorphic";
|
||||
import { describe, expect, vi } from "vitest";
|
||||
|
||||
// Stub `~/db.server` so the `runStore` singleton doesn't eagerly connect at
|
||||
// import. The concern passes its constructor `prisma` arg as the explicit
|
||||
// client/tx to every store call, so the singleton's bound handles are never
|
||||
// exercised — the passed client runs the query. Mirrors the shipped
|
||||
// `mollifierClaimResolution` test: env-wiring mock only; the DB under test is
|
||||
// the real PG14 + PG17 hetero-fixture containers.
|
||||
vi.mock("~/db.server", () => ({
|
||||
prisma: {},
|
||||
$replica: {},
|
||||
runOpsNewPrisma: {},
|
||||
runOpsLegacyPrisma: {},
|
||||
}));
|
||||
// Keep split off so resolveIdempotencyDedupClient returns this.prisma (the hetero fixture client).
|
||||
vi.mock("~/v3/runOpsMigration/splitMode.server", () => ({ isSplitEnabled: async () => false }));
|
||||
|
||||
import { IdempotencyKeyConcern } from "~/runEngine/concerns/idempotencyKeys.server";
|
||||
import type { TriggerTaskRequest } from "~/runEngine/types";
|
||||
|
||||
vi.setConfig({ testTimeout: 60_000 });
|
||||
|
||||
// The constructor `prisma` arg is the client the four store sites execute
|
||||
// against. With the run-ops split off (mocked above) the dedup resolver is a
|
||||
// pass-through that returns this same client, so constructing with the PG14 or
|
||||
// PG17 fixture client decides which DB the residency-routed dedup probe reads.
|
||||
function makeConcern(client: PrismaClient) {
|
||||
return new IdempotencyKeyConcern(
|
||||
client as never,
|
||||
{} as never, // engine — unused on the reuse / clear paths
|
||||
{} as never // traceEventConcern — unused on the reuse / clear paths
|
||||
);
|
||||
}
|
||||
|
||||
function makeRequest(opts: {
|
||||
environmentId: string;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
taskId: string;
|
||||
idempotencyKey: string;
|
||||
}): TriggerTaskRequest {
|
||||
return {
|
||||
taskId: opts.taskId,
|
||||
environment: {
|
||||
id: opts.environmentId,
|
||||
organizationId: opts.organizationId,
|
||||
projectId: opts.projectId,
|
||||
// Leave the org mollifier flag unset so the pre-gate claim path is
|
||||
// skipped — this test exercises the PG existing-run lookup + clear,
|
||||
// not the Redis claim. (resolveOrgMollifierFlag returns falsy for an
|
||||
// org with no mollifier flag, so claimEligible is false.)
|
||||
organization: { featureFlags: {} },
|
||||
},
|
||||
options: {},
|
||||
body: { options: { idempotencyKey: opts.idempotencyKey } },
|
||||
} as unknown as TriggerTaskRequest;
|
||||
}
|
||||
|
||||
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 runtimeEnvironment = await prisma.runtimeEnvironment.create({
|
||||
data: {
|
||||
slug: `test-${suffix}`,
|
||||
type: "DEVELOPMENT",
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
apiKey: `test-${suffix}`,
|
||||
pkApiKey: `test-${suffix}`,
|
||||
shortcode: `test-${suffix}`,
|
||||
},
|
||||
});
|
||||
return { organization, project, runtimeEnvironment };
|
||||
}
|
||||
|
||||
async function seedRun(
|
||||
prisma: PrismaClient,
|
||||
args: {
|
||||
runtimeEnvironmentId: string;
|
||||
projectId: string;
|
||||
organizationId: string;
|
||||
taskIdentifier: string;
|
||||
idempotencyKey: string;
|
||||
status?: "PENDING" | "EXECUTING" | "COMPLETED_SUCCESSFULLY" | "COMPLETED_WITH_ERRORS";
|
||||
idempotencyKeyExpiresAt?: Date;
|
||||
}
|
||||
) {
|
||||
const runId = generateRunOpsId();
|
||||
return prisma.taskRun.create({
|
||||
data: {
|
||||
id: runId,
|
||||
friendlyId: `run_${runId}`,
|
||||
taskIdentifier: args.taskIdentifier,
|
||||
idempotencyKey: args.idempotencyKey,
|
||||
idempotencyKeyExpiresAt: args.idempotencyKeyExpiresAt ?? null,
|
||||
status: args.status ?? "EXECUTING",
|
||||
payload: JSON.stringify({ foo: "bar" }),
|
||||
payloadType: "application/json",
|
||||
traceId: "1234",
|
||||
spanId: "1234",
|
||||
queue: "test",
|
||||
runtimeEnvironmentId: args.runtimeEnvironmentId,
|
||||
projectId: args.projectId,
|
||||
organizationId: args.organizationId,
|
||||
environmentType: "DEVELOPMENT",
|
||||
engine: "V2",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("IdempotencyKeyConcern · residency-routed dedup (cross-DB)", () => {
|
||||
heteroPostgresTest(
|
||||
"resolves a legacy-resident key against the legacy DB; a key whose run lives on the new DB is resolved against the new DB",
|
||||
async ({ prisma14, prisma17 }) => {
|
||||
const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv(
|
||||
prisma14,
|
||||
"legacy-auth"
|
||||
);
|
||||
|
||||
// Seed the same org/project/env shape on the NEW (PG17) DB so we can
|
||||
// place a row there for a *different* key — proving the legacy-pinned
|
||||
// read does not see it.
|
||||
const newSide = await seedOrgProjectEnv(prisma17, "new-side");
|
||||
|
||||
const reusedKey = "idem-reuse-1";
|
||||
|
||||
// The authoritative existing run lives on the LEGACY (PG14) DB.
|
||||
const legacyRun = await seedRun(prisma14, {
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
taskIdentifier: "my-task",
|
||||
idempotencyKey: reusedKey,
|
||||
status: "EXECUTING",
|
||||
});
|
||||
|
||||
// A row for a DIFFERENT key lives only on the NEW (PG17) DB.
|
||||
const newOnlyKey = "idem-new-only";
|
||||
await seedRun(prisma17, {
|
||||
runtimeEnvironmentId: newSide.runtimeEnvironment.id,
|
||||
projectId: newSide.project.id,
|
||||
organizationId: newSide.organization.id,
|
||||
taskIdentifier: "my-task",
|
||||
idempotencyKey: newOnlyKey,
|
||||
status: "EXECUTING",
|
||||
});
|
||||
|
||||
const concern = makeConcern(prisma14);
|
||||
|
||||
// (1) Reuse with the legacy key resolves the legacy-seeded run.
|
||||
const reuse = await concern.handleTriggerRequest(
|
||||
makeRequest({
|
||||
environmentId: runtimeEnvironment.id,
|
||||
organizationId: organization.id,
|
||||
projectId: project.id,
|
||||
taskId: "my-task",
|
||||
idempotencyKey: reusedKey,
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(reuse.isCached).toBe(true);
|
||||
if (reuse.isCached === true) {
|
||||
expect(reuse.run.id).toBe(legacyRun.id);
|
||||
}
|
||||
|
||||
// Exactly one run matches the key on the legacy DB — no duplicate.
|
||||
const legacyMatches = await prisma14.taskRun.count({
|
||||
where: {
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
taskIdentifier: "my-task",
|
||||
idempotencyKey: reusedKey,
|
||||
},
|
||||
});
|
||||
expect(legacyMatches).toBe(1);
|
||||
|
||||
// (2) A key whose run lives on the new DB is resolved against the new DB.
|
||||
const concernOnNew = makeConcern(prisma17);
|
||||
const newSideHit = await concernOnNew.handleTriggerRequest(
|
||||
makeRequest({
|
||||
environmentId: newSide.runtimeEnvironment.id,
|
||||
organizationId: newSide.organization.id,
|
||||
projectId: newSide.project.id,
|
||||
taskId: "my-task",
|
||||
idempotencyKey: newOnlyKey,
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(newSideHit.isCached).toBe(true);
|
||||
if (newSideHit.isCached === true) {
|
||||
expect(newSideHit.run.idempotencyKey).toBe(newOnlyKey);
|
||||
}
|
||||
|
||||
// (3) An unknown key on the legacy env does not wrongly return the
|
||||
// stale legacy hit for a different key.
|
||||
const unknown = await concern.handleTriggerRequest(
|
||||
makeRequest({
|
||||
environmentId: runtimeEnvironment.id,
|
||||
organizationId: organization.id,
|
||||
projectId: project.id,
|
||||
taskId: "my-task",
|
||||
idempotencyKey: "idem-never-seen",
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
expect(unknown.isCached).toBe(false);
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"cleared-status reuse clears the key on the legacy (PG14) DB and proceeds with a fresh trigger",
|
||||
async ({ prisma14 }) => {
|
||||
const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv(
|
||||
prisma14,
|
||||
"cleared-status"
|
||||
);
|
||||
|
||||
const key = "idem-cleared-1";
|
||||
|
||||
// Existing run is in a failed (cleared) status — the concern must
|
||||
// clear its key against the legacy authority and return isCached:false.
|
||||
const legacyRun = await seedRun(prisma14, {
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
taskIdentifier: "my-task",
|
||||
idempotencyKey: key,
|
||||
status: "COMPLETED_WITH_ERRORS",
|
||||
});
|
||||
|
||||
const concern = makeConcern(prisma14);
|
||||
|
||||
const result = await concern.handleTriggerRequest(
|
||||
makeRequest({
|
||||
environmentId: runtimeEnvironment.id,
|
||||
organizationId: organization.id,
|
||||
projectId: project.id,
|
||||
taskId: "my-task",
|
||||
idempotencyKey: key,
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
|
||||
// A fresh trigger proceeds (not cached).
|
||||
expect(result.isCached).toBe(false);
|
||||
|
||||
// The clear executed against the legacy (PG14) DB: re-query PG14 and
|
||||
// assert the key + its expiry are now null on the seeded run.
|
||||
const cleared = await prisma14.taskRun.findFirst({ where: { id: legacyRun.id } });
|
||||
expect(cleared?.idempotencyKey).toBeNull();
|
||||
expect(cleared?.idempotencyKeyExpiresAt).toBeNull();
|
||||
}
|
||||
);
|
||||
|
||||
heteroPostgresTest(
|
||||
"expired idempotency key is cleared on the legacy (PG14) DB and a fresh trigger proceeds",
|
||||
async ({ prisma14 }) => {
|
||||
const { project, organization, runtimeEnvironment } = await seedOrgProjectEnv(
|
||||
prisma14,
|
||||
"expired-key"
|
||||
);
|
||||
|
||||
const key = "idem-expired-1";
|
||||
|
||||
const legacyRun = await seedRun(prisma14, {
|
||||
runtimeEnvironmentId: runtimeEnvironment.id,
|
||||
projectId: project.id,
|
||||
organizationId: organization.id,
|
||||
taskIdentifier: "my-task",
|
||||
idempotencyKey: key,
|
||||
status: "EXECUTING",
|
||||
idempotencyKeyExpiresAt: new Date(Date.now() - 60_000), // already expired
|
||||
});
|
||||
|
||||
const concern = makeConcern(prisma14);
|
||||
|
||||
const result = await concern.handleTriggerRequest(
|
||||
makeRequest({
|
||||
environmentId: runtimeEnvironment.id,
|
||||
organizationId: organization.id,
|
||||
projectId: project.id,
|
||||
taskId: "my-task",
|
||||
idempotencyKey: key,
|
||||
}),
|
||||
undefined
|
||||
);
|
||||
|
||||
expect(result.isCached).toBe(false);
|
||||
|
||||
const cleared = await prisma14.taskRun.findFirst({ where: { id: legacyRun.id } });
|
||||
expect(cleared?.idempotencyKey).toBeNull();
|
||||
expect(cleared?.idempotencyKeyExpiresAt).toBeNull();
|
||||
}
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user