11/** Real PostgreSQL claims verify scheduling fairness and concurrent delivery. */
22import { db } from '@sim/db'
33import { outboxEvent } from '@sim/db/schema'
4+ import { withUtcTimestamps } from '@sim/db/timestamps'
45import { generateId } from '@sim/utils/id'
56import { eq , inArray , sql } from 'drizzle-orm'
6- import { afterAll , afterEach , describe , expect , it } from 'vitest'
7+ import { drizzle , type PostgresJsDatabase } from 'drizzle-orm/postgres-js'
8+ import postgres from 'postgres'
9+ import { afterAll , afterEach , beforeAll , describe , expect , it , vi } from 'vitest'
10+
11+ const database = vi . hoisted ( ( ) => ( { current : undefined as PostgresJsDatabase | undefined } ) )
12+
13+ vi . mock ( '@sim/db' , ( ) => ( {
14+ get db ( ) {
15+ if ( ! database . current ) throw new Error ( 'Outbox PostgreSQL test database is not initialized' )
16+ return database . current
17+ } ,
18+ } ) )
19+
720import {
821 type OutboxHandler ,
922 processOutboxEvents ,
@@ -22,6 +35,26 @@ function planNodes(plan: QueryPlan): QueryPlan[] {
2235
2336describe ( 'outbox scheduling in PostgreSQL' , ( ) => {
2437 const eventTypes = new Set < string > ( )
38+ const schemaName = `outbox_test_${ generateId ( ) . replaceAll ( '-' , '' ) } `
39+ const databaseUrl = process . env . KNOWLEDGE_ACL_TEST_DATABASE_URL
40+ if ( ! databaseUrl ) throw new Error ( 'Outbox tests require a disposable local database' )
41+ const connection = postgres (
42+ databaseUrl ,
43+ withUtcTimestamps ( {
44+ max : 4 ,
45+ prepare : false ,
46+ fetch_types : false ,
47+ connection : { search_path : schemaName } ,
48+ onnotice : ( ) => { } ,
49+ } )
50+ )
51+
52+ beforeAll ( async ( ) => {
53+ await connection `CREATE SCHEMA ${ connection ( schemaName ) } `
54+ /** Copy the provisioned table and indexes without consuming another suite's pending events. */
55+ await connection `CREATE TABLE outbox_event (LIKE public.outbox_event INCLUDING ALL)`
56+ database . current = drizzle ( connection )
57+ } )
2558
2659 afterEach ( async ( ) => {
2760 if ( eventTypes . size ) {
@@ -31,7 +64,12 @@ describe('outbox scheduling in PostgreSQL', () => {
3164 } )
3265
3366 afterAll ( async ( ) => {
34- await db . $client . end ( )
67+ try {
68+ await connection `DROP SCHEMA ${ connection ( schemaName ) } CASCADE`
69+ } finally {
70+ await connection . end ( )
71+ database . current = undefined
72+ }
3573 } )
3674
3775 async function enqueue ( eventType : string , count : number , ageMs = 10_000 ) {
@@ -162,11 +200,17 @@ describe('outbox scheduling in PostgreSQL', () => {
162200 AND available_at <= now()
163201 ORDER BY available_at, created_at, id
164202 LIMIT 1 FOR UPDATE SKIP LOCKED
165- ` )
203+ ` )
166204 const nodes = planNodes ( plans [ 0 ] [ 'QUERY PLAN' ] [ 0 ] . Plan )
167- expect (
168- nodes . some ( ( node ) => node [ 'Index Name' ] === 'outbox_event_pending_type_available_idx' )
169- ) . toBe ( true )
205+ const indexScan = nodes . find ( ( node ) => node [ 'Node Type' ] === 'Index Scan' )
206+ expect ( indexScan ) . toBeDefined ( )
207+ const [ index ] = await connection < { indexdef : string } [ ] > `
208+ SELECT indexdef FROM pg_indexes
209+ WHERE schemaname = ${ schemaName } AND indexname = ${ indexScan ?. [ 'Index Name' ] ?? '' }
210+ LIMIT 1
211+ `
212+ expect ( index . indexdef ) . toContain ( '(event_type, available_at, created_at, id)' )
213+ expect ( index . indexdef ) . toContain ( "WHERE (status = 'pending'::text)" )
170214 expect ( nodes . some ( ( node ) => node [ 'Node Type' ] === 'Sort' ) ) . toBe ( false )
171215
172216 const delivered : string [ ] = [ ]
0 commit comments