@@ -23,18 +23,37 @@ function fakePrisma(rows: Array<TaskRun | null>): PrismaStub {
2323 return { taskRun : { findFirst : fn } } ;
2424}
2525
26+ // Env-matching entry returned by the env-pre-check getEntry call that
27+ // mutateWithFallback now does before any buffer write (cross-env auth
28+ // gate). Same envId/orgId as `baseInput` so the check passes and the
29+ // flow under test proceeds to mutateSnapshot.
30+ const preCheckEntry = ( ) : BufferEntry =>
31+ ( {
32+ envId : "env_a" ,
33+ orgId : "org_1" ,
34+ status : "QUEUED" ,
35+ materialised : false ,
36+ } ) as unknown as BufferEntry ;
37+
2638function bufferReturning ( result : MutateSnapshotResult ) : MollifierBuffer {
39+ const getEntry = vi . fn ( async ( ) => preCheckEntry ( ) ) ;
2740 return {
2841 mutateSnapshot : vi . fn ( async ( ) => result ) ,
29- getEntry : vi . fn ( async ( ) => null ) ,
42+ getEntry,
3043 } as unknown as MollifierBuffer ;
3144}
3245
3346// Buffer whose mutateSnapshot returns "busy" and whose getEntry walks a
34- // scripted sequence of entry states (the drainer's progress). The last
35- // element repeats once the sequence is exhausted.
47+ // scripted sequence of entry states. The pre-check getEntry call (one
48+ // extra read before the busy-wait loop, used for env authorization)
49+ // consumes the first scripted result, then the busy-wait loop pops the
50+ // remainder; the last element repeats once the sequence is exhausted.
3651function bufferBusy ( entries : Array < BufferEntry | null > ) : MollifierBuffer {
3752 const getEntry = vi . fn ( ) ;
53+ // Pre-check consumes one entry. Use a QUEUED env-matching entry so
54+ // the env-check passes and the flow reaches mutateSnapshot (which
55+ // returns "busy") and enters the wait-loop.
56+ getEntry . mockResolvedValueOnce ( preCheckEntry ( ) ) ;
3857 for ( const e of entries ) getEntry . mockResolvedValueOnce ( e ) ;
3958 getEntry . mockResolvedValue ( entries . length ? entries [ entries . length - 1 ] : null ) ;
4059 return {
@@ -44,11 +63,26 @@ function bufferBusy(entries: Array<BufferEntry | null>): MollifierBuffer {
4463}
4564
4665const entryDraining = ( ) : BufferEntry =>
47- ( { status : "DRAINING" , materialised : false } ) as unknown as BufferEntry ;
66+ ( {
67+ envId : "env_a" ,
68+ orgId : "org_1" ,
69+ status : "DRAINING" ,
70+ materialised : false ,
71+ } ) as unknown as BufferEntry ;
4872const entryQueued = ( ) : BufferEntry =>
49- ( { status : "QUEUED" , materialised : false } ) as unknown as BufferEntry ;
73+ ( {
74+ envId : "env_a" ,
75+ orgId : "org_1" ,
76+ status : "QUEUED" ,
77+ materialised : false ,
78+ } ) as unknown as BufferEntry ;
5079const entryMaterialised = ( ) : BufferEntry =>
51- ( { status : "DRAINING" , materialised : true } ) as unknown as BufferEntry ;
80+ ( {
81+ envId : "env_a" ,
82+ orgId : "org_1" ,
83+ status : "DRAINING" ,
84+ materialised : true ,
85+ } ) as unknown as BufferEntry ;
5286
5387const fakeRun = ( overrides : Partial < TaskRun > = { } ) : TaskRun =>
5488 ( {
@@ -150,8 +184,9 @@ describe("mutateWithFallback", () => {
150184 } ) ;
151185 expect ( result ) . toEqual ( { kind : "pg" , response : "pg-after-wait" } ) ;
152186 expect ( pgMutation ) . toHaveBeenCalledWith ( row ) ;
153- // Detection happened against Redis (3 polls), the primary exactly once.
154- expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 3 ) ;
187+ // One env-pre-check call + 3 busy-wait polls = 4 getEntry reads;
188+ // primary read exactly once.
189+ expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 4 ) ;
155190 expect ( writer . taskRun . findFirst ) . toHaveBeenCalledTimes ( 1 ) ;
156191 } ) ;
157192
@@ -227,7 +262,8 @@ describe("mutateWithFallback", () => {
227262 random : ( ) => 0 ,
228263 } ) ;
229264 expect ( result ) . toEqual ( { kind : "pg" , response : "pg-after-requeue" } ) ;
230- expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 3 ) ;
265+ // One env-pre-check + 3 busy-wait polls.
266+ expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 4 ) ;
231267 expect ( writer . taskRun . findFirst ) . toHaveBeenCalledTimes ( 1 ) ;
232268 } ) ;
233269
@@ -278,8 +314,8 @@ describe("mutateWithFallback", () => {
278314 abortSignal : controller . signal ,
279315 } ) ;
280316 expect ( result ) . toEqual ( { kind : "timed_out" } ) ;
281- // One buffer poll happened before the sleep+abort; primary untouched.
282- expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 1 ) ;
317+ // One env-pre-check + one busy-wait poll before sleep+abort; primary untouched.
318+ expect ( buffer . getEntry ) . toHaveBeenCalledTimes ( 2 ) ;
283319 expect ( writer . taskRun . findFirst ) . toHaveBeenCalledTimes ( 0 ) ;
284320 } ) ;
285321
@@ -313,6 +349,64 @@ describe("mutateWithFallback", () => {
313349 ) . rejects . toThrow ( / l i m i t _ e x c e e d e d / ) ;
314350 } ) ;
315351
352+ it ( "replica miss + buffer entry belongs to a different env → not_found (cross-env auth gate)" , async ( ) => {
353+ // Same flow as the applied_to_snapshot test, except the entry's
354+ // envId doesn't match input.environmentId. mutateWithFallback must
355+ // refuse the write and return not_found (without leaking that the
356+ // runId exists in another env), and must NOT call mutateSnapshot.
357+ const crossEnvEntry : BufferEntry = {
358+ envId : "env_OTHER" ,
359+ orgId : "org_1" ,
360+ status : "QUEUED" ,
361+ materialised : false ,
362+ } as unknown as BufferEntry ;
363+ const mutateSnapshot = vi . fn ( async ( ) => "applied_to_snapshot" as const ) ;
364+ const buffer = {
365+ mutateSnapshot,
366+ getEntry : vi . fn ( async ( ) => crossEnvEntry ) ,
367+ } as unknown as MollifierBuffer ;
368+
369+ const pgMutation = vi . fn ( async ( ) => "pg" ) ;
370+ const synthesisedResponse = vi . fn ( ( ) => "snap" ) ;
371+ const result = await mutateWithFallback ( {
372+ ...baseInput ,
373+ pgMutation,
374+ synthesisedResponse,
375+ prismaReplica : fakePrisma ( [ null ] ) as unknown as typeof import ( "~/db.server" ) . $replica ,
376+ prismaWriter : fakePrisma ( [ ] ) as unknown as typeof import ( "~/db.server" ) . prisma ,
377+ getBuffer : ( ) => buffer ,
378+ } ) ;
379+ expect ( result ) . toEqual ( { kind : "not_found" } ) ;
380+ expect ( mutateSnapshot ) . not . toHaveBeenCalled ( ) ;
381+ expect ( pgMutation ) . not . toHaveBeenCalled ( ) ;
382+ expect ( synthesisedResponse ) . not . toHaveBeenCalled ( ) ;
383+ } ) ;
384+
385+ it ( "replica miss + buffer entry belongs to a different org → not_found (cross-org auth gate)" , async ( ) => {
386+ const crossOrgEntry : BufferEntry = {
387+ envId : "env_a" ,
388+ orgId : "org_OTHER" ,
389+ status : "QUEUED" ,
390+ materialised : false ,
391+ } as unknown as BufferEntry ;
392+ const mutateSnapshot = vi . fn ( async ( ) => "applied_to_snapshot" as const ) ;
393+ const buffer = {
394+ mutateSnapshot,
395+ getEntry : vi . fn ( async ( ) => crossOrgEntry ) ,
396+ } as unknown as MollifierBuffer ;
397+
398+ const result = await mutateWithFallback ( {
399+ ...baseInput ,
400+ pgMutation : async ( ) => "pg" ,
401+ synthesisedResponse : ( ) => "snap" ,
402+ prismaReplica : fakePrisma ( [ null ] ) as unknown as typeof import ( "~/db.server" ) . $replica ,
403+ prismaWriter : fakePrisma ( [ ] ) as unknown as typeof import ( "~/db.server" ) . prisma ,
404+ getBuffer : ( ) => buffer ,
405+ } ) ;
406+ expect ( result ) . toEqual ( { kind : "not_found" } ) ;
407+ expect ( mutateSnapshot ) . not . toHaveBeenCalled ( ) ;
408+ } ) ;
409+
316410 it ( "buffer is null (mollifier disabled) → not_found after replica miss" , async ( ) => {
317411 const result = await mutateWithFallback ( {
318412 ...baseInput ,
0 commit comments