@@ -13,6 +13,21 @@ vi.setConfig({ testTimeout: 60_000 });
1313// 25-char cuid body → LEGACY residency. 26-char v1 body (version "1" at index 25) → NEW residency.
1414const LEGACY_RUN_ID = "run_" + "a" . repeat ( 25 ) ;
1515const NEW_RUN_ID = "run_" + "b" . repeat ( 24 ) + "01" ;
16+ // 26-char gen-2 body: shard char at index 24, version "2" at index 25.
17+ const SHARD_A_RUN_ID = "run_" + "c" . repeat ( 24 ) + "a2" ;
18+ const SHARD_Z_RUN_ID = "run_" + "c" . repeat ( 24 ) + "z2" ;
19+ const LEGACY_WAITPOINT_ID = "waitpoint_" + "d" . repeat ( 25 ) ;
20+
21+ function throwingClient ( label : string ) {
22+ return vi . fn ( async ( ) : Promise < { marker : number } | null > => {
23+ throw new Error ( `${ label } must never be read` ) ;
24+ } ) ;
25+ }
26+
27+ function collectingLogger ( ) {
28+ const errors : { message : string ; meta ?: unknown } [ ] = [ ] ;
29+ return { errors, error : ( message : string , meta ?: unknown ) => errors . push ( { message, meta } ) } ;
30+ }
1631
1732// Lightweight real read: a trivial `$queryRaw` that genuinely hits the given container.
1833// `hit` controls whether the read "finds" the run, so we exercise routing without
@@ -28,14 +43,7 @@ async function realRead(
2843// A presenter-shaped mapping: both "not-found" and "past-retention" collapse to the
2944// same 404-ish surface, so an old run after termination yields the normal response.
3045function toHttpish < T > ( result : ReadThroughResult < T > ) : { status : number ; value ?: T } {
31- switch ( result . source ) {
32- case "new" :
33- case "legacy-replica" :
34- return { status : 200 , value : result . value } ;
35- case "not-found" :
36- case "past-retention" :
37- return { status : 404 } ;
38- }
46+ return result . found ? { status : 200 , value : result . value } : { status : 404 } ;
3947}
4048
4149describe ( "readThroughRun (legacy replica + new DB)" , ( ) => {
@@ -46,7 +54,8 @@ describe("readThroughRun (legacy replica + new DB)", () => {
4654 // read resolving through `legacyReplica` (prisma14) IS the structural guarantee
4755 // that the primary is never touched.
4856 const result = await readThroughRun ( {
49- runId : LEGACY_RUN_ID ,
57+ id : LEGACY_RUN_ID ,
58+ idKind : "run" ,
5059 environmentId : "env_1" ,
5160 readNew : ( c ) => realRead ( c , false ) ,
5261 readLegacy : ( c ) => realRead ( c , true ) ,
@@ -57,7 +66,7 @@ describe("readThroughRun (legacy replica + new DB)", () => {
5766 } ,
5867 } ) ;
5968
60- expect ( result . source ) . toBe ( "legacy-replica" ) ;
69+ expect ( result . found && result . source ) . toBe ( "legacy-replica" ) ;
6170 expect ( toHttpish ( result ) . status ) . toBe ( 200 ) ;
6271 }
6372 ) ;
@@ -66,7 +75,8 @@ describe("readThroughRun (legacy replica + new DB)", () => {
6675 "post-termination past-retention returns the normal not-found surface" ,
6776 async ( { prisma14, prisma17 } ) => {
6877 const pastRetentionResult = await readThroughRun ( {
69- runId : LEGACY_RUN_ID ,
78+ id : LEGACY_RUN_ID ,
79+ idKind : "run" ,
7080 environmentId : "env_1" ,
7181 readNew : ( c ) => realRead ( c , false ) ,
7282 readLegacy : ( c ) => realRead ( c , false ) , // legacy gone / retention elapsed
@@ -78,11 +88,12 @@ describe("readThroughRun (legacy replica + new DB)", () => {
7888 } ,
7989 } ) ;
8090
81- expect ( pastRetentionResult . source ) . toBe ( "past-retention" ) ;
91+ expect ( pastRetentionResult . found === false && pastRetentionResult . reason ) . toBe ( "past-retention" ) ;
8292
8393 // A run that is simply absent (not past retention) yields not-found.
8494 const notFoundResult = await readThroughRun ( {
85- runId : LEGACY_RUN_ID ,
95+ id : LEGACY_RUN_ID ,
96+ idKind : "run" ,
8697 environmentId : "env_1" ,
8798 readNew : ( c ) => realRead ( c , false ) ,
8899 readLegacy : ( c ) => realRead ( c , false ) ,
@@ -94,7 +105,7 @@ describe("readThroughRun (legacy replica + new DB)", () => {
94105 } ,
95106 } ) ;
96107
97- expect ( notFoundResult . source ) . toBe ( "not-found" ) ;
108+ expect ( notFoundResult . found === false && notFoundResult . reason ) . toBe ( "not-found" ) ;
98109 // Both collapse to the same 404-ish surface.
99110 expect ( toHttpish ( pastRetentionResult ) . status ) . toBe ( toHttpish ( notFoundResult ) . status ) ;
100111 expect ( toHttpish ( pastRetentionResult ) . status ) . toBe ( 404 ) ;
@@ -110,7 +121,8 @@ describe("readThroughRun (legacy replica + new DB)", () => {
110121 const newRead = vi . fn ( ( c : PrismaReplicaClient ) => realRead ( c , true ) ) ;
111122
112123 const result = await readThroughRun ( {
113- runId : LEGACY_RUN_ID ,
124+ id : LEGACY_RUN_ID ,
125+ idKind : "run" ,
114126 environmentId : "env_1" ,
115127 readNew : newRead ,
116128 readLegacy : throwingLegacy ,
@@ -121,7 +133,7 @@ describe("readThroughRun (legacy replica + new DB)", () => {
121133 } ,
122134 } ) ;
123135
124- expect ( result . source ) . toBe ( "new" ) ;
136+ expect ( result . found && result . source ) . toBe ( "new" ) ;
125137 expect ( newRead ) . toHaveBeenCalledTimes ( 1 ) ;
126138 expect ( throwingLegacy ) . not . toHaveBeenCalled ( ) ;
127139 }
@@ -135,7 +147,152 @@ describe("readThroughRun (legacy replica + new DB)", () => {
135147 } ) ;
136148
137149 const result = await readThroughRun ( {
138- runId : NEW_RUN_ID ,
150+ id : NEW_RUN_ID ,
151+ idKind : "run" ,
152+ environmentId : "env_1" ,
153+ readNew : ( c ) => realRead ( c , true ) ,
154+ readLegacy : throwingLegacy ,
155+ deps : {
156+ splitEnabled : true ,
157+ newClient : prisma17 as unknown as PrismaReplicaClient ,
158+ legacyReplica : prisma14 as unknown as PrismaReplicaClient ,
159+ } ,
160+ } ) ;
161+
162+ expect ( result . found && result . source ) . toBe ( "new" ) ;
163+ expect ( throwingLegacy ) . not . toHaveBeenCalled ( ) ;
164+ }
165+ ) ;
166+
167+ heteroPostgresTest (
168+ "gen-2 id reads its OWN shard replica once and probes no other store" ,
169+ async ( { prisma14, prisma17 } ) => {
170+ const throwingNew = throwingClient ( "the gen-1 new store" ) ;
171+ const throwingLegacy = throwingClient ( "the legacy replica" ) ;
172+ const shardRead = vi . fn ( ( c : PrismaReplicaClient ) => realRead ( c , true ) ) ;
173+
174+ const result = await readThroughRun ( {
175+ id : SHARD_A_RUN_ID ,
176+ idKind : "run" ,
177+ environmentId : "env_1" ,
178+ // One closure serves both the gen-1 new store and a shard: a shard is the same
179+ // dedicated schema. The throwing clients prove WHICH client it was handed.
180+ readNew : ( c ) => shardRead ( c ) ,
181+ readLegacy : throwingLegacy ,
182+ deps : {
183+ splitEnabled : true ,
184+ newClient : throwingNew as unknown as PrismaReplicaClient ,
185+ legacyReplica : prisma14 as unknown as PrismaReplicaClient ,
186+ shardReplicas : new Map ( [ [ "a" , prisma17 as unknown as PrismaReplicaClient ] ] ) ,
187+ } ,
188+ } ) ;
189+
190+ expect ( result . found && result . source ) . toBe ( "shard:a" ) ;
191+ expect ( shardRead ) . toHaveBeenCalledTimes ( 1 ) ;
192+ // Identity, not deep equality: a Prisma client is too large to deep-compare.
193+ expect ( shardRead . mock . calls [ 0 ] [ 0 ] ) . toBe ( prisma17 ) ;
194+ expect ( throwingLegacy ) . not . toHaveBeenCalled ( ) ;
195+ }
196+ ) ;
197+
198+ heteroPostgresTest (
199+ "gen-2 id on an UNCONFIGURED shard key logs an error and returns not-found, never throws" ,
200+ async ( { prisma14, prisma17 } ) => {
201+ const logger = collectingLogger ( ) ;
202+ const throwingLegacy = throwingClient ( "the legacy replica" ) ;
203+ const newRead = vi . fn ( ( c : PrismaReplicaClient ) => realRead ( c , true ) ) ;
204+
205+ // Shard "z" is not configured. A 500 here would be inducible by any caller that
206+ // guesses a shard char, so the layer must degrade rather than throw.
207+ const result = await readThroughRun ( {
208+ id : SHARD_Z_RUN_ID ,
209+ idKind : "run" ,
210+ environmentId : "env_1" ,
211+ readNew : newRead ,
212+ readLegacy : throwingLegacy ,
213+ deps : {
214+ splitEnabled : true ,
215+ newClient : prisma17 as unknown as PrismaReplicaClient ,
216+ legacyReplica : prisma14 as unknown as PrismaReplicaClient ,
217+ shardReplicas : new Map ( [ [ "a" , prisma17 as unknown as PrismaReplicaClient ] ] ) ,
218+ logger,
219+ } ,
220+ } ) ;
221+
222+ expect ( result . found ) . toBe ( false ) ;
223+ expect ( result . found === false && result . reason ) . toBe ( "not-found" ) ;
224+ expect ( logger . errors ) . toHaveLength ( 1 ) ;
225+ expect ( logger . errors [ 0 ] . meta ) . toMatchObject ( { shardKey : "z" , configured : [ "a" ] } ) ;
226+ // It must not silently fall back onto a gen-1 store.
227+ expect ( newRead ) . not . toHaveBeenCalled ( ) ;
228+ expect ( throwingLegacy ) . not . toHaveBeenCalled ( ) ;
229+ }
230+ ) ;
231+
232+ heteroPostgresTest (
233+ "gen-1 RUN id reads the legacy replica only and never probes the new store" ,
234+ async ( { prisma14 } ) => {
235+ const throwingNew = throwingClient ( "the new store" ) ;
236+ const legacyRead = vi . fn ( ( c : PrismaReplicaClient ) => realRead ( c , true ) ) ;
237+
238+ const result = await readThroughRun ( {
239+ id : LEGACY_RUN_ID ,
240+ idKind : "run" ,
241+ environmentId : "env_1" ,
242+ readNew : throwingNew ,
243+ readLegacy : legacyRead ,
244+ deps : {
245+ splitEnabled : true ,
246+ newClient : prisma14 as unknown as PrismaReplicaClient ,
247+ legacyReplica : prisma14 as unknown as PrismaReplicaClient ,
248+ } ,
249+ } ) ;
250+
251+ expect ( result . found && result . source ) . toBe ( "legacy-replica" ) ;
252+ expect ( throwingNew ) . not . toHaveBeenCalled ( ) ;
253+ expect ( legacyRead ) . toHaveBeenCalledTimes ( 1 ) ;
254+ }
255+ ) ;
256+
257+ heteroPostgresTest (
258+ "cuid WAITPOINT id keeps the new-FIRST pair probe (frozen: cuid waitpoints co-locate on new)" ,
259+ async ( { prisma14, prisma17 } ) => {
260+ const calls : string [ ] = [ ] ;
261+ const newRead = vi . fn ( async ( c : PrismaReplicaClient ) => {
262+ calls . push ( "new" ) ;
263+ return realRead ( c , false ) ;
264+ } ) ;
265+ const legacyRead = vi . fn ( async ( c : PrismaReplicaClient ) => {
266+ calls . push ( "legacy" ) ;
267+ return realRead ( c , true ) ;
268+ } ) ;
269+
270+ const result = await readThroughRun ( {
271+ id : LEGACY_WAITPOINT_ID ,
272+ idKind : "waitpoint" ,
273+ environmentId : "env_1" ,
274+ readNew : newRead ,
275+ readLegacy : legacyRead ,
276+ deps : {
277+ splitEnabled : true ,
278+ newClient : prisma17 as unknown as PrismaReplicaClient ,
279+ legacyReplica : prisma14 as unknown as PrismaReplicaClient ,
280+ } ,
281+ } ) ;
282+
283+ expect ( result . found && result . source ) . toBe ( "legacy-replica" ) ;
284+ expect ( calls ) . toEqual ( [ "new" , "legacy" ] ) ;
285+ }
286+ ) ;
287+
288+ heteroPostgresTest (
289+ "a cuid waitpoint found on the new store returns it without touching legacy" ,
290+ async ( { prisma14, prisma17 } ) => {
291+ const throwingLegacy = throwingClient ( "the legacy replica" ) ;
292+
293+ const result = await readThroughRun ( {
294+ id : LEGACY_WAITPOINT_ID ,
295+ idKind : "waitpoint" ,
139296 environmentId : "env_1" ,
140297 readNew : ( c ) => realRead ( c , true ) ,
141298 readLegacy : throwingLegacy ,
@@ -146,7 +303,7 @@ describe("readThroughRun (legacy replica + new DB)", () => {
146303 } ,
147304 } ) ;
148305
149- expect ( result . source ) . toBe ( "new" ) ;
306+ expect ( result . found && result . source ) . toBe ( "new" ) ;
150307 expect ( throwingLegacy ) . not . toHaveBeenCalled ( ) ;
151308 }
152309 ) ;
0 commit comments