@fuzdev/fuz_app 0.118.0 → 0.119.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +301 -73
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +16 -1
- package/dist/actions/connection_closer.d.ts +136 -51
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +133 -34
- package/dist/actions/register_action_ws.d.ts +176 -24
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +581 -241
- package/dist/actions/register_ws_endpoint.d.ts +1 -0
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +1 -0
- package/dist/actions/socket.svelte.d.ts +43 -9
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +59 -8
- package/dist/actions/transports.d.ts +55 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +55 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +87 -111
- package/dist/actions/transports_ws_backend.d.ts +212 -30
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +327 -70
- package/dist/actions/ws_endpoint_spec.d.ts +26 -13
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +113 -32
- package/dist/auth/account_actions.d.ts +12 -18
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +47 -33
- package/dist/auth/account_routes.d.ts +5 -11
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +40 -36
- package/dist/auth/account_schema.d.ts +27 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +22 -0
- package/dist/auth/admin_actions.d.ts +8 -17
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +33 -21
- package/dist/auth/api_token_queries.d.ts +23 -2
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +39 -3
- package/dist/auth/audit_emitter.d.ts +61 -38
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +57 -14
- package/dist/auth/audit_log_route_schema.js +7 -7
- package/dist/auth/audit_log_routes.d.ts +31 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +85 -11
- package/dist/auth/audit_log_schema.d.ts +48 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +58 -0
- package/dist/auth/cleanup.d.ts +203 -28
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +222 -26
- package/dist/auth/deps.d.ts +28 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/resolved_auth.d.ts +105 -0
- package/dist/auth/resolved_auth.d.ts.map +1 -0
- package/dist/auth/resolved_auth.js +124 -0
- package/dist/auth/session_middleware.d.ts +8 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +9 -3
- package/dist/auth/session_queries.d.ts +31 -6
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +31 -6
- package/dist/auth/standard_rpc_actions.d.ts +9 -9
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +3 -3
- package/dist/db/CLAUDE.md +7 -1
- package/dist/db/status.d.ts +58 -1
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +205 -77
- package/dist/http/CLAUDE.md +7 -3
- package/dist/http/db_routes.d.ts +6 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +2 -2
- package/dist/periodic.d.ts +80 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +122 -0
- package/dist/realtime/sse_auth_guard.d.ts +79 -49
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +103 -73
- package/dist/realtime/subscriber_registry.d.ts +119 -8
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +184 -25
- package/dist/server/app_backend.d.ts +12 -1
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +5 -2
- package/dist/server/app_server.d.ts +55 -15
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +41 -13
- package/dist/testing/CLAUDE.md +140 -48
- package/dist/testing/app_server.d.ts +2 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +11 -2
- package/dist/testing/audit_drift_guard.d.ts +1 -48
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +0 -42
- package/dist/testing/connection_closer_helpers.d.ts +9 -22
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +11 -17
- package/dist/testing/cross_backend/capabilities.d.ts +67 -13
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +27 -12
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +21 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
- package/dist/testing/cross_backend/session_cap.d.ts +13 -0
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +55 -19
- package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +93 -26
- package/dist/testing/cross_backend/testing_server_bun.js +4 -4
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +156 -19
- package/dist/testing/sse_round_trip.d.ts +6 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/stubs.d.ts +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/testing/transports/sse_transport.d.ts +1 -1
- package/dist/testing/transports/ws_client.d.ts +7 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -1
- package/dist/testing/transports/ws_raw_client.d.ts +51 -0
- package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
- package/dist/testing/transports/ws_raw_client.js +156 -0
- package/dist/testing/transports/ws_transport.d.ts +28 -0
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +45 -1
- package/dist/testing/ws_round_trip.d.ts +8 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -4
- package/dist/ui/AdminAccounts.svelte +4 -3
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +3 -1
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +22 -6
- package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -0
- package/dist/ui/auth_state.svelte.d.ts +25 -1
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +28 -1
- package/package.json +1 -1
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -31,10 +31,10 @@ time (never runtime), where a throwing guard would break `vite build`.
|
|
|
31
31
|
- `create_stub_db()` — real `Db` whose `client.query` yields `{rows: []}` and `transaction(fn)` synchronously calls `fn(inner_stub_db)`. Safe for `apply_route_specs`'s declarative transaction wrapper.
|
|
32
32
|
- `stub_handler()` — fresh `Response('stub')`.
|
|
33
33
|
- `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
|
|
34
|
-
- `stub_app_deps` — frozen `AppDeps`, every capability throwing, `audit` a no-op `AuditEmitter` from `create_test_audit_emitter`.
|
|
35
|
-
- `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`.
|
|
34
|
+
- `stub_app_deps` — frozen `AppDeps`, every capability throwing, `audit` a no-op `AuditEmitter` from `create_test_audit_emitter`, `connection_closer` an empty `RealtimeCloser`.
|
|
35
|
+
- `create_stub_app_deps()` — factory: fresh `AppDeps` with no-op keyring/password/`delete_file`, a `read_secure_file` that throws ENOENT (the no-token-file state), a `create_noop_stub` DB, silent `Logger`, no-op `audit`, an empty `connection_closer`.
|
|
36
36
|
- `create_test_audit_emitter()` — no-op `AuditEmitter`; `emit` / `emit_role_grant_target` no-op, `emit_pool` resolves immediately, `notify` no-op, `add_listener` throws (use `create_recording_audit_emitter` for a listener-accepting emitter), `listener_count` returns 0.
|
|
37
|
-
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `
|
|
37
|
+
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `registry` is a fresh `SubscriberRegistry` (live `.count` / `.close_*` for registry-state tests, isolated per call). For real SSE plumbing build via `create_audit_log_sse` against `create_test_app`.
|
|
38
38
|
- `create_stub_api_middleware({include_daemon_token?})` — stub `MiddlewareSpec[]` matching `create_auth_middleware_specs`'s output (origin/session/request_context/bearer_auth, optional daemon_token) for surface generation without booting real auth. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
39
39
|
- `create_stub_app_server_context(session_options)` — stub `AppServerContext`; rate limiters null, `bootstrap_status.available: false`, `app_settings.open_signup: false`.
|
|
40
40
|
- `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly (consumer routes + stub middleware + surface generation). `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `transform_middleware?`, `bootstrap?`. Bootstrap is opt-in (symmetric with `create_app_server` — omit to skip; pass the same value as prod to mount routes at `bootstrap.route_prefix ?? '/api/account'`). Single source of truth for attack-surface tests — track `create_app_server` wiring changes here.
|
|
@@ -100,16 +100,14 @@ mismatch.
|
|
|
100
100
|
### `audit_drift_guard.ts` — audit-emission validation
|
|
101
101
|
|
|
102
102
|
- `install_audit_drift_guard()` — `beforeEach` resets + `afterEach` zero-checks `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts`. Call once at the top of any `describe_db` block firing audit emits — production validation is fail-open, so without this any regression shipping a typo'd `event_type` or undeclared metadata field is silent. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes complete by response time.
|
|
103
|
-
- `create_emit_ordering_audit_factory<E>(seq_ref, events_ref, build_inner)` — returns an `AuditFactory` wrapping `build_inner({db, log})` so every `emit` pushes `{kind: 'emit', at: seq.value++}` into a shared sequence + events array. Pass through `create_test_app({audit_factory: …})` — the test backend invokes it with its `{db, log}` and lands the wrapped emitter on `deps.audit`. Generic `E extends {kind: string; at: number}` so the events array typechecks against the caller's own `close` / custom marker shape. Pair with `create_recording_closer(seq_ref)` for close-vs-emit ordering tests. Scope is `emit` only — `emit_role_grant_target`, `emit_pool`, `notify` forward to the inner emitter unwrapped.
|
|
104
|
-
- `AuditEmitMarker` — `{kind: 'emit'; at: number}`, the marker type pushed.
|
|
105
103
|
- `create_recording_audit_emitter(calls_ref?)` — no-op `AuditEmitter` pushing every `emit` and `emit_pool` call into `calls`. Pass `calls_ref` to write into a caller-owned array; omit to let the helper allocate. Returns `{emitter, calls}` — destructure `emitter` as the `audit` dep and read `calls` to assert. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc.
|
|
106
104
|
- `RecordingAuditEmitter` — `{emitter: AuditEmitter; calls: Array<AuditLogInput>}`.
|
|
107
105
|
|
|
108
106
|
### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
|
|
109
107
|
|
|
110
|
-
- `create_recording_closer(
|
|
111
|
-
- `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call
|
|
112
|
-
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id
|
|
108
|
+
- `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing. To see a handler's closes, add it to the backend's closer (`ctx.deps.connection_closer.add(closer)`) or build the factory's deps with it. The closes are queued post-commit, so they have run by the time a `create_test_app` response is in hand (`await_pending_effects: true` flushes both queues).
|
|
109
|
+
- `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call; handles the missing-element case.
|
|
110
|
+
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id}`.
|
|
113
111
|
- `RecordingCloser` — `{closer, calls}`.
|
|
114
112
|
|
|
115
113
|
## Database — `db.ts`
|
|
@@ -171,7 +169,7 @@ pre-keeper, lock unflipped), use `create_test_app_for_bootstrap` — pair with
|
|
|
171
169
|
Types:
|
|
172
170
|
|
|
173
171
|
- `TestAppServer extends AppBackend` — adds `account`, `actor`, `api_token`, `session_cookie`, `keyring`, `cleanup()`.
|
|
174
|
-
- `TestAppServerOptions` — `session_options` (required), optional `db`, `db_type`, `migration_namespaces`, `password`, `username`, `password_value`, `roles`, `audit_factory`. `migration_namespaces` runs extra namespaces after auth in the auto-created PGlite (mirrors `create_app_backend`); mutually exclusive with `db` (caller-migrated) — passing both throws. The optional `audit_factory` defaults to `default_audit_factory` (no-listener `create_audit_emitter` over the test backend's `{db, log}`); pass a custom factory to compose `on_audit_event` / `audit_log_config`,
|
|
172
|
+
- `TestAppServerOptions` — `session_options` (required), optional `db`, `db_type`, `migration_namespaces`, `password`, `username`, `password_value`, `roles`, `audit_factory`. `migration_namespaces` runs extra namespaces after auth in the auto-created PGlite (mirrors `create_app_backend`); mutually exclusive with `db` (caller-migrated) — passing both throws. The optional `audit_factory` defaults to `default_audit_factory` (no-listener `create_audit_emitter` over the test backend's `{db, log}`); pass a custom factory to compose `on_audit_event` / `audit_log_config`, instrument `emit` with `emit_decorator`, or otherwise replace the emitter. Mirrors `CreateAppBackendOptions` end-to-end — the previous `on_audit_event` / `audit_log_config` sugar was removed alongside the production rename.
|
|
175
173
|
- `CreateTestAppOptions extends TestAppServerOptions` — adds `create_route_specs` (required), `rpc_endpoints?: RpcEndpointsSuiteOption` (top-level only — single source of truth, symmetric with the suite-level option), `bootstrap?: BootstrapServerOptions` (top-level only — same precedent as `rpc_endpoints`), and `app_options?: SuiteAppOptions` (`Partial<AppServerOptions>` excluding the five fields the helper manages: `backend`, `session_options`, `create_route_specs`, `rpc_endpoints`, `bootstrap`).
|
|
176
174
|
- `TestAccount` — `{account, actor, session_cookie, api_token, create_session_headers, create_bearer_headers}`.
|
|
177
175
|
- `TestApp` — `{app, backend, surface_spec, surface, route_specs, create_session_headers, create_bearer_headers, create_daemon_token_headers, create_account, cleanup}`.
|
|
@@ -186,6 +184,11 @@ state (no rotation, keeper already set),
|
|
|
186
184
|
the response returns so tests can assert on side effects inline), and silent
|
|
187
185
|
logger. Override via `app_options`.
|
|
188
186
|
|
|
187
|
+
`auth_cleanup` is left at its default, **off**, here and in
|
|
188
|
+
`create_test_app_for_bootstrap`: a background pass would delete rows and
|
|
189
|
+
write audit rows mid-test. A suite that opts in through `app_options` has the
|
|
190
|
+
schedule stopped by `cleanup()`, which is the assembled server's `close`.
|
|
191
|
+
|
|
189
192
|
A fresh Hono app is created on every call because middleware closures bind
|
|
190
193
|
to the server's deps (db, keyring). Hono assembly is cheap (~10–50ms);
|
|
191
194
|
PGlite WASM caching in `db.ts` is where the real savings are.
|
|
@@ -515,13 +518,14 @@ bootstrapped keeper) based on `method.auth`; keeper uses the daemon token.
|
|
|
515
518
|
Per SSE route: open stream with matching auth, assert the
|
|
516
519
|
`SSE_CONNECTED_COMMENT` comment, fire a consumer-supplied `trigger()`,
|
|
517
520
|
validate the next `data:` frame as `{method, params}` against declared
|
|
518
|
-
`EventSpec`s, then (by default)
|
|
519
|
-
and assert the stream closes within 2s.
|
|
521
|
+
`EventSpec`s, then (by default) dispatch the `account_session_revoke_all` RPC
|
|
522
|
+
method and assert the stream closes within 2s.
|
|
520
523
|
|
|
521
524
|
`SseRouteTestSpec` per route: `{path, trigger, event_specs?, assert_closes_on_revoke?}`.
|
|
522
|
-
|
|
523
|
-
(
|
|
524
|
-
the
|
|
525
|
+
A consumer registry closes on the revoke when its route joined it to the
|
|
526
|
+
backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry))`)
|
|
527
|
+
or when the suite's `on_audit_event` option wires a guard (e.g. `create_sse_auth_guard`); with
|
|
528
|
+
neither, the revoke assertion fails.
|
|
525
529
|
|
|
526
530
|
Frame reading is delegated to the shared `create_sse_frame_reader`
|
|
527
531
|
(`transports/sse_frame_reader.ts`) — `\n\n` framing, a 2s per-read timeout
|
|
@@ -539,12 +543,12 @@ skipped (the Node test runtime has no `@hono/node-ws` adapter).
|
|
|
539
543
|
|
|
540
544
|
Three layers:
|
|
541
545
|
|
|
542
|
-
1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`.
|
|
543
|
-
2. **Harness** — `create_ws_test_harness({actions, transport?, heartbeat?, log?, on_socket_open?, on_socket_close?})` → `WsTestHarness`. `connect(identity?)` is async and resolves after `on_socket_open` completes, so broadcasts sent immediately after `await harness.connect()` reach the client. The harness threads its own `create_stub_db()` into the dispatcher's `db` slot so handlers declaring `side_effects: true` execute under the same transaction wrap they would in production (the stub's `transaction(fn)` synchronously calls `fn(stub_db)`); domain deps reach handlers via factory closures, the same way HTTP RPC factories already wire them. Audit fan-out runs through whatever `audit` emitter the consumer supplied to its action factory closure (typically `create_test_audit_emitter()` for unit harnesses).
|
|
546
|
+
1. **Primitives** — `create_fake_ws()`, `create_fake_hono_context(opts)`, `create_stub_upgrade()`, `MinimalActionEnvironment`, `dispatch_ws_message(on_message, event, ws)`. The fake context sets the keys the production auth middleware sets — `AUTH_SESSION_TOKEN_HASH_KEY` included, as `hash_session_token(auth_session_id)` — because the dispatcher reads the credential through `get_resolved_auth(c)`; a hand-built upgrade context must do the same.
|
|
547
|
+
2. **Harness** — `create_ws_test_harness({actions, transport?, heartbeat?, log?, on_socket_open?, on_socket_close?})` → `WsTestHarness`. `connect(identity?)` is async and resolves after the connection is admitted and `on_socket_open` completes, so broadcasts sent immediately after `await harness.connect()` reach the client. The harness pre-bakes its `RequestContext` (`TEST_CONTEXT_PRESET_KEY`), which also skips the admission credential re-read — there are no session or token rows behind a harness identity — so tests of the re-read itself drive `register_action_ws` with a real database and no preset (`src/test/actions/register_action_ws.admission.db.test.ts`). The harness threads its own `create_stub_db()` into the dispatcher's `db` slot so handlers declaring `side_effects: true` execute under the same transaction wrap they would in production (the stub's `transaction(fn)` synchronously calls `fn(stub_db)`); domain deps reach handlers via factory closures, the same way HTTP RPC factories already wire them. Audit fan-out runs through whatever `audit` emitter the consumer supplied to its action factory closure (typically `create_test_audit_emitter()` for unit harnesses).
|
|
544
548
|
3. **Round-trip helpers** — predicates + wire-frame types live in `transports/ws_client.ts` (shared with the cross-process `ws_transport.ts` impl): `is_notification(method)`, `is_notification_with<P>(method, match)` (type-guard combinator — narrows `wait_for` return type), `is_response_for(id)`, `JsonrpcNotificationFrame<P>` / `JsonrpcSuccessResponseFrame<R>` / `JsonrpcErrorResponseFrame<D>` (typed wire-frame shapes distinct from the runtime Zod schemas in `http/jsonrpc.ts` — generic over `params` / `result` / `data` so tests narrow without casts). `build_broadcast_api<TApi>({harness, specs})` (in `ws_round_trip.ts`) wires a typed broadcast API against the harness transport.
|
|
545
549
|
|
|
546
550
|
`WsClient` (in `transports/ws_client.ts`):
|
|
547
|
-
`{send, request<R>, close, messages, wait_for, wait_for_close, close_code}`.
|
|
551
|
+
`{send, request<R>, close, messages, wait_for, wait_for_close, close_code, close_reason}`.
|
|
548
552
|
The harness's `connect()` returns this shape; the cross-process
|
|
549
553
|
`create_ws_transport` in `transports/ws_transport.ts` implements the same
|
|
550
554
|
interface so assertion helpers and suite bodies work against either impl.
|
|
@@ -553,7 +557,9 @@ socket within the timeout, `false` on timeout (and `true` immediately when
|
|
|
553
557
|
already closed) — the signal for server-initiated close (e.g. an auth-guard
|
|
554
558
|
revocation), distinct from client-initiated `close()`. Mirrors the SSE frame
|
|
555
559
|
reader's `wait_for_close`. `close_code` is the connection's close code once
|
|
556
|
-
closed (`1006` for a drop without a close frame), `null` while open
|
|
560
|
+
closed (`1006` for a drop without a close frame), `null` while open;
|
|
561
|
+
`close_reason` is the text sent with it (empty when the close carried none),
|
|
562
|
+
`null` while open.
|
|
557
563
|
`request` throws with code + message + data on error frames (so asserting `result.foo` on a
|
|
558
564
|
failed request surfaces the real cause, not a `Cannot read property 'foo'
|
|
559
565
|
of undefined`). `wait_for(predicate, timeout_ms?)` checks already-received
|
|
@@ -915,9 +921,10 @@ source of truth for wire-shape conformance.
|
|
|
915
921
|
- `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
|
|
916
922
|
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
917
923
|
`cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
|
|
918
|
-
`account_status` / `oversized_reject_closes_connection` / `peer_request`
|
|
924
|
+
`account_status` / `oversized_reject_closes_connection` / `peer_request` /
|
|
925
|
+
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` —
|
|
919
926
|
each has a `test_if`
|
|
920
|
-
reader; `peer_request` (server-initiated requests — the ActionPeer
|
|
927
|
+
reader (`ws_self_revocation_reply` forks one case's assertion instead); `peer_request` (server-initiated requests — the ActionPeer
|
|
921
928
|
`peer/ping` round-trip) is `true` for both the Rust spine and the TS spine
|
|
922
929
|
(`BackendWebsocketTransport.request_connection`); `ts_default_capabilities`
|
|
923
930
|
keeps it `false` like sse/ready until a backend wires the `peer/ping` HTTP +
|
|
@@ -962,6 +969,17 @@ source of truth for wire-shape conformance.
|
|
|
962
969
|
smuggling probe (`true` Node/Deno/Rust — they close on an oversized reject;
|
|
963
970
|
`false` Bun — `Bun.serve` drains + keepalives but frames correctly, so the
|
|
964
971
|
suite's no-desync half still runs).
|
|
972
|
+
`ws_handshake_pipelining` gates the WS round-trip suite's
|
|
973
|
+
frames-with-the-handshake case (`true` Node/Deno/Rust — frames written in the
|
|
974
|
+
same TCP write as the upgrade request reach the socket; `false` Bun, whose
|
|
975
|
+
HTTP parser answers such a request `400`, and `false` in-process, where there
|
|
976
|
+
is no socket to write raw bytes to).
|
|
977
|
+
`ws_account_actions` gates the WS round-trip suite's self-revocation case
|
|
978
|
+
(the WS endpoint mounts the account actions — `true` on fuz_app's own spine
|
|
979
|
+
presets, `false` in the family defaults), and `ws_self_revocation_reply`
|
|
980
|
+
says what that case requires: the reply ahead of the close (`true`, the TS
|
|
981
|
+
family) or the close alone (`false`, the Rust family, whose socket loop
|
|
982
|
+
drops the response once the connection is closed — a recorded divergence).
|
|
965
983
|
|
|
966
984
|
### `cross_backend/standard.ts` — `describe_standard_cross_process_tests`
|
|
967
985
|
|
|
@@ -1084,20 +1102,32 @@ base_url, ws_path, origin?, rpc_path?, max_message_bytes?})` opens a live
|
|
|
1084
1102
|
`WebSocket` via `create_ws_transport` (the `ws` npm package) and asserts
|
|
1085
1103
|
cases against the upgrade stack `register_ws_endpoint` wires (origin →
|
|
1086
1104
|
`require_auth` → dispatch): authed upgrade round-trips `heartbeat`,
|
|
1105
|
+
requests sent the moment the socket opens are all answered (both spines admit a
|
|
1106
|
+
connection only after re-reading its credential; frames that arrive first wait
|
|
1107
|
+
for that, they are not dropped) — and, gated on
|
|
1108
|
+
`capabilities.ws_handshake_pipelining`, the same with the ordering forced:
|
|
1109
|
+
frames written in the same TCP write as the upgrade request (`connect_raw_ws`)
|
|
1110
|
+
are all answered,
|
|
1087
1111
|
`heartbeat` admits only the parameterless shapes (a declared param →
|
|
1088
1112
|
`invalid_params`), a message past `max_message_bytes` (default
|
|
1089
1113
|
`DEFAULT_WS_MAX_MESSAGE_BYTES`) closes with `WS_CLOSE_MESSAGE_TOO_BIG`,
|
|
1090
1114
|
anonymous upgrade refused, disallowed-origin upgrade refused, and — gated on
|
|
1091
1115
|
`rpc_path` — a live socket drops when the account's sessions are revoked
|
|
1092
|
-
mid-connection (`account_session_revoke_all` over the keeper session channel
|
|
1093
|
-
|
|
1094
|
-
|
|
1116
|
+
mid-connection (`account_session_revoke_all` over the keeper session channel;
|
|
1117
|
+
asserted via `WsClient.wait_for_close`) and when its account is deleted
|
|
1118
|
+
(`account_delete` by the keeper, also gated on
|
|
1119
|
+
`capabilities.account_lifecycle`; the close code is
|
|
1120
|
+
`WS_CLOSE_SESSION_REVOKED`). Per-connection auth is enforced **at upgrade
|
|
1095
1121
|
time**, so the negative upgrade cases assert the upgrade itself rejects, not
|
|
1096
|
-
a per-message error; the close
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1122
|
+
a per-message error; the close cases prove a revocation closes an
|
|
1123
|
+
already-open socket, since per-message dispatch never re-checks credential
|
|
1124
|
+
validity. Omit `rpc_path` to skip the close cases (consumers without the
|
|
1125
|
+
standard account and admin actions on their RPC endpoint). One case is gated
|
|
1126
|
+
on `capabilities.ws_account_actions` (the WS endpoint mounts the account
|
|
1127
|
+
actions): a socket that revokes its own sessions over that socket is closed
|
|
1128
|
+
with `WS_CLOSE_SESSION_REVOKED`, and — where the backend declares
|
|
1129
|
+
`capabilities.ws_self_revocation_reply` — reads the reply first.
|
|
1130
|
+
Otherwise **consumer-agnostic** — it drives only the `heartbeat` protocol action
|
|
1101
1131
|
(guaranteed on every WS endpoint by `assert_ws_endpoints_include_protocol_actions`),
|
|
1102
1132
|
so it validates the transport without touching domain WS methods. Gated on
|
|
1103
1133
|
`capabilities.ws`; cross-process only (needs a real bound socket — wire from
|
|
@@ -1114,7 +1144,9 @@ the seven server-initiated notifications (`received` → recipient,
|
|
|
1114
1144
|
superseded sibling's grantor on **both** the accept- and revoke-cascade paths).
|
|
1115
1145
|
`describe_role_grant_offer_notification_ws_tests({setup_test, capabilities,
|
|
1116
1146
|
base_url, ws_path})` opens the affected counterparty's socket
|
|
1117
|
-
(`
|
|
1147
|
+
(`create_admitted_ws_transport` — a targeted notification reaches only an
|
|
1148
|
+
admitted connection, and the handshake alone doesn't say it is one), drives the
|
|
1149
|
+
lifecycle RPC over `fixture.transport`, then
|
|
1118
1150
|
strict-parses the delivered frame against its canonical params schema from
|
|
1119
1151
|
`auth/role_grant_offer_notifications.ts` (the guard against serialization drift —
|
|
1120
1152
|
field / null / datetime / the flat revoke shape / the supersede `reason` +
|
|
@@ -1159,6 +1191,48 @@ action (manifest-excluded), so parity is behavioral here, not via the manifest
|
|
|
1159
1191
|
gate. Cross-process only; fuz_app's own wiring is
|
|
1160
1192
|
`src/test/cross_backend/peer_ping_ws.cross.test.ts`.
|
|
1161
1193
|
|
|
1194
|
+
### `cross_backend/ws_connection_cap.ts` — `describe_ws_connection_cap_cross_tests`
|
|
1195
|
+
|
|
1196
|
+
Real-upgrade parity for the **per-account WebSocket connection cap** — the
|
|
1197
|
+
evict-oldest policy both spines apply (`BackendWebsocketTransport`'s
|
|
1198
|
+
`max_connections_per_account`, the Rust `fuz_realtime` `ConnectionRegistry`).
|
|
1199
|
+
The close code, its reason, and which socket closes are wire contract — a
|
|
1200
|
+
client reads `WS_CLOSE_CONNECTION_LIMIT` as "superseded, don't reconnect" — so
|
|
1201
|
+
this is the pin that runs on both.
|
|
1202
|
+
`describe_ws_connection_cap_cross_tests({setup_test, capabilities, base_url,
|
|
1203
|
+
ws_path, origin?, max_connections_per_account?})` opens every socket with the
|
|
1204
|
+
fresh-per-test keeper's session cookies (one account), each answering a
|
|
1205
|
+
`heartbeat` before the next opens (`create_admitted_ws_transport`) so the
|
|
1206
|
+
backend registered them in order and
|
|
1207
|
+
"oldest" names the same socket on both spines. Cases: one connection past the
|
|
1208
|
+
cap closes exactly the oldest with `WS_CLOSE_CONNECTION_LIMIT` + reason
|
|
1209
|
+
`connection limit` while every other socket, the newest included, keeps
|
|
1210
|
+
dispatching — and the session then upgrades again (a revoked session couldn't),
|
|
1211
|
+
superseding the next-oldest; and a socket held open while a cap's worth of
|
|
1212
|
+
others open and close in sequence is never evicted (a backend that leaks closed
|
|
1213
|
+
connections in its registry counts them toward the cap, which is what this
|
|
1214
|
+
catches). `max_connections_per_account` defaults to
|
|
1215
|
+
`DEFAULT_MAX_CONNECTIONS_PER_ACCOUNT`, the cap both spines apply unless
|
|
1216
|
+
overridden; pass the consumer's value when it sets one, `null` (cap disabled)
|
|
1217
|
+
to skip. **Consumer-agnostic** like the WS round-trip suite (`heartbeat` only).
|
|
1218
|
+
A separate call rather than more cases on that suite because each case opens
|
|
1219
|
+
more than a cap's worth of real sockets. Gated on `capabilities.ws`;
|
|
1220
|
+
cross-process only. fuz_app's own wiring is
|
|
1221
|
+
`src/test/cross_backend/ws_connection_cap.cross.test.ts`.
|
|
1222
|
+
|
|
1223
|
+
### `cross_backend/session_cap.ts` — `describe_session_cap_cross_tests`
|
|
1224
|
+
|
|
1225
|
+
Parity for the per-account session cap over real HTTP: logging in past
|
|
1226
|
+
`max_sessions` (default `DEFAULT_MAX_SESSIONS`) never refuses a login and
|
|
1227
|
+
evicts the oldest session.
|
|
1228
|
+
`describe_session_cap_cross_tests({setup_test, max_sessions?, login_path?,
|
|
1229
|
+
status_path?, ws?})` — pass `ws` (`{base_url, ws_path, origin?}`) to also
|
|
1230
|
+
assert that the evicted session's socket is closed with
|
|
1231
|
+
`WS_CLOSE_SESSION_REVOKED` once the evicting login commits; omitted, that case
|
|
1232
|
+
is skipped, so a caller passes it only for a backend with `capabilities.ws`.
|
|
1233
|
+
Cross-process only. fuz_app's own wiring is
|
|
1234
|
+
`src/test/cross_backend/session_cap.cross.test.ts`.
|
|
1235
|
+
|
|
1162
1236
|
### `cross_backend/sse_round_trip.ts` — `describe_cross_process_sse_tests`
|
|
1163
1237
|
|
|
1164
1238
|
Cross-process counterpart to the in-process `sse_round_trip.ts` harness —
|
|
@@ -1167,23 +1241,26 @@ endpoint via `create_sse_transport` (built-in `fetch` + `TextDecoder`, no
|
|
|
1167
1241
|
dep), threading the fresh-per-test keeper's session cookie. Kept a separate
|
|
1168
1242
|
call (not folded into `describe_standard_cross_process_tests`) for the same
|
|
1169
1243
|
reason the WS suite is — it needs raw `base_url` / `sse_path` the standard
|
|
1170
|
-
bundle doesn't carry.
|
|
1171
|
-
|
|
1172
|
-
|
|
1244
|
+
bundle doesn't carry. The cases mirror the in-process audit-log self-test:
|
|
1245
|
+
the stream emits the `: connected` comment; a minted secondary's sessions are
|
|
1246
|
+
revoked over the keeper's admin channel (`admin_session_revoke_all`),
|
|
1173
1247
|
broadcasting one `session_revoke_all` audit `data:` frame **without** closing
|
|
1174
1248
|
the keeper's stream (target ≠ subscriber — secondary minted before the stream
|
|
1175
|
-
opens so its `create_account` audit events stay off it);
|
|
1176
|
-
_own_ sessions are revoked (`account_session_revoke_all
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
`
|
|
1182
|
-
`
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1249
|
+
opens so its `create_account` audit events stay off it); the subscriber's
|
|
1250
|
+
_own_ sessions are revoked, all of them (`account_session_revoke_all`, the
|
|
1251
|
+
account-wide close) and then just the one (`account_session_revoke`, the
|
|
1252
|
+
session-scoped close), each dropping the live stream (asserted via
|
|
1253
|
+
`SseTransport.wait_for_close`); the keeper soft-deletes a second admin whose
|
|
1254
|
+
stream is open (`account_delete`, also gated on
|
|
1255
|
+
`capabilities.account_lifecycle`), dropping it; and one stream past the per-session cap
|
|
1256
|
+
(`max_per_scope`, default `AUDIT_LOG_SSE_MAX_PER_SCOPE`; `null` skips) on one
|
|
1257
|
+
session closes the oldest and leaves the rest open. The data-frame + close
|
|
1258
|
+
cases gate on `rpc_path` (they drive the standard account/admin actions); all
|
|
1259
|
+
cases gate on `capabilities.sse`. Cross-process only — wire from a
|
|
1260
|
+
`*.cross.test.ts`. fuz_app's own wiring is
|
|
1261
|
+
`src/test/cross_backend/sse.cross.test.ts`, which runs on every backend — the
|
|
1262
|
+
TS spines wire `audit_log_sse` and the Rust `spine_stub` serves the same
|
|
1263
|
+
stream.
|
|
1187
1264
|
|
|
1188
1265
|
### `cross_backend/cell_crud.ts` + `cell_relations.ts` + `cell_gated_create.ts` — cell parity suites
|
|
1189
1266
|
|
|
@@ -1292,7 +1369,8 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1292
1369
|
transport carries the keeper session cookie in its jar after this call
|
|
1293
1370
|
resolves.
|
|
1294
1371
|
- `testing/transports/ws_client.ts` — shared `WsClient` interface (`send` /
|
|
1295
|
-
`request` / `close` / `messages` / `wait_for` / `wait_for_close` / `close_code`
|
|
1372
|
+
`request` / `close` / `messages` / `wait_for` / `wait_for_close` / `close_code` /
|
|
1373
|
+
`close_reason`),
|
|
1296
1374
|
wire-frame types, and
|
|
1297
1375
|
predicates (`is_notification`, `is_response_for`, ...). Both in-process
|
|
1298
1376
|
(`ws_round_trip.ts`) and cross-process (`ws_transport.ts`) impls satisfy
|
|
@@ -1309,6 +1387,20 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1309
1387
|
Threads the keeper cookie onto the upgrade so per-action auth succeeds on
|
|
1310
1388
|
the first message. `on_request` (attached at construction, before the upgrade
|
|
1311
1389
|
completes) answers server-initiated requests via the shared `deliver_inbound`.
|
|
1390
|
+
An open socket is not yet an *admitted* connection — both spines answer the
|
|
1391
|
+
handshake, then re-read the credential, then admit, and deliver no
|
|
1392
|
+
server-initiated message until then. `create_admitted_ws_transport(options)`
|
|
1393
|
+
opens and round-trips `heartbeat` (response id `WS_ADMISSION_PROBE_ID`) so the
|
|
1394
|
+
connection is admitted when it resolves; suites that wait for a push, or need
|
|
1395
|
+
connections registered in a known order, open through it.
|
|
1396
|
+
- `testing/transports/ws_raw_client.ts` — `connect_raw_ws({port, hostname?, path, headers?, pipelined?})`
|
|
1397
|
+
→ `RawWsClient` (`{frames, send_text, wait_for, destroy}`), a WebSocket client
|
|
1398
|
+
over a raw `node:net` socket for the two things a client library can't do:
|
|
1399
|
+
**withhold the close frame** (it never answers one) and put frames in the
|
|
1400
|
+
server's hands **with the handshake** (`pipelined` — written in the same TCP
|
|
1401
|
+
write as the upgrade request, so before the connection can have been
|
|
1402
|
+
admitted). Small unfragmented text frames only. Helpers `is_raw_ws_close`,
|
|
1403
|
+
`raw_ws_close_code`, `RAW_WS_OPCODE_TEXT` / `_CLOSE`.
|
|
1312
1404
|
- `testing/transports/sse_frame_reader.ts` — `create_sse_frame_reader(reader, default_timeout_ms?)`,
|
|
1313
1405
|
the transport-agnostic SSE framing core over a
|
|
1314
1406
|
`ReadableStreamDefaultReader<Uint8Array>`: `\n\n` framing, per-read timeout,
|
|
@@ -1658,7 +1750,7 @@ re-roll the serve / daemon-info / WS-attach / drain boilerplate:
|
|
|
1658
1750
|
- `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, options)` / `full_spine_rpc_endpoints(ctx, options)` — the **full** live RPC mount: the declared bundle **plus** the off-declared-surface families the binary live-mounts (`_testing_*` backdoors, the cell verb set, the opt-in `actor_lookup` / `actor_search` resolvers). Single-sources what was an inline assembly in `testing_spine_server.ts`, so the binary and the `spine_method_coverage` reconciliation test build the same list. Also `$lib`-free.
|
|
1659
1751
|
- `testing/cross_backend/ts_spine_backend_config.ts` — `ts_spine_node_backend_config()` / `ts_spine_deno_backend_config()` / `ts_spine_bun_backend_config()` presets (in-memory PGlite, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
|
|
1660
1752
|
|
|
1661
|
-
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic.
|
|
1753
|
+
fuz_app's own binary wiring (`src/test/cross_backend/testing_spine_server{,_node,_deno,_bun}.ts`) is the worked example: ~one `build_app` over `create_app_backend` + `create_app_server` + `full_spine_rpc_endpoints` + a WS mount, reusing `default_spine_surface`. The `_node`/`_deno`/`_bun` entries differ only in which adapter they wire — `build_spine_app` is runtime-agnostic. It leaves `create_app_server`'s `auth_cleanup` off, as the Rust `testing_spine_stub` leaves its twin unscheduled: a background pass would delete rows and write audit rows under a running suite. A consumer's cross-process test binary may schedule it when nothing its suites seed is already expired (the conformance table's `expired_session` principal seeds a backdated session row); an in-process harness leaves it off.
|
|
1662
1754
|
|
|
1663
1755
|
### Live-method coverage reconciliation — `method_coverage.ts`
|
|
1664
1756
|
|
|
@@ -1687,8 +1779,8 @@ no-DB reconciliation test `spine_method_coverage.test.ts`, which enumerates
|
|
|
1687
1779
|
The in-process `ws_round_trip` harness stays (it drives the dispatcher
|
|
1688
1780
|
against a fake upgrade, no wire), but the real-upgrade coverage now lives in
|
|
1689
1781
|
the cross-process `cross_backend/ws_round_trip.ts` suite below — including
|
|
1690
|
-
close-on-revoke (`WsClient.wait_for_close` asserts
|
|
1691
|
-
|
|
1782
|
+
close-on-revoke (`WsClient.wait_for_close` asserts a live socket is dropped
|
|
1783
|
+
once `account_session_revoke_all` commits).
|
|
1692
1784
|
|
|
1693
1785
|
`audit_completeness` is in-process by design (FK-structural introspection
|
|
1694
1786
|
beyond the `audit_log_list` RPC reads — structurally in-process).
|
|
@@ -196,7 +196,7 @@ export interface TestAppServerOptions {
|
|
|
196
196
|
* needs:
|
|
197
197
|
* - to capture audit events (compose `on_audit_event` inside the body)
|
|
198
198
|
* - to register consumer event-type schemas (pass `audit_log_config`)
|
|
199
|
-
* - to instrument `emit`
|
|
199
|
+
* - to instrument `emit` (`emit_decorator`)
|
|
200
200
|
* - to wrap or replace the emitter for some other reason
|
|
201
201
|
*
|
|
202
202
|
* Matches the production shape — `create_app_backend` requires an
|
|
@@ -302,7 +302,7 @@ export interface TestApp {
|
|
|
302
302
|
create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
303
303
|
/** Create an additional account with credentials. */
|
|
304
304
|
create_account: (options?: CreateTestAppAccountArgs) => Promise<TestAccount>;
|
|
305
|
-
/** Cleanup resources (
|
|
305
|
+
/** Cleanup resources (`AppServer.close`: stops an opted-in auth cleanup schedule, then the backend's `close`). */
|
|
306
306
|
cleanup: () => Promise<void>;
|
|
307
307
|
}
|
|
308
308
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAU5D,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE5E,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAU5D,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC7F,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAEN,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAIhC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AA0CjD;;;;;GAKG;AACH,MAAM,WAAW,uCAAuC;IACvD,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,2DAA2D;AAC3D,MAAM,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,uCAAuC,KAC9C,OAAO,CAAC;IACV,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAqDA,CAAC;AAEF,uCAAuC;AACvC,MAAM,WAAW,sBAAsB;IACtC,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,sBAAsB,KAC7B,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAQpC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,0BAA0B,KACjC,OAAO,CAAC;IACV,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAQA,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAChD,gCAAgC;IAChC,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,uCAAuC;IACvC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kGAAkG;IAClG,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACzD,yHAAyH;IACzH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAkJD,eAAO,MAAM,sBAAsB,GAClC,SAAS,oBAAoB,KAC3B,OAAO,CAAC,aAAa,CA2BvB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IACjE,yEAAyE;IACzE,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,CACpC,IAAI,CACH,gBAAgB,EAChB,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,GAAG,WAAW,CACpF,CACD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,EAAE;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,CAAC;IACpB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,8DAA8D;IAC9D,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClF;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,kEAAkE;IAClE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,gEAAgE;IAChE,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClF,iEAAiE;IACjE,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxF,qDAAqD;IACrD,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,wBAAwB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7E,kHAAkH;IAClH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAyGpF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAChD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,8EAA8E;IAC9E,SAAS,EAAE,oBAAoB,CAAC;IAChC;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,UAAU,CAAC;IACpB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,mBAAmB,CA2E7B,CAAC"}
|
|
@@ -12,6 +12,7 @@ import { query_create_api_token } from '../auth/api_token_queries.js';
|
|
|
12
12
|
import { create_session_cookie_value } from '../auth/session_cookie.js';
|
|
13
13
|
import { run_migrations } from '../db/migrate.js';
|
|
14
14
|
import { auth_migration_ns } from '../auth/migrations.js';
|
|
15
|
+
import { create_realtime_closer } from '../actions/connection_closer.js';
|
|
15
16
|
import { default_audit_factory } from '../server/app_backend.js';
|
|
16
17
|
import { create_app_server } from '../server/app_server.js';
|
|
17
18
|
import { generate_daemon_token, DAEMON_TOKEN_HEADER } from '../auth/daemon_token.js';
|
|
@@ -200,6 +201,7 @@ const _build_test_backend = async (options) => {
|
|
|
200
201
|
db: existing_db,
|
|
201
202
|
log: test_log,
|
|
202
203
|
audit,
|
|
204
|
+
connection_closer: create_realtime_closer(),
|
|
203
205
|
...fs_stubs
|
|
204
206
|
}
|
|
205
207
|
};
|
|
@@ -228,6 +230,7 @@ const _build_test_backend = async (options) => {
|
|
|
228
230
|
db,
|
|
229
231
|
log: test_log,
|
|
230
232
|
audit,
|
|
233
|
+
connection_closer: create_realtime_closer(),
|
|
231
234
|
...fs_stubs
|
|
232
235
|
}
|
|
233
236
|
};
|
|
@@ -292,6 +295,9 @@ export const create_test_app = async (options) => {
|
|
|
292
295
|
daemon_token_state,
|
|
293
296
|
rpc_endpoints: options.rpc_endpoints,
|
|
294
297
|
bootstrap: options.bootstrap,
|
|
298
|
+
// `auth_cleanup` stays at its default, off: a background pass would
|
|
299
|
+
// delete rows and write audit rows mid-test. A suite that opts in
|
|
300
|
+
// through `app_options` has the schedule stopped by `cleanup`.
|
|
295
301
|
...options.app_options,
|
|
296
302
|
create_route_specs: options.create_route_specs
|
|
297
303
|
});
|
|
@@ -352,7 +358,9 @@ export const create_test_app = async (options) => {
|
|
|
352
358
|
create_bearer_headers,
|
|
353
359
|
create_daemon_token_headers,
|
|
354
360
|
create_account,
|
|
355
|
-
|
|
361
|
+
// the server's `close`, not the backend's: it also stops an auth cleanup
|
|
362
|
+
// schedule a suite opted into
|
|
363
|
+
cleanup: () => result.close()
|
|
356
364
|
};
|
|
357
365
|
};
|
|
358
366
|
/**
|
|
@@ -438,6 +446,7 @@ export const create_test_app_for_bootstrap = async (options) => {
|
|
|
438
446
|
surface: result.surface_spec.surface,
|
|
439
447
|
route_specs: result.surface_spec.route_specs,
|
|
440
448
|
create_request_headers,
|
|
441
|
-
cleanup
|
|
449
|
+
// `AppServer.close`, so an opted-in auth cleanup schedule stops too
|
|
450
|
+
cleanup: () => result.close()
|
|
442
451
|
};
|
|
443
452
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { AuditEmitter } from '../auth/audit_emitter.js';
|
|
3
3
|
import type { AuditLogInput } from '../auth/audit_log_schema.js';
|
|
4
|
-
import type { AuditFactory } from '../server/app_backend.js';
|
|
5
4
|
/**
|
|
6
5
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
7
6
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
@@ -22,16 +21,6 @@ import type { AuditFactory } from '../server/app_backend.js';
|
|
|
22
21
|
* the time the after-each check observes counter state.
|
|
23
22
|
*/
|
|
24
23
|
export declare const install_audit_drift_guard: () => void;
|
|
25
|
-
/**
|
|
26
|
-
* Marker pushed into a shared sequence array by an emit-recording
|
|
27
|
-
* `audit_factory`. Pair with `RecordedClose` from
|
|
28
|
-
* `testing/connection_closer_helpers.ts` to test close-vs-emit ordering at
|
|
29
|
-
* handler call sites — see `create_emit_ordering_audit_factory` below.
|
|
30
|
-
*/
|
|
31
|
-
export interface AuditEmitMarker {
|
|
32
|
-
kind: 'emit';
|
|
33
|
-
at: number;
|
|
34
|
-
}
|
|
35
24
|
/**
|
|
36
25
|
* Pair returned by {@link create_recording_audit_emitter} — the
|
|
37
26
|
* `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
|
|
@@ -77,40 +66,4 @@ export interface RecordingAuditEmitter {
|
|
|
77
66
|
* so the freeze isn't load-bearing here.
|
|
78
67
|
*/
|
|
79
68
|
export declare const create_recording_audit_emitter: (calls_ref?: Array<AuditLogInput>) => RecordingAuditEmitter;
|
|
80
|
-
/**
|
|
81
|
-
* Build an `audit_factory` that produces a real `create_audit_emitter`
|
|
82
|
-
* with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
|
|
83
|
-
* marker into a shared sequence + events array. Used by the close-vs-emit
|
|
84
|
-
* ordering test to compose against a shared sequence counter (typically
|
|
85
|
-
* `create_recording_closer(seq_ref)` capturing eager-close calls).
|
|
86
|
-
*
|
|
87
|
-
* Pass the returned factory through `create_test_app({audit_factory: …})`
|
|
88
|
-
* — the test backend invokes it with its constructed `{db, log}` and
|
|
89
|
-
* lands the decorated emitter on `backend.deps.audit`. Production
|
|
90
|
-
* handlers dereference `deps.audit.emit` at call time, so the decorator
|
|
91
|
-
* sees every subsequent handler invocation. The underlying `emit` still
|
|
92
|
-
* runs — the decorator records the call, it does not suppress side
|
|
93
|
-
* effects.
|
|
94
|
-
*
|
|
95
|
-
* **Scope — both `emit` and `emit_role_grant_target`.** The decorator
|
|
96
|
-
* is captured by `emit_role_grant_target`'s closure inside
|
|
97
|
-
* `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
|
|
98
|
-
* role-grant-shape emissions land in `events_ref` alongside bare `emit`
|
|
99
|
-
* calls. `emit_pool` and `notify` are not decorated — they take
|
|
100
|
-
* `AuditLogInput` / `AuditLogEvent` directly without going through
|
|
101
|
-
* `emit`, so `emit_pool` writes skip capture. Close-firing handlers all reach for
|
|
102
|
-
* `emit` or `emit_role_grant_target`, so the ordering test sees them
|
|
103
|
-
* regardless of which entry point a future refactor picks.
|
|
104
|
-
*
|
|
105
|
-
* Optionally accept `extra_options` to thread `on_audit_event` /
|
|
106
|
-
* `audit_log_config` into the inner emitter — useful when a test wants
|
|
107
|
-
* both ordering capture and a real SSE/WS guard wired into the same
|
|
108
|
-
* emitter chain.
|
|
109
|
-
*/
|
|
110
|
-
export declare const create_emit_ordering_audit_factory: <E extends {
|
|
111
|
-
kind: string;
|
|
112
|
-
at: number;
|
|
113
|
-
}>(seq_ref: {
|
|
114
|
-
value: number;
|
|
115
|
-
}, events_ref: Array<AuditEmitMarker | E>, extra_options?: Omit<CreateAuditEmitterOptions, "db" | "log" | "emit_decorator">) => AuditFactory;
|
|
116
69
|
//# sourceMappingURL=audit_drift_guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,
|
|
1
|
+
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBA8BF,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import { assert, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import { get_audit_metadata_validation_failures, get_audit_unknown_event_type_failures, reset_audit_metadata_validation_failures, reset_audit_unknown_event_type_failures } from '../auth/audit_log_queries.js';
|
|
4
|
-
import { create_audit_emitter } from '../auth/audit_emitter.js';
|
|
5
4
|
/**
|
|
6
5
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
7
6
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
@@ -95,44 +94,3 @@ export const create_recording_audit_emitter = (calls_ref) => {
|
|
|
95
94
|
};
|
|
96
95
|
return { emitter, calls };
|
|
97
96
|
};
|
|
98
|
-
/**
|
|
99
|
-
* Build an `audit_factory` that produces a real `create_audit_emitter`
|
|
100
|
-
* with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
|
|
101
|
-
* marker into a shared sequence + events array. Used by the close-vs-emit
|
|
102
|
-
* ordering test to compose against a shared sequence counter (typically
|
|
103
|
-
* `create_recording_closer(seq_ref)` capturing eager-close calls).
|
|
104
|
-
*
|
|
105
|
-
* Pass the returned factory through `create_test_app({audit_factory: …})`
|
|
106
|
-
* — the test backend invokes it with its constructed `{db, log}` and
|
|
107
|
-
* lands the decorated emitter on `backend.deps.audit`. Production
|
|
108
|
-
* handlers dereference `deps.audit.emit` at call time, so the decorator
|
|
109
|
-
* sees every subsequent handler invocation. The underlying `emit` still
|
|
110
|
-
* runs — the decorator records the call, it does not suppress side
|
|
111
|
-
* effects.
|
|
112
|
-
*
|
|
113
|
-
* **Scope — both `emit` and `emit_role_grant_target`.** The decorator
|
|
114
|
-
* is captured by `emit_role_grant_target`'s closure inside
|
|
115
|
-
* `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
|
|
116
|
-
* role-grant-shape emissions land in `events_ref` alongside bare `emit`
|
|
117
|
-
* calls. `emit_pool` and `notify` are not decorated — they take
|
|
118
|
-
* `AuditLogInput` / `AuditLogEvent` directly without going through
|
|
119
|
-
* `emit`, so `emit_pool` writes skip capture. Close-firing handlers all reach for
|
|
120
|
-
* `emit` or `emit_role_grant_target`, so the ordering test sees them
|
|
121
|
-
* regardless of which entry point a future refactor picks.
|
|
122
|
-
*
|
|
123
|
-
* Optionally accept `extra_options` to thread `on_audit_event` /
|
|
124
|
-
* `audit_log_config` into the inner emitter — useful when a test wants
|
|
125
|
-
* both ordering capture and a real SSE/WS guard wired into the same
|
|
126
|
-
* emitter chain.
|
|
127
|
-
*/
|
|
128
|
-
export const create_emit_ordering_audit_factory = (seq_ref, events_ref, extra_options) => {
|
|
129
|
-
return ({ db, log }) => create_audit_emitter({
|
|
130
|
-
...extra_options,
|
|
131
|
-
db,
|
|
132
|
-
log,
|
|
133
|
-
emit_decorator: (inner) => (ctx, input) => {
|
|
134
|
-
events_ref.push({ kind: 'emit', at: seq_ref.value++ });
|
|
135
|
-
inner(ctx, input);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
};
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
3
|
-
/**
|
|
4
|
-
* Record of a single `ConnectionCloser` method invocation. `at` is the
|
|
5
|
-
* value of a monotonically-increasing sequence counter at the time of
|
|
6
|
-
* the call — pair with `create_emit_ordering_audit_factory` to record both
|
|
7
|
-
* close + audit emit calls into the same sequence for ordering tests.
|
|
8
|
-
*/
|
|
3
|
+
/** Record of a single `ConnectionCloser` method invocation. */
|
|
9
4
|
export interface RecordedClose {
|
|
10
5
|
method: 'session' | 'token' | 'account';
|
|
11
6
|
id: string;
|
|
12
|
-
at: number;
|
|
13
7
|
}
|
|
14
8
|
export interface RecordingCloser {
|
|
15
9
|
closer: ConnectionCloser;
|
|
@@ -17,24 +11,17 @@ export interface RecordingCloser {
|
|
|
17
11
|
}
|
|
18
12
|
/**
|
|
19
13
|
* Build a `ConnectionCloser` that records every call into `calls` rather
|
|
20
|
-
* than touching real transports. Each method returns 1 ("one
|
|
21
|
-
* closed") regardless of whether a real
|
|
22
|
-
*
|
|
14
|
+
* than touching real transports. Each method returns 1 ("one connection
|
|
15
|
+
* closed") regardless of whether a real connection exists — handlers
|
|
16
|
+
* ignore the return value.
|
|
23
17
|
*
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
* uses a fresh internal counter — `at: N` values within a single test
|
|
28
|
-
* are meaningful, but cannot be compared against audit emit ordering.
|
|
18
|
+
* To see a handler's closes, add the closer to the backend's
|
|
19
|
+
* `deps.connection_closer` (`RealtimeCloser.add`), or build the action
|
|
20
|
+
* factory's deps with it.
|
|
29
21
|
*/
|
|
30
|
-
export declare const create_recording_closer: (
|
|
31
|
-
value: number;
|
|
32
|
-
}) => RecordingCloser;
|
|
22
|
+
export declare const create_recording_closer: () => RecordingCloser;
|
|
33
23
|
/**
|
|
34
|
-
* Pin `{method, id}` on a single recorded close call
|
|
35
|
-
* the `at: N` sequence number. Use at every "did the closer fire?"
|
|
36
|
-
* assertion site; the sequence number is only meaningful for dedicated
|
|
37
|
-
* ordering tests (paired with `create_emit_ordering_audit_factory`).
|
|
24
|
+
* Pin `{method, id}` on a single recorded close call.
|
|
38
25
|
*
|
|
39
26
|
* Throws via `assert.ok` if `call` is `undefined` — index a recorded
|
|
40
27
|
* `calls` array directly (`calls[0]`) and let this helper handle the
|