1- // Property: the metadata GET loader resolves a live run on a replica+buffer double-miss via its primary
2- // fallback, returning 200 + the run's metadata. Drives the REAL exported `loader` end-to-end:
3- // 1. runStore.findRun(..., $replica) → REPLICA (lagging) → null
4- // 2. findRunByIdWithMollifierFallback(...) → buffer MISS (null)
5- // 3. runStore.findRunOnPrimary(...) → owning PRIMARY → HIT
6- // Store is REAL: a split RoutingRunStore over two testcontainer Postgres DBs, the owning
7- // (legacy/control-plane) REPLICA frozen behind the shared laggingReplica. Only the loader's webapp
8- // singletons (auth, $replica brand, mollifier buffer, route builder, logging) are stubbed.
9-
10- import { describe , expect , vi } from "vitest" ;
11- import { heteroRunOpsPostgresTest , laggingReplica } from "@internal/testcontainers" ;
12- import type { PrismaClient } from "@trigger.dev/database" ;
13- import type { RunOpsPrismaClient } from "@internal/run-ops-database" ;
14- import { PostgresRunStore , RoutingRunStore } from "@internal/run-store" ;
15- import type { CreateRunInput } from "@internal/run-store" ;
16-
17- // vi.mock factories are hoisted above the imports, so anything they reference must be created inside
18- // vi.hoisted. `holder.store` is filled in per-test with the REAL split router; the mocked
19- // `~/v3/runStore.server` export is a stable Proxy that forwards every property access to it. The
20- // branded `$replica` marker uses the global-registry symbol the run-store brands replicas with
21- // (readReplicaClient.ts) — a branded client makes the routing store keep the read on the owning
22- // REPLICA (no primary escalation), which under lag is exactly the miss the primary fallback recovers.
23- const { holder } = vi . hoisted ( ( ) => {
24- const REPLICA_BRAND = Symbol . for ( "trigger.dev/run-store/read-replica" ) ;
25- return {
26- holder : {
27- store : undefined as unknown ,
28- brandedReplica : { [ REPLICA_BRAND ] : true } as object ,
29- environment : undefined as unknown ,
30- bufferResult : null as unknown ,
31- } ,
32- } ;
33- } ) ;
1+ import { beforeEach , describe , expect , it , vi } from "vitest" ;
2+
3+ const { mocks } = vi . hoisted ( ( ) => ( {
4+ mocks : {
5+ replica : { } ,
6+ environment : { id : "env_meta" , organizationId : "org_meta" } ,
7+ authenticateApiRequest : vi . fn ( ) ,
8+ findRun : vi . fn ( ) ,
9+ findRunOnPrimary : vi . fn ( ) ,
10+ findRunByIdWithMollifierFallback : vi . fn ( ) ,
11+ } ,
12+ } ) ) ;
3413
35- vi . mock ( "~/db.server" , ( ) => ( { prisma : { } , $replica : holder . brandedReplica } ) ) ;
14+ vi . mock ( "~/db.server" , ( ) => ( { prisma : { } , $replica : mocks . replica } ) ) ;
3615vi . mock ( "~/env.server" , ( ) => ( {
3716 env : {
3817 TASK_RUN_METADATA_MAXIMUM_SIZE : 256 * 1024 ,
@@ -41,42 +20,30 @@ vi.mock("~/env.server", () => ({
4120 TRIGGER_MOLLIFIER_METADATA_BACKOFF_STEP_MS : 10 ,
4221 } ,
4322} ) ) ;
44- // The route module builds its action at import time via createActionApiRoute; stub the builder so the
45- // heavy platform/auth middleware graph never evaluates. We drive the exported `loader` directly.
4623vi . mock ( "~/services/routeBuilders/apiBuilder.server" , ( ) => ( {
4724 createActionApiRoute : ( ) => ( { action : vi . fn ( ) } ) ,
4825} ) ) ;
4926vi . mock ( "~/services/apiAuth.server" , ( ) => ( {
50- authenticateApiRequest : vi . fn ( async ( ) => ( { environment : holder . environment } ) ) ,
27+ authenticateApiRequest : mocks . authenticateApiRequest ,
5128} ) ) ;
52- // Inject the REAL split router. A stable Proxy keeps the named import binding constant while
53- // forwarding every method to the per-test router set in holder.store.
5429vi . mock ( "~/v3/runStore.server" , ( ) => ( {
55- runStore : new Proxy (
56- { } ,
57- {
58- get ( _target , prop ) {
59- const store = holder . store as Record < string | symbol , unknown > ;
60- if ( ! store ) throw new Error ( "test bug: holder.store not initialised before loader ran" ) ;
61- const value = store [ prop ] ;
62- return typeof value === "function"
63- ? ( value as ( ...a : unknown [ ] ) => unknown ) . bind ( store )
64- : value ;
65- } ,
66- }
67- ) ,
30+ runStore : {
31+ findRun : mocks . findRun ,
32+ findRunOnPrimary : mocks . findRunOnPrimary ,
33+ } ,
6834} ) ) ;
69- // Buffer fallback returns whatever the test set (null = buffer miss, the double-miss scenario).
7035vi . mock ( "~/v3/mollifier/readFallback.server" , ( ) => ( {
71- findRunByIdWithMollifierFallback : vi . fn ( async ( ) => holder . bufferResult ) ,
36+ findRunByIdWithMollifierFallback : mocks . findRunByIdWithMollifierFallback ,
7237} ) ) ;
73- // Action-only leaf; stub to keep the import graph light.
7438vi . mock ( "~/v3/mollifier/applyMetadataMutation.server" , ( ) => ( {
7539 applyMetadataMutationToBufferedRun : vi . fn ( ) ,
7640} ) ) ;
7741vi . mock ( "~/services/metadata/updateMetadataInstance.server" , ( ) => ( {
7842 updateMetadataService : { call : vi . fn ( async ( ) => undefined ) } ,
7943} ) ) ;
44+ vi . mock ( "~/services/realtime/runChangeNotifierInstance.server" , ( ) => ( {
45+ publishChangeRecord : vi . fn ( ) ,
46+ } ) ) ;
8047vi . mock ( "~/v3/services/common.server" , ( ) => ( {
8148 ServiceValidationError : class extends Error { } ,
8249} ) ) ;
@@ -86,176 +53,66 @@ vi.mock("~/services/logger.server", () => ({
8653
8754import { loader } from "~/routes/api.v1.runs.$runId.metadata" ;
8855
89- // A cuid (25 chars after `run_`) classifies LEGACY, so both the create and the friendlyId-keyed reads
90- // route to the legacy (control-plane) store — the store that owns this run.
91- const CUID_25 = "c" . repeat ( 25 ) ;
56+ const friendlyId = "run_meta_live" ;
57+ const runWhere = { friendlyId , runtimeEnvironmentId : mocks . environment . id } ;
58+ const metadataSelect = { select : { metadata : true , metadataType : true } } ;
9259
93- async function seedEnvironmentLegacy ( prisma : PrismaClient , suffix : string ) {
94- const organization = await prisma . organization . create ( {
95- data : { title : `Org ${ suffix } ` , slug : `org-${ suffix } ` } ,
96- } ) ;
97- const project = await prisma . project . create ( {
98- data : {
99- name : `Project ${ suffix } ` ,
100- slug : `project-${ suffix } ` ,
101- externalRef : `proj_${ suffix } ` ,
102- organizationId : organization . id ,
103- } ,
104- } ) ;
105- const environment = await prisma . runtimeEnvironment . create ( {
106- data : {
107- type : "DEVELOPMENT" ,
108- slug : "dev" ,
109- projectId : project . id ,
110- organizationId : organization . id ,
111- apiKey : `tr_dev_${ suffix } ` ,
112- pkApiKey : `pk_dev_${ suffix } ` ,
113- shortcode : `short_${ suffix } ` ,
114- } ,
115- } ) ;
116- return { organization, project, environment } ;
117- }
118-
119- function buildCreateRunInput ( params : {
120- runId : string ;
121- friendlyId : string ;
122- organizationId : string ;
123- projectId : string ;
124- runtimeEnvironmentId : string ;
125- metadata ?: string ;
126- metadataType ?: string ;
127- } ) : CreateRunInput {
128- return {
129- data : {
130- id : params . runId ,
131- engine : "V2" ,
132- status : "PENDING" ,
133- friendlyId : params . friendlyId ,
134- runtimeEnvironmentId : params . runtimeEnvironmentId ,
135- environmentType : "DEVELOPMENT" ,
136- organizationId : params . organizationId ,
137- projectId : params . projectId ,
138- taskIdentifier : "my-task" ,
139- payload : '{"hello":"world"}' ,
140- payloadType : "application/json" ,
141- metadata : params . metadata ,
142- metadataType : params . metadataType ,
143- context : { foo : "bar" } ,
144- traceContext : { trace : "ctx" } ,
145- traceId : "trace_1" ,
146- spanId : "span_1" ,
147- runTags : [ "alpha" ] ,
148- queue : "task/my-task" ,
149- isTest : false ,
150- taskEventStore : "taskEvent" ,
151- depth : 0 ,
152- createdAt : new Date ( "2024-01-01T00:00:00.000Z" ) ,
153- } ,
154- snapshot : {
155- engine : "V2" ,
156- executionStatus : "RUN_CREATED" ,
157- description : "Run was created" ,
158- runStatus : "PENDING" ,
159- environmentId : params . runtimeEnvironmentId ,
160- environmentType : "DEVELOPMENT" ,
161- projectId : params . projectId ,
162- organizationId : params . organizationId ,
163- } ,
164- } ;
165- }
166-
167- function buildRouterWithLaggingLegacyReplica ( prisma14 : PrismaClient , prisma17 : RunOpsPrismaClient ) {
168- const legacyReplica = laggingReplica ( prisma14 , [ { model : "taskRun" , mode : "missing" } ] ) ;
169- const legacyStore = new PostgresRunStore ( {
170- prisma : prisma14 ,
171- readOnlyPrisma : legacyReplica . client ,
172- schemaVariant : "legacy" ,
173- } ) ;
174- const newStore = new PostgresRunStore ( {
175- prisma : prisma17 as never ,
176- readOnlyPrisma : prisma17 as never ,
177- schemaVariant : "dedicated" ,
178- } ) ;
179- const router = new RoutingRunStore ( { new : newStore , legacy : legacyStore } ) ;
180- return { router, legacyStore, legacyReplica } ;
181- }
182-
183- async function callLoader ( friendlyId : string ) {
184- const response = await loader ( {
185- request : new Request ( "https://api.trigger.dev/api/v1/runs/" + friendlyId + "/metadata" , {
60+ async function callLoader ( runId = friendlyId ) {
61+ return ( await loader ( {
62+ request : new Request ( `https://example.com/api/v1/runs/${ runId } /metadata` , {
18663 headers : { Authorization : "Bearer tr_dev_meta" } ,
18764 } ) ,
188- params : { runId : friendlyId } ,
65+ params : { runId } ,
18966 context : { } as never ,
190- } ) ;
191- return response as Response ;
67+ } ) ) as Response ;
19268}
19369
194- describe ( "metadata GET loader under replica lag" , ( ) => {
195- heteroRunOpsPostgresTest (
196- "metadata GET loader resolves a live run on a replica and buffer double-miss" ,
197- async ( { prisma14, prisma17 } ) => {
198- const { router, legacyStore, legacyReplica } = buildRouterWithLaggingLegacyReplica (
199- prisma14 ,
200- prisma17
201- ) ;
202-
203- const seed = await seedEnvironmentLegacy ( prisma14 , "meta" ) ;
204- const runId = `run_${ CUID_25 } ` ; // cuid → LEGACY-owned
205- const friendlyId = "run_meta_live" ;
206- const metadata = '{"phase":"one"}' ;
207- await legacyStore . createRun (
208- buildCreateRunInput ( {
209- runId,
210- friendlyId,
211- organizationId : seed . organization . id ,
212- projectId : seed . project . id ,
213- runtimeEnvironmentId : seed . environment . id ,
214- metadata,
215- metadataType : "application/json" ,
216- } )
217- ) ;
218-
219- // Wire the loader's module singletons: the real split router, the authenticated env, and a
220- // buffer that MISSES (the run has drained from the buffer to the primary but the replica has
221- // not caught up — the exact double-miss under test).
222- holder . store = router ;
223- holder . environment = { id : seed . environment . id , organizationId : seed . organization . id } ;
224- holder . bufferResult = null ;
225-
226- const response = await callLoader ( friendlyId ) ;
227- const body = ( await response . json ( ) ) as {
228- metadata ?: unknown ;
229- metadataType ?: unknown ;
230- error ?: string ;
231- } ;
232-
233- // The replica WAS consulted (and, being frozen, missed) — proving the read genuinely went
234- // through the lagging replica and the recovery is the primary fallback, not a lucky replica hit.
235- expect ( legacyReplica . wasHit ( ) ) . toBe ( true ) ;
236-
237- // The property: the loader re-reads the owning primary and returns the live run's metadata.
238- expect ( response . status ) . toBe ( 200 ) ;
239- expect ( body . metadata ) . toBe ( metadata ) ;
240- expect ( body . metadataType ) . toBe ( "application/json" ) ;
241- }
242- ) ;
70+ beforeEach ( ( ) => {
71+ vi . clearAllMocks ( ) ;
72+ mocks . authenticateApiRequest . mockResolvedValue ( { environment : mocks . environment } ) ;
73+ mocks . findRun . mockResolvedValue ( null ) ;
74+ mocks . findRunByIdWithMollifierFallback . mockResolvedValue ( null ) ;
75+ mocks . findRunOnPrimary . mockResolvedValue ( null ) ;
76+ } ) ;
24377
244- // Negative control: when the run truly does not exist anywhere (replica miss + buffer miss +
245- // primary miss), the loader must still 404. This pins the behavior to "recover a LIVE run" rather
246- // than "never 404".
247- heteroRunOpsPostgresTest (
248- "metadata GET loader 404s when the run is absent on the primary too" ,
249- async ( { prisma14, prisma17 } ) => {
250- const { router } = buildRouterWithLaggingLegacyReplica ( prisma14 , prisma17 ) ;
251- const seed = await seedEnvironmentLegacy ( prisma14 , "meta_absent" ) ;
78+ describe ( "metadata GET loader under replica lag" , ( ) => {
79+ it ( "resolves a live run after a replica and buffer double-miss" , async ( ) => {
80+ const metadata = '{"phase":"one"}' ;
81+ mocks . findRunOnPrimary . mockResolvedValue ( {
82+ metadata,
83+ metadataType : "application/json" ,
84+ } ) ;
85+
86+ const response = await callLoader ( ) ;
87+
88+ expect ( mocks . findRun ) . toHaveBeenCalledWith ( runWhere , metadataSelect , mocks . replica ) ;
89+ expect ( mocks . findRunByIdWithMollifierFallback ) . toHaveBeenCalledWith ( {
90+ runId : friendlyId ,
91+ environmentId : mocks . environment . id ,
92+ organizationId : mocks . environment . organizationId ,
93+ } ) ;
94+ expect ( mocks . findRunOnPrimary ) . toHaveBeenCalledWith ( runWhere , metadataSelect ) ;
95+ expect ( mocks . findRun . mock . invocationCallOrder [ 0 ] ) . toBeLessThan (
96+ mocks . findRunByIdWithMollifierFallback . mock . invocationCallOrder [ 0 ]
97+ ) ;
98+ expect ( mocks . findRunByIdWithMollifierFallback . mock . invocationCallOrder [ 0 ] ) . toBeLessThan (
99+ mocks . findRunOnPrimary . mock . invocationCallOrder [ 0 ]
100+ ) ;
101+ await expect ( response . json ( ) ) . resolves . toEqual ( {
102+ metadata,
103+ metadataType : "application/json" ,
104+ } ) ;
105+ expect ( response . status ) . toBe ( 200 ) ;
106+ } ) ;
252107
253- holder . store = router ;
254- holder . environment = { id : seed . environment . id , organizationId : seed . organization . id } ;
255- holder . bufferResult = null ;
108+ it ( "returns 404 when the run is absent from the primary too" , async ( ) => {
109+ const response = await callLoader ( "run_does_not_exist" ) ;
256110
257- const response = await callLoader ( "run_does_not_exist" ) ;
258- expect ( response . status ) . toBe ( 404 ) ;
259- }
260- ) ;
111+ expect ( mocks . findRunOnPrimary ) . toHaveBeenCalledWith (
112+ { friendlyId : "run_does_not_exist" , runtimeEnvironmentId : mocks . environment . id } ,
113+ metadataSelect
114+ ) ;
115+ await expect ( response . json ( ) ) . resolves . toEqual ( { error : "Run not found" } ) ;
116+ expect ( response . status ) . toBe ( 404 ) ;
117+ } ) ;
261118} ) ;
0 commit comments