Skip to content

Commit 0e430e5

Browse files
committed
test(outbox): isolate scheduler PostgreSQL fixtures
1 parent ff8c92e commit 0e430e5

1 file changed

Lines changed: 50 additions & 6 deletions

File tree

‎apps/sim/lib/core/outbox/service.integration.ts‎

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
/** Real PostgreSQL claims verify scheduling fairness and concurrent delivery. */
22
import { db } from '@sim/db'
33
import { outboxEvent } from '@sim/db/schema'
4+
import { withUtcTimestamps } from '@sim/db/timestamps'
45
import { generateId } from '@sim/utils/id'
56
import { 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+
720
import {
821
type OutboxHandler,
922
processOutboxEvents,
@@ -22,6 +35,26 @@ function planNodes(plan: QueryPlan): QueryPlan[] {
2235

2336
describe('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

Comments
 (0)