Skip to content

Commit fee35d5

Browse files
committed
test(run-engine): assert the resume path delivers a NEW run's completed waitpoints
Covers getExecutionSnapshotsSince end to end for a NEW-residency run: the resumed snapshot must carry the completed waitpoint whose join lives on the new store, which the snapshot-id-routed lookup used to miss.
1 parent 38daf49 commit fee35d5

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// End-to-end resume-read path (managed worker `snapshots.since`): getExecutionSnapshotsSince ->
2+
// getSnapshotWaitpointIds -> runStore.findSnapshotCompletedWaitpointIds(latestSnapshot.id). The
3+
// latest snapshot id is a cuid, so the pre-fix router misrouted it to #legacy and the resumed NEW
4+
// run received an empty completedWaitpoints list and hung. This asserts the whole path delivers the
5+
// NEW-resident completed waitpoint. Real two-DB topology; never mocked.
6+
7+
import { heteroRunOpsPostgresTest } from "@internal/testcontainers";
8+
import { PostgresRunStore, RoutingRunStore, type CreateRunInput } from "@internal/run-store";
9+
import type { PrismaClient } from "@trigger.dev/database";
10+
import type { RunOpsPrismaClient } from "@internal/run-ops-database";
11+
import { expect } from "vitest";
12+
import { setTimeout as sleep } from "timers/promises";
13+
import { getExecutionSnapshotsSince } from "../systems/executionSnapshotSystem.js";
14+
15+
const RUN_OPS_A = "n".repeat(24) + "01"; // run-ops id -> NEW (#new / prisma17)
16+
17+
function makeRouter(prisma14: PrismaClient, prisma17: RunOpsPrismaClient) {
18+
const newStore = new PostgresRunStore({
19+
prisma: prisma17 as never,
20+
readOnlyPrisma: prisma17 as never,
21+
schemaVariant: "dedicated",
22+
});
23+
const legacyStore = new PostgresRunStore({
24+
prisma: prisma14,
25+
readOnlyPrisma: prisma14,
26+
schemaVariant: "legacy",
27+
});
28+
return new RoutingRunStore({ new: newStore, legacy: legacyStore });
29+
}
30+
31+
async function seedControlPlaneEnv(prisma: PrismaClient, suffix: string) {
32+
const organization = await prisma.organization.create({
33+
data: { title: `Org ${suffix}`, slug: `org-${suffix}` },
34+
});
35+
const project = await prisma.project.create({
36+
data: {
37+
name: `Project ${suffix}`,
38+
slug: `project-${suffix}`,
39+
externalRef: `proj_${suffix}`,
40+
organizationId: organization.id,
41+
},
42+
});
43+
const environment = await prisma.runtimeEnvironment.create({
44+
data: {
45+
type: "PRODUCTION",
46+
slug: `prod-${suffix}`,
47+
projectId: project.id,
48+
organizationId: organization.id,
49+
apiKey: `tr_prod_${suffix}`,
50+
pkApiKey: `pk_prod_${suffix}`,
51+
shortcode: `short_${suffix}`,
52+
maximumConcurrencyLimit: 10,
53+
},
54+
});
55+
return { organization, project, environment };
56+
}
57+
58+
function buildCreateRunInput(p: {
59+
runId: string;
60+
organizationId: string;
61+
projectId: string;
62+
runtimeEnvironmentId: string;
63+
}): CreateRunInput {
64+
return {
65+
data: {
66+
id: p.runId,
67+
engine: "V2",
68+
status: "EXECUTING",
69+
friendlyId: "run_since",
70+
runtimeEnvironmentId: p.runtimeEnvironmentId,
71+
environmentType: "PRODUCTION",
72+
organizationId: p.organizationId,
73+
projectId: p.projectId,
74+
taskIdentifier: "since-task",
75+
payload: "{}",
76+
payloadType: "application/json",
77+
context: {},
78+
traceContext: {},
79+
traceId: `trace_${p.runId}`,
80+
spanId: `span_${p.runId}`,
81+
runTags: [],
82+
queue: "task/since-task",
83+
isTest: false,
84+
taskEventStore: "taskEvent",
85+
depth: 0,
86+
createdAt: new Date("2024-01-01T00:00:00.000Z"),
87+
},
88+
snapshot: {
89+
engine: "V2",
90+
executionStatus: "RUN_CREATED",
91+
description: "Run was created",
92+
runStatus: "PENDING",
93+
environmentId: p.runtimeEnvironmentId,
94+
environmentType: "PRODUCTION",
95+
projectId: p.projectId,
96+
organizationId: p.organizationId,
97+
},
98+
};
99+
}
100+
101+
describe("run-ops split — getExecutionSnapshotsSince delivers a NEW run's completed waitpoints on resume", () => {
102+
heteroRunOpsPostgresTest(
103+
"the resumed snapshot carries the NEW-resident completed waitpoint (managed snapshots.since path)",
104+
async ({ prisma14, prisma17 }: { prisma14: PrismaClient; prisma17: RunOpsPrismaClient }) => {
105+
const router = makeRouter(prisma14, prisma17);
106+
const runId = `run_${RUN_OPS_A}`;
107+
const env = await seedControlPlaneEnv(prisma14, "sincepath");
108+
109+
await router.createRun(
110+
buildCreateRunInput({
111+
runId,
112+
organizationId: env.organization.id,
113+
projectId: env.project.id,
114+
runtimeEnvironmentId: env.environment.id,
115+
})
116+
);
117+
const since = await router.findLatestExecutionSnapshot(runId); // RUN_CREATED, the "since"
118+
119+
// The resumed PENDING_EXECUTING snapshot (its id is a cuid). Space it past `since` in time.
120+
await sleep(10);
121+
const latest = await router.createExecutionSnapshot(
122+
{
123+
run: { id: runId, status: "EXECUTING", attemptNumber: 1 },
124+
snapshot: { executionStatus: "PENDING_EXECUTING", description: "resumed" },
125+
previousSnapshotId: since!.id,
126+
environmentId: env.environment.id,
127+
environmentType: "PRODUCTION",
128+
projectId: env.project.id,
129+
organizationId: env.organization.id,
130+
},
131+
prisma14
132+
);
133+
134+
// A completed waitpoint on #new, joined to the resumed snapshot (as completeWaitpoint would).
135+
const waitpointId = `waitpoint_${RUN_OPS_A}`;
136+
await prisma17.waitpoint.create({
137+
data: {
138+
id: waitpointId,
139+
friendlyId: "wp_since",
140+
type: "MANUAL",
141+
status: "COMPLETED",
142+
completedAt: new Date(),
143+
idempotencyKey: `idem_${waitpointId}`,
144+
userProvidedIdempotencyKey: false,
145+
projectId: env.project.id,
146+
environmentId: env.environment.id,
147+
},
148+
});
149+
await prisma17.completedWaitpoint.create({
150+
data: { snapshotId: latest.id, waitpointId },
151+
});
152+
153+
const snapshots = await getExecutionSnapshotsSince(prisma14, runId, since!.id, router);
154+
const resumed = snapshots.find((s) => s.id === latest.id);
155+
156+
// RED: findSnapshotCompletedWaitpointIds(latest.id cuid) -> #legacy -> [] -> resumed run hangs.
157+
// GREEN: fan-out finds the #new join -> the resumed snapshot carries the completed waitpoint.
158+
expect(resumed?.completedWaitpoints.map((w) => w.id)).toEqual([waitpointId]);
159+
}
160+
);
161+
});

0 commit comments

Comments
 (0)