testing/audit_drift_guard.ts

Declarations
#

5 declarations

view source

AuditEmitMarker
#

create_emit_ordering_audit_factory
#

testing/audit_drift_guard.ts view source

<E extends { kind: string; at: number; }>(seq_ref: { value: number; }, events_ref: (AuditEmitMarker | E)[], extra_options?: Omit<CreateAuditEmitterOptions, "db" | ... 1 more ... | "emit_decorator"> | undefined): AuditFactory

Build an audit_factory that produces a real create_audit_emitter with its emit decorated to push a {kind: 'emit', at: seq.value++} marker into a shared sequence + events array. Used by the close-vs-emit ordering test to compose against a shared sequence counter (typically create_recording_closer(seq_ref) capturing eager-close calls).

Pass the returned factory through create_test_app({audit_factory: …}) — the test backend invokes it with its constructed {db, log} and lands the decorated emitter on backend.deps.audit. Production handlers dereference deps.audit.emit at call time, so the decorator sees every subsequent handler invocation. The underlying emit still runs — the decorator records the call, it does not suppress side effects.

Scope — both emit and emit_role_grant_target. The decorator is captured by emit_role_grant_target's closure inside create_audit_emitter (and re-exposed as the outer emit slot), so role-grant-shape emissions land in events_ref alongside bare emit calls. emit_pool and notify are not decorated — they take AuditLogInput / AuditLogEvent directly without going through emit, so handler-side emit_pool writes (today only auth/cleanup.ts) skip capture. Close-firing handlers all reach for emit or emit_role_grant_target, so the ordering test sees them regardless of which entry point a future refactor picks.

Optionally accept extra_options to thread on_audit_event / audit_log_config into the inner emitter — useful when a test wants both ordering capture and a real SSE/WS guard wired into the same emitter chain.

seq_ref

type { value: number; }

events_ref

type (AuditEmitMarker | E)[]

extra_options?

type Omit<CreateAuditEmitterOptions, "db" | "log" | "emit_decorator"> | undefined
optional

returns

AuditFactory

create_recording_audit_emitter
#

testing/audit_drift_guard.ts view source

(calls_ref?: AuditLogInput<"invite_create" | "invite_delete" | "app_settings_update" | "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | ... 10 more ... | "role_grant_offer_supersede">[] | undefined): RecordingAuditEmitter

Build a no-op AuditEmitter that records every emit, emit_pool, and emit_role_grant_target call into calls as an AuditLogInput. Use to capture audit metadata shapes in unit tests (e.g. password change failure outcome, role-grant create denial) without standing up the full PGlite + query_audit_log pipeline.

Capture scope — all four production fan-out shapes. emit_role_grant_target mirrors create_audit_emitter's lift logic in place — actor_id / account_id / ip are populated from auth + ctx and the event_type / outcome / target_*_id / metadata fields forward from the input envelope. Tests asserting on role-grant-shape emissions read out of the same homogeneous calls array. notify is a no-op; on_event_chain is an empty array.

emit AND emit_pool both append to calls so cleanup-sweep tests (which use emit_pool exclusively — see auth/cleanup.ts) can also read assertions off the same array.

Pass calls_ref to write into a caller-owned array (callers that declared const events: Array<AuditLogInput> = [] and want to keep the reference). Omit to let the helper allocate a fresh array and return it on the calls field of the result.

The returned emitter is deliberately NOT frozen — slots stay mutable so a test can override one when it needs bespoke shape (e.g. an emit_pool that throws on the first call). The production create_audit_emitter freeze invariant exists to catch the patch_audit_emit_capture hot-patch footgun against the closure-captured emit; the recording emitter has no inner closure, so the freeze isn't load-bearing here.

calls_ref?

type AuditLogInput<"invite_create" | "invite_delete" | "app_settings_update" | "login" | "logout" | "bootstrap" | "signup" | "password_change" | "session_revoke" | "session_revoke_all" | ... 10 more ... | "role_grant_offer_supersede">[] | undefined
optional

returns

RecordingAuditEmitter

install_audit_drift_guard
#

testing/audit_drift_guard.ts view source

(): void

Register per-test beforeEach + afterEach hooks that catch any audit emission with a metadata shape that fails its audit_metadata_schemas entry, or an event_type not present in the active AuditLogConfig.

The production validation in query_audit_log is fail-open — it bumps process-wide counters and proceeds, so a regression that emits an undeclared metadata field or a typo'd event-type lands a row that passes downstream queries but breaks forensics. Tests that exercise audit emits should fail loudly when this happens.

Call at the top of every describe / describe_db block that fires audit writes through deps.audit.emit. Resets counters before each test and asserts zero on completion.

Pair with await_pending_effects: true (the default for create_test_app) so fire-and-forget audit writes have completed by the time the after-each check observes counter state.

returns

void

RecordingAuditEmitter
#

testing/audit_drift_guard.ts view source

RecordingAuditEmitter

Pair returned by {@link create_recording_audit_emitter} — the AuditEmitter to inject as deps.audit, plus the shared calls array that records every captured emission. Both fields are live — callers read calls after exercising the handler to assert on the audit metadata shape.

emitter

calls

type Array<AuditLogInput>

Depends on
#