11import assert from "node:assert/strict" ;
22import test from "node:test" ;
3+ import { randomUUID } from "node:crypto" ;
34import { mkdtemp , rm , readdir , writeFile , rename } from "node:fs/promises" ;
45import { tmpdir } from "node:os" ;
56import { join } from "node:path" ;
7+ import { Pool } from "pg" ;
68import type { JsonObject } from "../../loopx/control_plane/effect_program.ts" ;
79import { FileAuthorityStore } from "../../loopx/control_plane/coordination/file_authority_store.ts" ;
810import { SqliteAuthorityStore } from "../../loopx/control_plane/coordination/sqlite_authority_store.ts" ;
11+ import { PostgreSqlAuthorityStore , installPostgreSqlAuthorityStoreSchema } from "../../loopx/control_plane/coordination/postgresql_authority_store.ts" ;
912import { sqliteRuntimeIdentity } from "../../loopx/control_plane/coordination/sqlite_runtime.ts" ;
1013import { coordinationTodoReadModel } from "../../loopx/control_plane/coordination/coordination_projection.ts" ;
1114import { readCanonicalSnapshotFromStore , readCanonicalSnapshotPage , readCanonicalSnapshot ,
@@ -29,35 +32,49 @@ async function fixture(t: test.TestContext, kind: "file" | "sqlite" = "file") {
2932 const Store = kind === "file" ? FileAuthorityStore : SqliteAuthorityStore ;
3033 return { store : new Store ( root , "goal-a" ) , contender : new Store ( root , "goal-a" ) } ;
3134}
35+ async function postgresqlFixture ( t : test . TestContext ) : Promise < AuthorityStore > {
36+ const pool = new Pool ( { connectionString : process . env . LOOPX_TEST_POSTGRES_URL , max : 2 } ) ;
37+ t . after ( ( ) => pool . end ( ) ) ;
38+ const database = { connect : async ( ) => {
39+ const client = await pool . connect ( ) ;
40+ return { query : async ( sql : string , values ?: readonly unknown [ ] ) => client . query ( sql , values ? [ ...values ] : undefined ) ,
41+ release : ( ) => client . release ( ) } ;
42+ } } ;
43+ await installPostgreSqlAuthorityStoreSchema ( database , `postgresql:${ "b" . repeat ( 32 ) } ` ) ;
44+ return new PostgreSqlAuthorityStore ( database , { tenant_id : `snapshot-${ randomUUID ( ) } ` , goal_id : "goal-a" } ) ;
45+ }
46+ async function verifyLeaseSelection ( store : AuthorityStore ) : Promise < void > {
47+ const { projection} = productionScaleCoordinationFixture ( "goal-a" , "native" ) ;
48+ await seedSnapshot ( store , projection ) ;
49+ const before = await store . loadAuthority ( ) ;
50+ const full = await readCanonicalSnapshotFromStore ( snapshotRequest ( ) , store , CANONICAL_SNAPSHOT_LOCAL_BYTES ) ;
51+ const leases = projection . leases as JsonObject [ ] ;
52+ const selected = [ leases . find ( lease => lease . status === "active" ) ! , leases . find ( lease => lease . status === "released" ) ! ] ;
53+ const ids = [ ...selected . map ( lease => lease . todo_id ) , "todo_without_lease" ] ;
54+ const request = snapshotRequest ( { lease_todo_ids : ids } ) ;
55+ const page = await readCanonicalSnapshotFromStore ( request , store ) ;
56+ assert . equal ( page . status , "page" ) ;
57+ assert . deepEqual ( page . todos , [ ] ) ;
58+ assert . deepEqual ( page . leases , ( full . leases as JsonObject [ ] ) . filter ( lease => ids . includes ( lease . todo_id ) ) ) ;
59+ assert . equal ( ( page . leases as JsonObject [ ] ) . length , 2 ) ;
60+ const snapshot = page . snapshot as JsonObject ;
61+ assert . equal ( snapshot . provider_revision , ( full . snapshot as JsonObject ) . provider_revision ) ;
62+ assert . equal ( snapshot . store_identity , ( full . snapshot as JsonObject ) . store_identity ) ;
63+ assert . notEqual ( snapshot . query_sha256 , ( full . snapshot as JsonObject ) . query_sha256 ) ;
64+ const after = { snapshot, todo_offset : 0 , lease_offset : 1 } ;
65+ const last = await readCanonicalSnapshotFromStore ( { ...request , lease_todo_ids : ids . toReversed ( ) , after} , store ) ;
66+ assert . deepEqual ( last . leases , ( page . leases as JsonObject [ ] ) . slice ( 1 ) ) ;
67+ await assert . rejects ( readCanonicalSnapshotFromStore ( { ...request ,
68+ lease_todo_ids : [ ...ids . slice ( 0 , 2 ) , "todo_different_missing_lease" ] , after} , store ) , / c a n o n i c a l s n a p s h o t c h a n g e d / ) ;
69+ await assert . rejects ( readCanonicalSnapshotFromStore ( { ...request , include_leases : false } , store ) ,
70+ / l e a s e s e l e c t i o n r e q u i r e s / ) ;
71+ assert . deepEqual ( await store . loadAuthority ( ) , before ) ;
72+ }
3273const qualifiedSqlite = sqliteRuntimeIdentity ( ) . sqlite_authority_qualified ;
3374for ( const provider of ( qualifiedSqlite ? [ "file" , "sqlite" ] : [ "file" ] ) as ( "file" | "sqlite" ) [ ] ) {
3475 test ( `${ provider } : lease selection preserves matching lease records and binds continuation scope` , async t => {
3576 const { store} = await fixture ( t , provider ) ;
36- const { projection} = productionScaleCoordinationFixture ( "goal-a" , "native" ) ;
37- await seedSnapshot ( store , projection ) ;
38- const before = await store . loadAuthority ( ) ;
39- const full = await readCanonicalSnapshotFromStore ( snapshotRequest ( ) , store , CANONICAL_SNAPSHOT_LOCAL_BYTES ) ;
40- const leases = projection . leases as JsonObject [ ] ;
41- const selected = [ leases . find ( lease => lease . status === "active" ) ! , leases . find ( lease => lease . status === "released" ) ! ] ;
42- const ids = [ ...selected . map ( lease => lease . todo_id ) , "todo_without_lease" ] ;
43- const request = snapshotRequest ( { lease_todo_ids : ids } ) ;
44- const page = await readCanonicalSnapshotFromStore ( request , store ) ;
45- assert . equal ( page . status , "page" ) ;
46- assert . deepEqual ( page . todos , [ ] ) ;
47- assert . deepEqual ( page . leases , ( full . leases as JsonObject [ ] ) . filter ( lease => ids . includes ( lease . todo_id ) ) ) ;
48- assert . equal ( ( page . leases as JsonObject [ ] ) . length , 2 ) ;
49- const snapshot = page . snapshot as JsonObject ;
50- assert . equal ( snapshot . provider_revision , ( full . snapshot as JsonObject ) . provider_revision ) ;
51- assert . equal ( snapshot . store_identity , ( full . snapshot as JsonObject ) . store_identity ) ;
52- assert . notEqual ( snapshot . query_sha256 , ( full . snapshot as JsonObject ) . query_sha256 ) ;
53- const after = { snapshot, todo_offset : 0 , lease_offset : 1 } ;
54- const last = await readCanonicalSnapshotFromStore ( { ...request , lease_todo_ids : ids . toReversed ( ) , after} , store ) ;
55- assert . deepEqual ( last . leases , ( page . leases as JsonObject [ ] ) . slice ( 1 ) ) ;
56- await assert . rejects ( readCanonicalSnapshotFromStore ( { ...request ,
57- lease_todo_ids : [ ...ids . slice ( 0 , 2 ) , "todo_different_missing_lease" ] , after} , store ) , / c a n o n i c a l s n a p s h o t c h a n g e d / ) ;
58- await assert . rejects ( readCanonicalSnapshotFromStore ( { ...request , include_leases : false } , store ) ,
59- / l e a s e s e l e c t i o n r e q u i r e s / ) ;
60- assert . deepEqual ( await store . loadAuthority ( ) , before ) ;
77+ await verifyLeaseSelection ( store ) ;
6178 } ) ;
6279 for ( const defect of [ "manifest" , "digest" ] as const ) {
6380 test ( `${ provider } : invalid projection is distinct from an invalid snapshot request (${ defect } )` , async t => {
@@ -90,6 +107,10 @@ for (const provider of (qualifiedSqlite ? ["file", "sqlite"] : ["file"]) as ("fi
90107 } ) ;
91108 }
92109}
110+ test ( "postgresql: lease selection preserves matching lease records and binds continuation scope" ,
111+ { skip : ! process . env . LOOPX_TEST_POSTGRES_URL } , async t => {
112+ await verifyLeaseSelection ( await postgresqlFixture ( t ) ) ;
113+ } ) ;
93114if ( qualifiedSqlite ) {
94115 test ( "selected SQLite snapshot retains the complete independent read contract" , async t => {
95116 const { store} = await fixture ( t , "sqlite" ) ;
0 commit comments