Files
hackerspace.sh/integration/class-concurrency.test.ts
Moheeb Zara 6403220c92 test(integration): DB-backed harness validating shipped SQL (045/042/044/043)
Root-cause fix for 'correctness depended on post-deploy manual audits'.
Separate psql-driven suite (pnpm test:integration, own vitest config,
integration/ outside __tests__ so the hermetic pnpm test gate is
untouched; self-skips without a DB). 18 tests run against real Postgres:
- 045 class_signup_tx/class_cancel_tx: RETURNS TABLE->array shape the
  actions destructure, capacity/waitlist/dedupe, and the per-session
  advisory lock under TWO concurrent connections (D11 proven, never
  over-enrolls).
- 042 equipment exclusion constraint (overlap 23P01; cancelled/adjacent
  ok). 044 self-change trigger (role + payment cols blocked; service
  bypass). 043 members_update WITH CHECK (cross-space move blocked).
Disproves the pass-52 flagged risk (signup rpc-shape). Non-runtime;
default pnpm test still 532/26 hermetic; build clean.
2026-05-19 13:01:16 -07:00

130 lines
5.0 KiB
TypeScript

// Validates migration 045 (class_signup_tx / class_cancel_tx) against a real
// Postgres: the capacity/waitlist/dedupe logic, the per-session advisory lock
// under genuine concurrency, AND the RETURNS TABLE -> array shape the server
// actions destructure (`Array.isArray(rpc) ? rpc[0] : rpc`). This is the
// concrete prod risk flagged in the assessment: signup/cancel was shipped
// with zero functional coverage.
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { promisify } from 'node:util'
import { execFile } from 'node:child_process'
import {
dbReady,
DB_URL,
rows,
execService,
seedSpace,
seedMember,
seedClassSession,
dropSpace,
} from './db'
const execFileP = promisify(execFile)
type SignupRet = { signup_id: string | null; signup_status: string | null; err: string | null }
type CancelRet = { cancelled_id: string | null; promoted_id: string | null; err: string | null }
const d = describe.skipIf(!dbReady)
d('class_signup_tx / class_cancel_tx (migration 045)', () => {
let spaceId: string
let sessionId: string
let m1: string, m2: string, m3: string
beforeAll(() => {
spaceId = seedSpace().spaceId
sessionId = seedClassSession(spaceId, 1).sessionId // capacity = 1
m1 = seedMember(spaceId).memberId
m2 = seedMember(spaceId).memberId
m3 = seedMember(spaceId).memberId
})
afterAll(() => dropSpace(spaceId))
function signup(member: string) {
return rows<SignupRet>(`select * from class_signup_tx('${sessionId}','${spaceId}','${member}')`)
}
it('returns a single-row array (the shape the action destructures)', () => {
const r = signup(m1)
expect(Array.isArray(r)).toBe(true)
expect(r).toHaveLength(1)
expect(r[0]).toHaveProperty('signup_id')
expect(r[0]).toHaveProperty('signup_status')
expect(r[0]).toHaveProperty('err')
})
it('first within capacity -> registered; over capacity -> waitlisted', () => {
// m1 took the only seat above. m2 must waitlist.
const r2 = signup(m2)
expect(r2[0].err).toBeNull()
expect(r2[0].signup_status).toBe('waitlisted')
expect(r2[0].signup_id).toBeTruthy()
})
it('duplicate signup -> err "already", no row inserted', () => {
const r = signup(m1)
expect(r[0].err).toBe('already')
expect(r[0].signup_id).toBeNull()
const cnt = rows<{ n: number }>(
`select count(*)::int n from class_signups where session_id='${sessionId}' and member_id='${m1}'`,
)
expect(cnt[0].n).toBe(1)
})
it('unknown session -> err "no_session"', () => {
const bogus = '00000000-0000-0000-0000-000000000000'
const r = rows<SignupRet>(`select * from class_signup_tx('${bogus}','${spaceId}','${m3}')`)
expect(r[0].err).toBe('no_session')
})
it('cancel of the registered member promotes the earliest waitlisted', () => {
const c = rows<CancelRet>(`select * from class_cancel_tx('${sessionId}','${spaceId}','${m1}')`)
expect(c[0].err).toBeNull()
expect(c[0].cancelled_id).toBeTruthy()
expect(c[0].promoted_id).toBeTruthy()
// m2 (was waitlisted) is now registered; never exceeds capacity.
const reg = rows<{ status: string }>(
`select status from class_signups where session_id='${sessionId}' and member_id='${m2}' and status<>'cancelled'`,
)
expect(reg[0].status).toBe('registered')
const overCap = rows<{ n: number }>(
`select count(*)::int n from class_signups where session_id='${sessionId}' and status='registered'`,
)
expect(overCap[0].n).toBeLessThanOrEqual(1)
})
it('cancel when not signed up -> err "not_signed_up"', () => {
const c = rows<CancelRet>(`select * from class_cancel_tx('${sessionId}','${spaceId}','${m3}')`)
expect(c[0].err).toBe('not_signed_up')
})
it('advisory lock: two CONCURRENT signups at capacity 1 -> exactly one registered', async () => {
const sess2 = seedClassSession(spaceId, 1).sessionId
const a = seedMember(spaceId).memberId
const b = seedMember(spaceId).memberId
const call = (mid: string) =>
execFileP('psql', [
DB_URL,
'-tAc',
`select signup_status from class_signup_tx('${sess2}','${spaceId}','${mid}')`,
])
// Fire both before either resolves: the per-session advisory xact lock
// must serialize them so capacity is never exceeded.
const [ra, rb] = await Promise.all([call(a), call(b)])
const results = [ra.stdout.trim(), rb.stdout.trim()].sort()
expect(results).toEqual(['registered', 'waitlisted'])
const reg = rows<{ n: number }>(
`select count(*)::int n from class_signups where session_id='${sess2}' and status='registered'`,
)
expect(reg[0].n).toBe(1) // never 2 — the lock held
})
it('unlimited capacity (null) -> always registered', () => {
const openSess = seedClassSession(spaceId, null).sessionId
for (let i = 0; i < 3; i++) {
const mid = seedMember(spaceId).memberId
const r = rows<SignupRet>(`select * from class_signup_tx('${openSess}','${spaceId}','${mid}')`)
expect(r[0].signup_status).toBe('registered')
}
})
})