Files
hackerspace.sh/integration/self-change.test.ts
T
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

64 lines
2.3 KiB
TypeScript

// Validates the self-escalation guards: migration 044
// (prevent_member_self_role_change blocks self-edit of role/status AND the
// payment/dues columns) and migration 043 (members_update WITH CHECK blocks
// a privileged caller moving a row cross-space). Exercised through the real
// RLS/trigger path (role `authenticated` + request.jwt.claims.sub).
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { dbReady, execService, execAsUser, seedSpace, seedMember, dropSpace, uuid } from './db'
const d = describe.skipIf(!dbReady)
d('self-change trigger (044) + members_update WITH CHECK (043)', () => {
let spaceId: string
let member: { memberId: string; userId: string }
let admin: { memberId: string; userId: string }
beforeAll(() => {
spaceId = seedSpace().spaceId
member = seedMember(spaceId, { role: 'member', status: 'current' })
admin = seedMember(spaceId, { role: 'admin', status: 'current' })
})
afterAll(() => dropSpace(spaceId))
it('member CANNOT self-escalate role', () => {
const r = execAsUser(
member.userId,
`update space_members set role='admin' where id='${member.memberId}'`,
)
expect(r.ok).toBe(false)
expect(r.err).toMatch(/42501|cannot change/i)
})
it('member CANNOT self-edit a payment/dues column (044)', () => {
const r = execAsUser(
member.userId,
`update space_members set payment_status='paid' where id='${member.memberId}'`,
)
expect(r.ok).toBe(false)
expect(r.err).toMatch(/42501|cannot change/i)
})
it('member CAN self-edit an allowed profile column', () => {
const r = execAsUser(
member.userId,
`update space_members set display_name='renamed' where id='${member.memberId}'`,
)
expect(r.ok).toBe(true)
})
it('service path (no JWT) bypasses the trigger — the Stripe webhook path', () => {
const r = execService(
`update space_members set status='late', last_paid_at=now() where id='${member.memberId}'`,
)
expect(r.ok).toBe(true)
})
it('members_update WITH CHECK blocks a privileged cross-space move (043)', () => {
const r = execAsUser(
admin.userId,
`update space_members set space_id='${uuid()}' where id='${member.memberId}'`,
)
expect(r.ok).toBe(false) // RLS WITH CHECK (or FK) rejects the post-image
})
})