@fuzdev/fuz_app 0.134.0 → 0.136.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 +93 -11
- package/dist/actions/register_action_ws.d.ts +23 -3
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +57 -37
- package/dist/actions/transports.d.ts +23 -0
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +23 -0
- package/dist/actions/transports_ws_backend.d.ts +133 -10
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +237 -34
- package/dist/actions/ws_endpoint_spec.d.ts +9 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/actions/ws_outbound_writer.d.ts +206 -0
- package/dist/actions/ws_outbound_writer.d.ts.map +1 -0
- package/dist/actions/ws_outbound_writer.js +431 -0
- package/dist/auth/CLAUDE.md +10 -4
- package/dist/auth/account_action_specs.d.ts +7 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +11 -1
- package/dist/auth/audit_emitter.d.ts +4 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +18 -2
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +3 -1
- package/dist/auth/cell_action_specs.d.ts +10 -0
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +17 -3
- package/dist/auth/cell_actions.js +1 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +7 -2
- package/dist/auth/role_schema.d.ts +14 -3
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +14 -2
- package/dist/auth/scope_kind_schema.d.ts +10 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -1
- package/dist/auth/scope_kind_schema.js +11 -0
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +2 -2
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/http/CLAUDE.md +2 -1
- package/dist/http/body_limit.d.ts +74 -0
- package/dist/http/body_limit.d.ts.map +1 -0
- package/dist/http/body_limit.js +100 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +5 -3
- package/dist/http/error_schemas.d.ts +2 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +2 -0
- package/dist/realtime/outbound_budget.d.ts +91 -0
- package/dist/realtime/outbound_budget.d.ts.map +1 -0
- package/dist/realtime/outbound_budget.js +108 -0
- package/dist/realtime/sse.d.ts +36 -3
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +58 -6
- package/dist/realtime/sse_constants.d.ts +12 -0
- package/dist/realtime/sse_constants.d.ts.map +1 -1
- package/dist/realtime/sse_constants.js +12 -0
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +3 -6
- package/dist/testing/CLAUDE.md +96 -19
- package/dist/testing/admin_integration.js +1 -1
- package/dist/testing/audit_completeness.d.ts +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +19 -6
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +25 -9
- package/dist/testing/cross_backend/body_size.d.ts +18 -5
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +233 -34
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +0 -12
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +103 -108
- package/dist/testing/cross_backend/capabilities.d.ts +3 -21
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +1 -3
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +31 -2
- package/dist/testing/cross_backend/conformance_case.d.ts +3 -3
- package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_header_robustness.js +18 -54
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +2 -8
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts +6 -0
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_role_grammar.js +117 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +2 -3
- 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 +2 -3
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +10 -4
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +87 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +106 -2
- package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +34 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +8 -13
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +29 -0
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_outbound_overflow.js +276 -0
- package/dist/testing/integration.js +1 -1
- package/dist/testing/schema_introspect.d.ts +30 -2
- package/dist/testing/schema_introspect.d.ts.map +1 -1
- package/dist/testing/schema_introspect.js +38 -7
- package/dist/testing/transports/raw_http.d.ts +78 -0
- package/dist/testing/transports/raw_http.d.ts.map +1 -0
- package/dist/testing/transports/raw_http.js +141 -0
- package/dist/testing/transports/ws_transport.d.ts +4 -2
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +2 -1
- package/dist/testing/ws_round_trip.d.ts +32 -2
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +26 -4
- package/package.json +1 -1
- package/dist/env/resolve.d.ts +0 -163
- package/dist/env/resolve.d.ts.map +0 -1
- package/dist/env/resolve.js +0 -267
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Hono } from 'hono';
|
|
11
11
|
import { logger } from 'hono/logger';
|
|
12
|
-
import { bodyLimit } from 'hono/body-limit';
|
|
13
12
|
import { z } from 'zod';
|
|
14
13
|
import { session_cookie_options } from '../auth/session_cookie.js';
|
|
15
14
|
import { create_audit_log_sse, create_sse_connection_closer, audit_log_event_specs } from '../realtime/sse_auth_guard.js';
|
|
@@ -29,7 +28,7 @@ import { flush_pending_effects, flush_post_commit_effects } from '../http/pendin
|
|
|
29
28
|
import { assert_endpoint_in_auth_scope, assert_middleware_stack_mounted, create_auth_middleware_specs } from '../auth/middleware.js';
|
|
30
29
|
import { fuz_auth_guard_resolver } from '../auth/auth_guard_resolver.js';
|
|
31
30
|
import { create_fuz_authorization_handler } from '../auth/request_context.js';
|
|
32
|
-
import {
|
|
31
|
+
import { create_body_limit_middleware } from '../http/body_limit.js';
|
|
33
32
|
import { create_rpc_endpoint } from '../actions/action_rpc.js';
|
|
34
33
|
import { register_ws_endpoint } from '../actions/register_ws_endpoint.js';
|
|
35
34
|
import { resolve_ws_endpoints } from '../actions/ws_endpoint_spec.js';
|
|
@@ -483,10 +482,7 @@ const assemble_app_server = async (options, releases) => {
|
|
|
483
482
|
// Default 1 MiB; pass null to disable.
|
|
484
483
|
if (options.max_body_size !== null) {
|
|
485
484
|
const max_size = options.max_body_size ?? DEFAULT_MAX_BODY_SIZE;
|
|
486
|
-
app.use(
|
|
487
|
-
maxSize: max_size,
|
|
488
|
-
onError: (c) => c.json({ error: ERROR_PAYLOAD_TOO_LARGE }, 413)
|
|
489
|
-
}));
|
|
485
|
+
app.use(create_body_limit_middleware(max_size));
|
|
490
486
|
}
|
|
491
487
|
apply_middleware_specs(app, middleware_specs);
|
|
492
488
|
const authorize = create_fuz_authorization_handler({ db: deps.db });
|
|
@@ -524,6 +520,7 @@ const assemble_app_server = async (options, releases) => {
|
|
|
524
520
|
// added below instead, so the remover is the server's
|
|
525
521
|
connection_closer: null,
|
|
526
522
|
max_connections_per_account: endpoint.max_connections_per_account,
|
|
523
|
+
outbound_queue_bytes: endpoint.outbound_queue_bytes,
|
|
527
524
|
heartbeat: endpoint.heartbeat,
|
|
528
525
|
artificial_delay: endpoint.artificial_delay,
|
|
529
526
|
max_message_bytes: endpoint.max_message_bytes,
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -99,7 +99,8 @@ mismatch.
|
|
|
99
99
|
|
|
100
100
|
### `audit_drift_guard.ts` — audit-emission validation
|
|
101
101
|
|
|
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
|
|
102
|
+
- `install_audit_drift_guard()` — `beforeEach` resets + `afterEach` zero-checks the `audit_metadata_validation_failures` + `audit_unknown_event_type_failures` counters from `auth/audit_log_queries.ts` and the emitter's write-failure counter (`get_audit_write_failures` in `auth/audit_emitter.ts`). Call once at the top of any `describe_db` block firing audit emits — production validation and the emitter's write are fail-open, so without this any regression shipping a typo'd `event_type` or undeclared metadata field, or losing a row the database refused, is silent. A test that fails a write on purpose resets the counter (`reset_audit_write_failures`) once it has asserted it.
|
|
103
|
+
- `assert_no_audit_drift()` — the zero-check itself, for a test that manages the counters. Pair with `await_pending_effects: true` (the `create_test_app` default) so fire-and-forget audit writes complete by response time.
|
|
103
104
|
- `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. `add_listener` records (never fires) and returns a remover with the production semantics, so `listener_count` tracks what a server registered and released. Replaces per-file capturing emitters previously duplicated across `password_change.test.ts`, `audit_log.test.ts`, etc.
|
|
104
105
|
- `RecordingAuditEmitter` — `{emitter: AuditEmitter; calls: Array<AuditLogInput>}`.
|
|
105
106
|
|
|
@@ -223,7 +224,7 @@ test-only by construction.
|
|
|
223
224
|
|
|
224
225
|
### `schema_introspect.ts` — `query_schema_snapshot`
|
|
225
226
|
|
|
226
|
-
- `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), sequences, and enum types (`pg_enum` labels in declared `enumsortorder`, so a `cell_visibility` label-set/order drift is gated). The `schema_version` migration tracker is always excluded — it's framework bookkeeping, not domain schema
|
|
227
|
+
- `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Throws on pglet when the snapshot holds an index, unless `accept_pglet_index_definitions` is passed: pglet's `indexdef` omits index modifiers, so its index definitions read as false differences against PostgreSQL and false agreement between two pglet schemas. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), sequences, and enum types (`pg_enum` labels in declared `enumsortorder`, so a `cell_visibility` label-set/order drift is gated). The `schema_version` migration tracker is always excluded — it's framework bookkeeping, not domain schema; twin impls keep it identical (same namespaces, names and partitioning), which `query_migration_tracker` + `assert_migration_trackers_equal` gate separately. Twinned by `fuz_db::query_schema_snapshot` (Rust); the `_testing_schema_snapshot` RPC's wire validator is the shared `SchemaSnapshot`, so the enum field must serialize on both sides.
|
|
227
228
|
- `SchemaSnapshot` — the Zod schema is canonical (co-located in `schema_introspect.ts`; the cross-impl `_testing_schema_snapshot` RPC action reuses it as its wire validator, and the type is `z.infer`'d from it). Fully JSON-serializable; every collection deterministically sorted on capture so structural equality is stable across runs.
|
|
228
229
|
- `query_migration_tracker(db)` / `MigrationTracker` — the migration-_identity_ twin of the snapshot: reads the `schema_version` tracker rows (`[{namespace, name, sequence}]`) the snapshot deliberately _excludes_. `MigrationTracker` is the `_testing_migration_tracker` RPC's wire validator; pairs with `assert_migration_trackers_equal` to gate that the two spines record byte-identical migration identity (the swap-freely invariant), catching a migration-name or namespace-partitioning drift the provenance-agnostic schema snapshot can't see.
|
|
229
230
|
|
|
@@ -235,7 +236,7 @@ test-only by construction.
|
|
|
235
236
|
- `SchemaDiff` — tagged-union per drift kind: `table_only_in`, `column_only_in`, `column_field_differs`, `index_only_in`, `index_definition_differs`, `constraint_only_in`, `constraint_differs`, `sequence_only_in`, `sequence_data_type_differs`, `enum_only_in`, `enum_labels_differ` (enum labels compared positionally — declared order is significant).
|
|
236
237
|
- `diff_migration_trackers(a, b)` / `format_migration_tracker_diffs(diffs, labels?)` / `assert_migration_trackers_equal(a, b, labels?)` — the migration-identity twins of the snapshot diff/format/assert, over two `MigrationTracker`s. `MigrationTrackerDiff` is keyed on `(namespace, name)` (the `schema_version` PK): `tracker_row_only_in` (a migration rename or namespace re-partition) and `tracker_sequence_differs` (a re-order).
|
|
237
238
|
|
|
238
|
-
fuz_app's own spine gates this cross-process via the `cross_backend_parity` project (`schema_parity.cross.test.ts` + the dual-spawn `global_setup_schema_parity.ts`), diffing the TS spine ↔ `testing_spine_stub` full schema — `npm run test:cross:parity`. The forge has its own deno↔rust parity gate.
|
|
239
|
+
fuz_app's own spine gates this cross-process via the `cross_backend_parity` project (`schema_parity.cross.test.ts` + the dual-spawn `global_setup_schema_parity.ts`), diffing the TS spine ↔ `testing_spine_stub` full schema — `npm run test:cross:parity`. Both legs run on PostgreSQL there: index definitions are compared as the engine renders them, and pglet (what `memory://` gives a TS backend) leaves sort direction, `NULLS` placement, operator classes and `NULLS NOT DISTINCT` out of `indexdef`, so a snapshot taken on it differs from a PostgreSQL one with identical DDL. The forge has its own deno↔rust parity gate.
|
|
239
240
|
|
|
240
241
|
## Cross-impl RPC-manifest parity
|
|
241
242
|
|
|
@@ -552,7 +553,7 @@ cover real sockets.
|
|
|
552
553
|
|
|
553
554
|
Three layers:
|
|
554
555
|
|
|
555
|
-
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. `create_stub_upgrade()` stands in for a runtime adapter's `upgradeWebSocket`: it passes a request without `Upgrade: websocket` to `next()`, and records an upgrade request — its real `Context` and the endpoint's events factory — in `upgrades`, answering `200`. So a test can send `app.request('/api/ws', {headers: {upgrade: 'websocket', cookie | authorization, origin, 'x-forwarded-for'}})` through an assembled app's whole middleware chain and build the socket's events from the `Context` that chain produced (`src/test/server/create_app_server_ws_endpoints.dispatch.db.test.ts`); `get_create_events()` still returns the last mounted factory, for a hand-built context.
|
|
556
|
+
1. **Primitives** — `create_fake_ws(options?)` (a real `WSContext` over capture arrays `sends` / `closes`, plus `socket` — the fake runtime socket behind `ws.raw`, shaped like Node's `ws`: a `bufferedAmount` the transport's outbound writer reads for backpressure, `stalled` (start it with `{stalled: true}`) to model a client that stopped reading, and `terminate` / `terminated` for the frameless cut; `readyState` reads open until closed or terminated), `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. `create_stub_upgrade()` stands in for a runtime adapter's `upgradeWebSocket`: it passes a request without `Upgrade: websocket` to `next()`, and records an upgrade request — its real `Context` and the endpoint's events factory — in `upgrades`, answering `200`. So a test can send `app.request('/api/ws', {headers: {upgrade: 'websocket', cookie | authorization, origin, 'x-forwarded-for'}})` through an assembled app's whole middleware chain and build the socket's events from the `Context` that chain produced (`src/test/server/create_app_server_ws_endpoints.dispatch.db.test.ts`); `get_create_events()` still returns the last mounted factory, for a hand-built context.
|
|
556
557
|
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).
|
|
557
558
|
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).
|
|
558
559
|
|
|
@@ -927,7 +928,7 @@ source of truth for wire-shape conformance.
|
|
|
927
928
|
- `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
|
|
928
929
|
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
929
930
|
`cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
|
|
930
|
-
`account_status` / `
|
|
931
|
+
`account_status` / `peer_request` /
|
|
931
932
|
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` —
|
|
932
933
|
each has a `test_if`
|
|
933
934
|
reader (`ws_self_revocation_reply` forks one case's assertion instead); `peer_request` (server-initiated requests — the ActionPeer
|
|
@@ -971,15 +972,17 @@ source of truth for wire-shape conformance.
|
|
|
971
972
|
case — `GET /api/account/status` is bundled into `create_account_route_specs`,
|
|
972
973
|
so every spine serves it (`true`); the gate fails loud if a declaring backend
|
|
973
974
|
doesn't mount it (replacing the old runtime 404-sniff-skip).
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
975
|
+
No flag covers the body-size smuggling probe: every backend closes the
|
|
976
|
+
connection after an oversized-body 413 (at once on Node, Bun and the Rust
|
|
977
|
+
spine, once the declared body has arrived on Deno), so the pipelined request
|
|
978
|
+
goes unanswered. Bun 1.4.3 is the tested floor — an older Bun kept the
|
|
979
|
+
connection and served the next request, correctly framed, and fails the probe.
|
|
978
980
|
`ws_handshake_pipelining` gates the WS round-trip suite's
|
|
979
981
|
frames-with-the-handshake case (`true` Node/Deno/Rust — frames written in the
|
|
980
|
-
same TCP write as the upgrade request reach the socket; `false` Bun,
|
|
981
|
-
|
|
982
|
-
|
|
982
|
+
same TCP write as the upgrade request reach the socket; `false` Bun, which
|
|
983
|
+
answers such a request `400` when the upgrade follows an asynchronous check,
|
|
984
|
+
as it does behind auth, and `false` in-process, where there is no socket to
|
|
985
|
+
write raw bytes to).
|
|
983
986
|
`ws_account_actions` (the WS endpoint mounts the account actions — `true` on
|
|
984
987
|
fuz_app's own spine presets, `false` in the family defaults) gates the WS
|
|
985
988
|
round-trip suite's self-revocation case and, with `rpc_path`, its
|
|
@@ -1218,6 +1221,36 @@ ws_path})`. The wire literals are independent copies of the producers'
|
|
|
1218
1221
|
constants, so a value changed on one spine fails here. Cross-process only;
|
|
1219
1222
|
fuz_app's own wiring is `src/test/cross_backend/ws_inbound_dispatch.cross.test.ts`.
|
|
1220
1223
|
|
|
1224
|
+
### `cross_backend/ws_outbound_overflow.ts` — `describe_ws_outbound_overflow_cross_tests`
|
|
1225
|
+
|
|
1226
|
+
Real-upgrade parity for the **per-connection outbound bound** — the byte
|
|
1227
|
+
budget both spines hold a WebSocket's waiting frames to
|
|
1228
|
+
(`BackendWebsocketTransport`'s `outbound_queue_bytes`, the Rust `fuz_realtime`
|
|
1229
|
+
outbound queue; same default, same per-frame charge). The close code and its
|
|
1230
|
+
reason are wire contract — a client reads `WS_CLOSE_OUTBOUND_OVERFLOW` as "you
|
|
1231
|
+
fell behind, reconnect" — so this is the pin that runs on both.
|
|
1232
|
+
`describe_ws_outbound_overflow_cross_tests({setup_test, capabilities, base_url,
|
|
1233
|
+
ws_path, origin?, rpc_path?})` drives `_testing_flood_notifications`, which
|
|
1234
|
+
queues a burst onto the caller's own connection in one synchronous run, so the
|
|
1235
|
+
burst waits at once whether or not the client reads and the overflow is
|
|
1236
|
+
deterministic. Cases: a burst within the budget arrives whole and in order
|
|
1237
|
+
ahead of its response, the socket left open — a small one, and one of a few
|
|
1238
|
+
mebibytes in megabyte frames, most of the default budget, which waits on the backend's write pump where megabyte frames outrun the socket's buffer (Deno; Node and Bun write the burst through); a burst far past it closes the
|
|
1239
|
+
socket with the code and the exact reason, no response for the flooding request
|
|
1240
|
+
arrives, and any flood frames that did arrive are a contiguous prefix of the
|
|
1241
|
+
burst — while a second connection on the same account keeps dispatching; a
|
|
1242
|
+
burst past the action's own size limit is refused `invalid_params` with the
|
|
1243
|
+
socket left open; and, with `rpc_path`, the action over HTTP refuses
|
|
1244
|
+
`invalid_params` with reason `no_connection`. Not asserted, because each
|
|
1245
|
+
implementation decides them: how many frames accepted before the overflow
|
|
1246
|
+
reach the client, and the boundary a burst of tiny frames overflows at. The
|
|
1247
|
+
flood action is daemon-token-gated, so the flooding socket upgrades with the
|
|
1248
|
+
daemon token and no `Origin` (`create_ws_transport`'s `origin: null`). The wire
|
|
1249
|
+
literals are independent copies of the producers' constants. Gated on
|
|
1250
|
+
`capabilities.ws`; cross-process only, and only for a backend that mounts the
|
|
1251
|
+
flood action on its WS endpoint. fuz_app's own wiring is
|
|
1252
|
+
`src/test/cross_backend/ws_outbound_overflow.cross.test.ts`.
|
|
1253
|
+
|
|
1221
1254
|
### `cross_backend/ws_connection_cap.ts` — `describe_ws_connection_cap_cross_tests`
|
|
1222
1255
|
|
|
1223
1256
|
Real-upgrade parity for the **per-account WebSocket connection cap** — the
|
|
@@ -1312,7 +1345,7 @@ an audit row, an admin account listing, and an offer's `created_at` /
|
|
|
1312
1345
|
`expires_at`. `cross_backend/setup.ts` carries the four bases every
|
|
1313
1346
|
imperative cross suite aliases: `CrossSuiteOptions` (`setup_test` alone),
|
|
1314
1347
|
`CapabilityGatedCrossSuiteOptions` (+ `capabilities` — `ready`),
|
|
1315
|
-
`RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size, actor
|
|
1348
|
+
`RpcPathCrossSuiteOptions` (+ `rpc_path?` — origin, body-size (which adds `base_url`), actor
|
|
1316
1349
|
lookup/search, app settings, testing backdoor), and the intersection
|
|
1317
1350
|
`RpcPathCapabilityGatedCrossSuiteOptions` (every `cell_*` suite, query-shape,
|
|
1318
1351
|
token-scope surface, fact-serving, account lifecycle). A suite takes the
|
|
@@ -1325,7 +1358,8 @@ the option type states whether the bundle is read.
|
|
|
1325
1358
|
edit/read/delete → 404 IDOR mask; admin reaches any; dup active `path` → 409;
|
|
1326
1359
|
`path` write by non-admin → 403 on create + update; `cell_get` with no
|
|
1327
1360
|
id/path → `invalid_params`; null-auth `cell_list` `created_by` →
|
|
1328
|
-
`invalid_params`
|
|
1361
|
+
`invalid_params`; a `kind` at `CELL_KIND_LENGTH_MAX` stored whole, one
|
|
1362
|
+
UTF-16 code unit past it → `invalid_params` on create and as a list filter).
|
|
1329
1363
|
- **`describe_cell_relations_cross_tests`** (gates on
|
|
1330
1364
|
`capabilities.cell_relations`) — the verbs beyond CRUD: grant lifecycle
|
|
1331
1365
|
(actor-shaped editor grant enables edit, manage-tier `cell_grant_list`,
|
|
@@ -1408,7 +1442,7 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1408
1442
|
request (`{result}` / `{error}` / `undefined` to swallow), the path
|
|
1409
1443
|
`describe_peer_ping_ws_tests` drives. With no `on_request` the seam is inert
|
|
1410
1444
|
(a server-initiated request flows to `messages` as before).
|
|
1411
|
-
- `testing/transports/ws_transport.ts` — `create_ws_transport({base_url, ws_path, cookies, origin?, on_request?, headers?})`
|
|
1445
|
+
- `testing/transports/ws_transport.ts` — `create_ws_transport({base_url, ws_path, cookies, origin?, on_request?, headers?})` (`origin: null` sends no `Origin`, which a daemon-token upgrade needs)
|
|
1412
1446
|
builds a real-upgrade WS client using the `ws` npm package (optional
|
|
1413
1447
|
peerDep; consumers wiring cross-process tests `npm install --save-dev ws`).
|
|
1414
1448
|
Threads the keeper cookie onto the upgrade so per-action auth succeeds on
|
|
@@ -1430,6 +1464,21 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1430
1464
|
write as the upgrade request, so before the connection can have been
|
|
1431
1465
|
admitted). Small unfragmented text frames only. Helpers `is_raw_ws_close`,
|
|
1432
1466
|
`raw_ws_close_code`, `RAW_WS_OPCODE_TEXT` / `_CLOSE`.
|
|
1467
|
+
- `testing/transports/raw_http.ts` — `raw_http_exchange(base_url, request_bytes, {read_timeout_ms, until?})`
|
|
1468
|
+
→ `RawHttpExchange` (`{received, closed_by_server}`): writes caller-built
|
|
1469
|
+
HTTP/1.1 bytes to a raw `node:net` socket once and reads what the server
|
|
1470
|
+
sends until it closes (`until: 'close'`, the default), until one whole
|
|
1471
|
+
response has arrived (`until: 'response'` — for a server that answers and
|
|
1472
|
+
keeps the connection), or until the read timeout. For what `fetch` can't
|
|
1473
|
+
send: two requests in one write, duplicate or malformed headers, a request
|
|
1474
|
+
head whose body never follows, an unterminated chunked body. Write errors are
|
|
1475
|
+
swallowed, and a write error ends the socket — so a caller that must read a
|
|
1476
|
+
refusal reliably sends nothing the server will leave unread. Helpers
|
|
1477
|
+
`raw_http_parse_response` (the first response, `null` until its head and
|
|
1478
|
+
framed body are whole), `raw_http_count_responses` (unanchored status-line
|
|
1479
|
+
count, so a response concatenated after a body is seen),
|
|
1480
|
+
`raw_http_first_status`. Shared by the body-size, body-size smuggling, and
|
|
1481
|
+
credential-header suites.
|
|
1433
1482
|
- `testing/transports/sse_frame_reader.ts` — `create_sse_frame_reader(reader, default_timeout_ms?)`,
|
|
1434
1483
|
the transport-agnostic SSE framing core over a
|
|
1435
1484
|
`ReadableStreamDefaultReader<Uint8Array>`: `\n\n` framing, per-read timeout,
|
|
@@ -1496,6 +1545,17 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1496
1545
|
emitter, so the shared suite body can call the barrier on every backend
|
|
1497
1546
|
uniformly.
|
|
1498
1547
|
|
|
1548
|
+
Also bundled: `_testing_flood_notifications`
|
|
1549
|
+
(`create_testing_flood_notifications_action()`) — queues `count`
|
|
1550
|
+
notifications of `size` payload bytes each (method `_testing_flood`, params
|
|
1551
|
+
`{index, payload}`) onto the caller's own WebSocket connection in one
|
|
1552
|
+
synchronous run and answers `{queued: count}`, so a suite can push a
|
|
1553
|
+
connection's outbound queue past its byte budget from outside. Refused with
|
|
1554
|
+
`invalid_params` past `TESTING_FLOOD_MAX_COUNT`, `TESTING_FLOOD_MAX_SIZE`, or
|
|
1555
|
+
`TESTING_FLOOD_MAX_TOTAL_BYTES` (`count` × `size`), and over HTTP (reason
|
|
1556
|
+
`no_connection` — there is no connection to queue onto). The Rust mirror is
|
|
1557
|
+
`fuz_testing::create_testing_flood_notifications_action_spec`.
|
|
1558
|
+
|
|
1499
1559
|
Also bundled: `_testing_mint_session` — mints a backdated-expiry
|
|
1500
1560
|
`auth_session` row for an account (via `mint_test_session` in `app_server.ts`)
|
|
1501
1561
|
and returns its signed cookie value (future-dated payload). Backs the
|
|
@@ -1601,14 +1661,31 @@ module's own tests (`account_status.test.ts`, `action_rpc.test.ts`,
|
|
|
1601
1661
|
`serve_fact_route.db.test.ts`); fuz_app's own wiring is
|
|
1602
1662
|
`cross_backend/query_shape.cross.test.ts`.
|
|
1603
1663
|
|
|
1664
|
+
### Grant-verb role grammar parity — `cross_backend/role_grant_role_grammar.ts`
|
|
1665
|
+
|
|
1666
|
+
`describe_role_grant_role_grammar_cross_tests({setup_test, rpc_path?})` — an
|
|
1667
|
+
off-grammar `role` (uppercase, hyphen, digit, leading or trailing underscore,
|
|
1668
|
+
space, empty, or all lowercase and one character past the length cap) on `role_grant_assign` and `role_grant_offer_create` answers
|
|
1669
|
+
`invalid_params` with no `data.reason`, and after the refusals no
|
|
1670
|
+
`role_grant_create` / `role_grant_offer_create` audit row carries one of those
|
|
1671
|
+
strings. The control — an on-grammar role no registry holds, refused past
|
|
1672
|
+
validation with `role_not_web_grantable` — keeps the audit read from being
|
|
1673
|
+
vacuous, and the same refusal for a role exactly at the length cap pins that
|
|
1674
|
+
boundary. Exists because the action-manifest parity gate captures no param
|
|
1675
|
+
schemas while the Rust spine parses params by hand, and the grant verbs write
|
|
1676
|
+
the role they refuse into their failure rows. Ungated; cross-process only (it
|
|
1677
|
+
reads the audit log behind `_testing_drain_effects`). fuz_app's own wiring is
|
|
1678
|
+
`src/test/cross_backend/role_grant_role_grammar.cross.test.ts`.
|
|
1679
|
+
|
|
1604
1680
|
### API-token lifetime parity — `cross_backend/token_lifetime.ts`
|
|
1605
1681
|
|
|
1606
1682
|
`describe_token_lifetime_cross_tests({setup_test, rpc_path?})` — the
|
|
1607
1683
|
imperative round-trip on `account_token_create`'s required `lifetime` union:
|
|
1608
1684
|
a `{kind:'ttl', days}` mint returns and stores a bounded `expires_at`
|
|
1609
1685
|
(create output + `account_token_list` read-back), `{kind:'eternal'}` stores
|
|
1610
|
-
NULL,
|
|
1611
|
-
spines
|
|
1686
|
+
NULL, an omitted or out-of-range lifetime 400s identically on both
|
|
1687
|
+
spines, and a `name` at `API_TOKEN_NAME_LENGTH_MAX` mints and reads back whole
|
|
1688
|
+
while one UTF-16 code unit past it is `invalid_params`. Exists because the action-manifest parity gate captures no param
|
|
1612
1689
|
schemas while the Rust spine hand-parses params — a spine that silently
|
|
1613
1690
|
dropped the field would answer the same mint with a 200 and an eternal
|
|
1614
1691
|
token; only the stored-state read-back catches it. Ungated (mint + list are
|
|
@@ -1645,8 +1722,8 @@ drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
|
|
|
1645
1722
|
|
|
1646
1723
|
`describe_testing_backdoor_cross_tests({setup_test, rpc_path?, ws?})` —
|
|
1647
1724
|
the negative-credential parity suite for the `_testing_*` backdoor actions.
|
|
1648
|
-
For each `_testing_*` action
|
|
1649
|
-
|
|
1725
|
+
For each `_testing_*` action (the privileged writes, the audit barrier, the
|
|
1726
|
+
connection flood, and the introspection reads) it fires three principals over real HTTP: **anonymous** →
|
|
1650
1727
|
401 `unauthenticated`, **session** → 403 `forbidden` `credential_type_required`,
|
|
1651
1728
|
**bearer** → 403 `forbidden` `credential_type_required` — and, when `ws`
|
|
1652
1729
|
(`{base_url, ws_path, origin?}`) is passed, sends each on a socket opened with
|
|
@@ -5,7 +5,7 @@ import './assert_dev_env.js';
|
|
|
5
5
|
* `describe_standard_admin_integration_tests` creates a composable test suite
|
|
6
6
|
* that exercises admin account listing, role_grant grant/revoke (via the RPC
|
|
7
7
|
* surface — see `role_grant_offer_create` / `role_grant_revoke`), session/token
|
|
8
|
-
* management, and audit log routes against a real
|
|
8
|
+
* management, and audit log routes against a real database.
|
|
9
9
|
*
|
|
10
10
|
* Consumers call it with their route factory, session config, role schema,
|
|
11
11
|
* and RPC endpoint specs — all admin route tests come for free.
|
|
@@ -34,7 +34,7 @@ export interface AuditCompletenessTestOptions {
|
|
|
34
34
|
* Composable audit log completeness test suite.
|
|
35
35
|
*
|
|
36
36
|
* Verifies that every auth mutation route produces the correct audit log
|
|
37
|
-
* event type. Exercises routes via HTTP requests against a real
|
|
37
|
+
* event type. Exercises routes via HTTP requests against a real
|
|
38
38
|
* database, then reads events back through the `audit_log_list` RPC
|
|
39
39
|
* (the production observation path the admin UI consumes).
|
|
40
40
|
*
|
|
@@ -96,7 +96,7 @@ const json_session_headers = (fixture, extra) => fixture.create_session_headers(
|
|
|
96
96
|
* Composable audit log completeness test suite.
|
|
97
97
|
*
|
|
98
98
|
* Verifies that every auth mutation route produces the correct audit log
|
|
99
|
-
* event type. Exercises routes via HTTP requests against a real
|
|
99
|
+
* event type. Exercises routes via HTTP requests against a real
|
|
100
100
|
* database, then reads events back through the `audit_log_list` RPC
|
|
101
101
|
* (the production observation path the admin UI consumes).
|
|
102
102
|
*
|
|
@@ -1,26 +1,39 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type AuditEmitter } from '../auth/audit_emitter.js';
|
|
3
3
|
import type { AuditLogInput } from '../auth/audit_log_schema.js';
|
|
4
4
|
/**
|
|
5
5
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
6
6
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
7
|
-
* entry,
|
|
7
|
+
* entry, an `event_type` not present in the active `AuditLogConfig`, or a
|
|
8
|
+
* row the emitter failed to write.
|
|
8
9
|
*
|
|
9
10
|
* The production validation in `query_audit_log` is fail-open — it bumps
|
|
10
11
|
* process-wide counters and proceeds, so a regression that emits an
|
|
11
12
|
* undeclared metadata field or a typo'd event-type lands a row that
|
|
12
|
-
* passes downstream queries but breaks forensics.
|
|
13
|
-
*
|
|
13
|
+
* passes downstream queries but breaks forensics. The emitter's pool write
|
|
14
|
+
* is fail-open too: a refused `INSERT` is logged and counted, the request
|
|
15
|
+
* succeeds, and the row is gone. Tests that exercise audit emits should fail
|
|
16
|
+
* loudly when either happens.
|
|
14
17
|
*
|
|
15
18
|
* Call at the top of every `describe` / `describe_db` block that fires
|
|
16
19
|
* audit writes through `deps.audit.emit`. Resets counters before each
|
|
17
|
-
* test and asserts zero on completion.
|
|
20
|
+
* test and asserts zero on completion. A test that makes a write fail on
|
|
21
|
+
* purpose calls `reset_audit_write_failures` (`auth/audit_emitter.ts`) once
|
|
22
|
+
* it has asserted the count.
|
|
18
23
|
*
|
|
19
24
|
* Pair with `await_pending_effects: true` (the default for
|
|
20
25
|
* `create_test_app`) so fire-and-forget audit writes have completed by
|
|
21
26
|
* the time the after-each check observes counter state.
|
|
22
27
|
*/
|
|
23
28
|
export declare const install_audit_drift_guard: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Assert the process-wide audit counters are zero — the check
|
|
31
|
+
* `install_audit_drift_guard` runs after each test. Exported for a test that
|
|
32
|
+
* manages the counters itself.
|
|
33
|
+
*
|
|
34
|
+
* @throws AssertionError naming the first counter that is not zero
|
|
35
|
+
*/
|
|
36
|
+
export declare const assert_no_audit_drift: () => void;
|
|
24
37
|
/**
|
|
25
38
|
* Pair returned by `create_recording_audit_emitter` — the
|
|
26
39
|
* `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
|
|
@@ -36,7 +49,7 @@ export interface RecordingAuditEmitter {
|
|
|
36
49
|
* Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
|
|
37
50
|
* `emit_role_grant_target` call into `calls` as an `AuditLogInput`. Use to
|
|
38
51
|
* capture audit metadata shapes in unit tests (e.g. password change failure
|
|
39
|
-
* outcome, role-grant create denial) without standing up
|
|
52
|
+
* outcome, role-grant create denial) without standing up a database and the
|
|
40
53
|
* `query_audit_log` pipeline.
|
|
41
54
|
*
|
|
42
55
|
* **Capture scope — all four production fan-out shapes.**
|
|
@@ -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,KAAK,
|
|
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,EAGN,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAO5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAgBxC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC"}
|
|
@@ -1,20 +1,26 @@
|
|
|
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 { get_audit_write_failures, reset_audit_write_failures } from '../auth/audit_emitter.js';
|
|
4
5
|
/**
|
|
5
6
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
6
7
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
7
|
-
* entry,
|
|
8
|
+
* entry, an `event_type` not present in the active `AuditLogConfig`, or a
|
|
9
|
+
* row the emitter failed to write.
|
|
8
10
|
*
|
|
9
11
|
* The production validation in `query_audit_log` is fail-open — it bumps
|
|
10
12
|
* process-wide counters and proceeds, so a regression that emits an
|
|
11
13
|
* undeclared metadata field or a typo'd event-type lands a row that
|
|
12
|
-
* passes downstream queries but breaks forensics.
|
|
13
|
-
*
|
|
14
|
+
* passes downstream queries but breaks forensics. The emitter's pool write
|
|
15
|
+
* is fail-open too: a refused `INSERT` is logged and counted, the request
|
|
16
|
+
* succeeds, and the row is gone. Tests that exercise audit emits should fail
|
|
17
|
+
* loudly when either happens.
|
|
14
18
|
*
|
|
15
19
|
* Call at the top of every `describe` / `describe_db` block that fires
|
|
16
20
|
* audit writes through `deps.audit.emit`. Resets counters before each
|
|
17
|
-
* test and asserts zero on completion.
|
|
21
|
+
* test and asserts zero on completion. A test that makes a write fail on
|
|
22
|
+
* purpose calls `reset_audit_write_failures` (`auth/audit_emitter.ts`) once
|
|
23
|
+
* it has asserted the count.
|
|
18
24
|
*
|
|
19
25
|
* Pair with `await_pending_effects: true` (the default for
|
|
20
26
|
* `create_test_app`) so fire-and-forget audit writes have completed by
|
|
@@ -24,17 +30,27 @@ export const install_audit_drift_guard = () => {
|
|
|
24
30
|
beforeEach(() => {
|
|
25
31
|
reset_audit_metadata_validation_failures();
|
|
26
32
|
reset_audit_unknown_event_type_failures();
|
|
33
|
+
reset_audit_write_failures();
|
|
27
34
|
});
|
|
28
|
-
afterEach(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
afterEach(assert_no_audit_drift);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Assert the process-wide audit counters are zero — the check
|
|
39
|
+
* `install_audit_drift_guard` runs after each test. Exported for a test that
|
|
40
|
+
* manages the counters itself.
|
|
41
|
+
*
|
|
42
|
+
* @throws AssertionError naming the first counter that is not zero
|
|
43
|
+
*/
|
|
44
|
+
export const assert_no_audit_drift = () => {
|
|
45
|
+
assert.strictEqual(get_audit_metadata_validation_failures(), 0, 'audit metadata failed schema validation — see audit_log_schema.audit_metadata_schemas');
|
|
46
|
+
assert.strictEqual(get_audit_unknown_event_type_failures(), 0, 'audit emitted an unknown event_type — see AUDIT_EVENT_TYPES');
|
|
47
|
+
assert.strictEqual(get_audit_write_failures(), 0, 'an audit row failed to write and was lost — the emitter logged the database error');
|
|
32
48
|
};
|
|
33
49
|
/**
|
|
34
50
|
* Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
|
|
35
51
|
* `emit_role_grant_target` call into `calls` as an `AuditLogInput`. Use to
|
|
36
52
|
* capture audit metadata shapes in unit tests (e.g. password change failure
|
|
37
|
-
* outcome, role-grant create denial) without standing up
|
|
53
|
+
* outcome, role-grant create denial) without standing up a database and the
|
|
38
54
|
* `query_audit_log` pipeline.
|
|
39
55
|
*
|
|
40
56
|
* **Capture scope — all four production fan-out shapes.**
|
|
@@ -2,11 +2,24 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
3
|
/**
|
|
4
4
|
* Options for the body-size parity suite — the standard RPC-dispatched
|
|
5
|
-
* cross-suite shape (`setup_test` / `rpc_path`)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* on the shape.
|
|
5
|
+
* cross-suite shape (`setup_test` / `rpc_path`) plus where the backend's
|
|
6
|
+
* socket is. The limit is on every spine, so no case is capability-gated and
|
|
7
|
+
* the flag bundle is not on the shape.
|
|
9
8
|
*/
|
|
10
|
-
export
|
|
9
|
+
export interface BodySizeCrossTestOptions extends RpcPathCrossSuiteOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Base URL the spawned backend is reachable at (e.g. `http://localhost:1178`),
|
|
12
|
+
* or `null` for an in-process run, which has no socket. Given a URL, the
|
|
13
|
+
* over-cap cases write to the socket directly and send only the bytes the
|
|
14
|
+
* refusal needs; with `null` they send whole bodies through the fixture's
|
|
15
|
+
* transport.
|
|
16
|
+
*
|
|
17
|
+
* Pass the URL whenever the fixture's transport reaches a real socket.
|
|
18
|
+
* `null` there uploads each whole over-cap body through `fetch`, where the
|
|
19
|
+
* write races the close that follows the 413 and the case fails some of the
|
|
20
|
+
* time.
|
|
21
|
+
*/
|
|
22
|
+
readonly base_url: string | null;
|
|
23
|
+
}
|
|
11
24
|
export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
|
|
12
25
|
//# sourceMappingURL=body_size.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8F9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA2HD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAyLlF,CAAC"}
|