@fuzdev/fuz_app 0.140.0 → 0.142.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 +72 -22
- package/dist/actions/action_rpc.d.ts +5 -1
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +9 -1
- package/dist/actions/connection_closer.d.ts +44 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +33 -3
- package/dist/actions/perform_action.d.ts +10 -0
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +23 -9
- package/dist/actions/register_action_ws.d.ts +42 -6
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +87 -19
- package/dist/actions/register_ws_endpoint.d.ts +15 -6
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +10 -4
- package/dist/actions/socket.svelte.d.ts +19 -4
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +31 -4
- package/dist/actions/transports.d.ts +30 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +30 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -10
- package/dist/actions/transports_ws_backend.d.ts +51 -5
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +62 -9
- package/dist/actions/ws_endpoint_spec.d.ts +11 -10
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +44 -22
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +12 -1
- package/dist/auth/account_route_schema.d.ts +1 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -1
- package/dist/auth/account_route_schema.js +12 -0
- package/dist/auth/account_routes.d.ts +7 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +240 -187
- package/dist/auth/admin_action_specs.d.ts +6 -1
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +6 -1
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +14 -2
- package/dist/auth/api_token.d.ts +20 -0
- package/dist/auth/api_token.d.ts.map +1 -1
- package/dist/auth/api_token.js +21 -1
- package/dist/auth/api_token_queries.d.ts +22 -4
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +11 -6
- package/dist/auth/audit_log_routes.d.ts +10 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +20 -0
- package/dist/auth/audit_log_schema.d.ts +30 -5
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +30 -0
- package/dist/auth/bearer_auth.d.ts +4 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +19 -8
- package/dist/auth/bootstrap_route_schema.d.ts +1 -0
- package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
- package/dist/auth/bootstrap_route_schema.js +1 -0
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +85 -76
- package/dist/auth/cell_action_specs.d.ts +7 -3
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +9 -5
- package/dist/auth/cell_actions.d.ts +3 -3
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +67 -38
- package/dist/auth/cell_authorize.d.ts +69 -8
- package/dist/auth/cell_authorize.d.ts.map +1 -1
- package/dist/auth/cell_authorize.js +89 -9
- package/dist/auth/cell_field_action_specs.d.ts +5 -4
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_field_action_specs.js +7 -7
- package/dist/auth/cell_field_actions.d.ts +7 -5
- package/dist/auth/cell_field_actions.d.ts.map +1 -1
- package/dist/auth/cell_field_actions.js +24 -22
- package/dist/auth/cell_grant_action_specs.d.ts +6 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_grant_action_specs.js +6 -0
- package/dist/auth/cell_grant_actions.d.ts +3 -1
- package/dist/auth/cell_grant_actions.d.ts.map +1 -1
- package/dist/auth/cell_grant_actions.js +16 -2
- package/dist/auth/cell_item_action_specs.d.ts +5 -4
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_item_action_specs.js +7 -7
- package/dist/auth/cell_item_actions.d.ts +6 -5
- package/dist/auth/cell_item_actions.d.ts.map +1 -1
- package/dist/auth/cell_item_actions.js +23 -22
- package/dist/auth/cell_relation_list_limit.d.ts +20 -0
- package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
- package/dist/auth/cell_relation_list_limit.js +27 -0
- package/dist/auth/cell_relation_visibility.d.ts +4 -3
- package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
- package/dist/auth/cell_relation_visibility.js +6 -5
- package/dist/auth/cell_steward.d.ts +45 -0
- package/dist/auth/cell_steward.d.ts.map +1 -0
- package/dist/auth/cell_steward.js +53 -0
- package/dist/auth/cleanup.d.ts +9 -9
- package/dist/auth/cleanup.js +9 -9
- package/dist/auth/credential_deadline.d.ts +95 -0
- package/dist/auth/credential_deadline.d.ts.map +1 -0
- package/dist/auth/credential_deadline.js +139 -0
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +4 -1
- package/dist/auth/middleware.d.ts +90 -12
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +115 -16
- package/dist/auth/request_context.d.ts +8 -2
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +20 -4
- package/dist/auth/resolved_auth.d.ts +13 -2
- package/dist/auth/resolved_auth.d.ts.map +1 -1
- package/dist/auth/resolved_auth.js +5 -4
- package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +6 -1
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +23 -16
- package/dist/auth/role_grant_offer_queries.d.ts +8 -6
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_queries.js +16 -8
- package/dist/auth/session_cookie.d.ts +4 -0
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +3 -2
- package/dist/auth/session_middleware.d.ts +1 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +4 -1
- package/dist/auth/session_queries.d.ts +19 -2
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +8 -2
- package/dist/auth/signup_route_schema.d.ts.map +1 -1
- package/dist/auth/signup_route_schema.js +1 -0
- package/dist/auth/signup_routes.d.ts +4 -3
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +157 -134
- package/dist/auth/token_scope.d.ts +7 -1
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +8 -2
- package/dist/db/CLAUDE.md +32 -19
- package/dist/db/cell_ddl.d.ts +1 -2
- package/dist/db/cell_ddl.d.ts.map +1 -1
- package/dist/db/cell_ddl.js +1 -2
- package/dist/db/cell_field_queries.d.ts +20 -18
- package/dist/db/cell_field_queries.d.ts.map +1 -1
- package/dist/db/cell_field_queries.js +28 -22
- package/dist/db/cell_item_queries.d.ts +20 -14
- package/dist/db/cell_item_queries.d.ts.map +1 -1
- package/dist/db/cell_item_queries.js +28 -18
- package/dist/db/cell_moderation.d.ts +67 -0
- package/dist/db/cell_moderation.d.ts.map +1 -0
- package/dist/db/cell_moderation.js +66 -0
- package/dist/db/cell_queries.d.ts +44 -14
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +68 -45
- package/dist/db/cell_view_sql.d.ts +102 -0
- package/dist/db/cell_view_sql.d.ts.map +1 -0
- package/dist/db/cell_view_sql.js +112 -0
- package/dist/db/db_pg.d.ts +1 -1
- package/dist/db/db_pg.js +1 -1
- package/dist/hono_context.d.ts +23 -0
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +19 -0
- package/dist/http/CLAUDE.md +41 -15
- package/dist/http/auth_shape.d.ts +5 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +5 -2
- package/dist/http/ip_canonical.d.ts +30 -3
- package/dist/http/ip_canonical.d.ts.map +1 -1
- package/dist/http/ip_canonical.js +50 -3
- package/dist/http/middleware_spec.d.ts +16 -0
- package/dist/http/middleware_spec.d.ts.map +1 -1
- package/dist/http/origin.d.ts +113 -18
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +247 -127
- package/dist/http/route_spec.d.ts +21 -0
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/schema_helpers.d.ts +22 -0
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +24 -0
- package/dist/http/surface.d.ts +6 -1
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +12 -5
- package/dist/primitive_schemas.d.ts +10 -1
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +11 -2
- package/dist/rate_limiter.d.ts +179 -12
- package/dist/rate_limiter.d.ts.map +1 -1
- package/dist/rate_limiter.js +352 -42
- package/dist/realtime/sse_auth_guard.d.ts +22 -6
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +31 -12
- package/dist/realtime/subscriber_registry.d.ts +10 -1
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +10 -1
- package/dist/server/app_server.d.ts +20 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +8 -3
- package/dist/server/env.d.ts +2 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +7 -17
- package/dist/server/serve_fact_route.d.ts +8 -4
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +16 -6
- package/dist/testing/CLAUDE.md +164 -25
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -6
- package/dist/testing/connection_closer_helpers.d.ts +6 -1
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +4 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +37 -2
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +14 -65
- package/dist/testing/cross_backend/capabilities.d.ts +26 -2
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +9 -3
- package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +401 -5
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +145 -2
- package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +6 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +20 -19
- package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
- package/dist/testing/cross_backend/credential_deadline.js +185 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +13 -2
- package/dist/testing/cross_backend/expected_schema.json +0 -1
- package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +7 -1
- package/dist/testing/cross_backend/login_security.d.ts +16 -0
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +154 -0
- package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
- package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
- package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin.js +6 -26
- package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
- package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_conformance.js +136 -0
- package/dist/testing/cross_backend/origin_conformance.json +1679 -0
- package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin_scope.js +140 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/query_shape.js +24 -2
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -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 +44 -33
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_participation.js +64 -0
- 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 +6 -4
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +20 -7
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +25 -21
- package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
- package/dist/testing/cross_backend/transport_composition.js +364 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
- package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_role_gated.js +142 -0
- package/dist/testing/entities.d.ts +23 -0
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +37 -0
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +3 -1
- package/dist/testing/middleware.d.ts +10 -0
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +31 -7
- package/dist/testing/schema_introspect.js +1 -1
- package/dist/testing/schema_parity.d.ts +4 -3
- package/dist/testing/schema_parity.d.ts.map +1 -1
- package/dist/testing/schema_parity.js +4 -3
- package/dist/testing/schema_ready_fixture.d.ts +2 -4
- package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
- package/dist/testing/schema_ready_fixture.js +2 -4
- package/dist/testing/stubs.d.ts +16 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +20 -7
- package/dist/testing/surface_invariants.js +2 -2
- package/dist/ui/auth_state.svelte.d.ts +10 -0
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +22 -4
- package/package.json +1 -1
- package/dist/db/cell_history_ddl.d.ts +0 -38
- package/dist/db/cell_history_ddl.d.ts.map +0 -1
- package/dist/db/cell_history_ddl.js +0 -61
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -33,11 +33,11 @@ time (never runtime), where a throwing guard would break `vite build`.
|
|
|
33
33
|
- `stub_mw` — pass-through middleware (`async (_c, next) => next()`).
|
|
34
34
|
- `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`.
|
|
35
35
|
- `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, `drain_inflight` resolves immediately.
|
|
36
|
-
- `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`.
|
|
37
|
-
- `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 — the same names, paths, order, and declared `errors` (`origin` declares a 403; `bearer_auth` and `daemon_token` declare none, since both soft-fail through `next()`), pinned against the real factory by `auth/middleware.mounted.test.ts`. The surface merges middleware errors into every route under the path, so a stub declaring more would widen the test surface with statuses the live server never returns. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
36
|
+
- `create_stub_audit_sse()` — no-op `AuditLogSse` for surface-test wiring without booting real SSE. `on_audit_event` no-op (nothing is broadcast); `required_role` is `admin`; `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`.
|
|
37
|
+
- `create_stub_api_middleware({include_daemon_token?, origin_carve_outs?})` — 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 — the same names, paths, order, and declared `errors` (`origin` declares a 403; `bearer_auth` and `daemon_token` declare none, since both soft-fail through `next()`), pinned against the real factory by `auth/middleware.mounted.test.ts`. The surface merges middleware errors into every route under the path, so a stub declaring more would widen the test surface with statuses the live server never returns. Each spec gets its own pass-through handler (not the shared `stub_mw`), so `assert_middleware_stack_mounted` tells the layers apart. See `auth/CLAUDE.md` §Middleware for the real stack.
|
|
38
38
|
- `create_stub_app_backend(options?)` — stub `AppBackend` with no database: `create_stub_app_deps()` (or `options.deps`), no migration results, a `close` that does nothing (or `options.close`). For assembling a server in tests that never reach the database.
|
|
39
39
|
- `create_stub_app_server_context(session_options)` — stub `AppServerContext` over `create_stub_app_backend()`; rate limiters null, `bootstrap_status.available: false`.
|
|
40
|
-
- `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly, in its order (consumer routes, then bootstrap routes, RPC endpoint routes, and `GET /api/surface`, + stub middleware + surface generation). `GET /api/surface` is listed only with `surface_route: true`, as `create_app_server` mounts it only when opted in — pass the same value to both so the surface-derived suites probe it exactly when the live server serves it. `daemon_token: true` lists the `daemon_token` layer `create_app_server` mounts when given a `daemon_token_state` (`create_test_app` always passes one, and `default_in_process_suite_options` sets it, along with `app_options.surface_route`); `testing/test_app_server.db.test.ts` pins the stub's routes (order, auth, error schemas, applicable middleware) and middleware names against `create_test_app`'s for every `surface_route` setting. Its middleware list mirrors the real one — a stub `trusted_proxy` spec at `'*'`, then the stub auth stack — so every route's `applicable_middleware` starts with `trusted_proxy` (a route outside `/api/*`
|
|
40
|
+
- `create_test_app_surface_spec(options)` — builds an `AppSurfaceSpec` mirroring `create_app_server`'s route assembly, in its order (consumer routes, then bootstrap routes, RPC endpoint routes, and `GET /api/surface`, + stub middleware + surface generation). `GET /api/surface` is listed only with `surface_route: true`, as `create_app_server` mounts it only when opted in — pass the same value to both so the surface-derived suites probe it exactly when the live server serves it. `daemon_token: true` lists the `daemon_token` layer `create_app_server` mounts when given a `daemon_token_state` (`create_test_app` always passes one, and `default_in_process_suite_options` sets it, along with `app_options.surface_route`); `testing/test_app_server.db.test.ts` pins the stub's routes (order, auth, error schemas, applicable middleware) and middleware names against `create_test_app`'s for every `surface_route` setting. Its middleware list mirrors the real one — a stub `trusted_proxy` spec at `'*'`, then the stub auth stack — so every route's `applicable_middleware` starts with `trusted_proxy` (a route outside `/api/*` lists `['trusted_proxy', 'origin']`, since the origin check covers every path, and a route under an origin carve-out drops `origin`), as on the live server. `CreateTestAppSurfaceSpecOptions`: `session_options`, `create_route_specs`, `env_schema?`, `event_specs?`, `rpc_endpoints?`, `ws_endpoints?`, `allowed_origins?`, `origin_carve_outs?`, `transform_middleware?`, `bootstrap?`, `surface_route?`, `daemon_token?`. Throws, like `create_app_server`, on an endpoint path outside `AUTH_MIDDLEWARE_PATH` (`assert_endpoint_in_auth_scope`), on a route that reads or mints a credential outside it (`assert_credential_routes_in_auth_scope`), on an origin carve-out that overlaps that scope (`assert_origin_carve_outs_valid`), and on a `transform_middleware` that moves, drops, duplicates, reorders, wraps, or replaces the stub proxy spec or a stub auth spec (`assert_middleware_stack_mounted`); the transform receives shallow copies in a fresh array, so an in-place mutation can't slip past the check. Throws on a `ws_endpoints` spec with no `allowed_origins` of its own when no `allowed_origins` option is given (there is no server list to default from). 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.
|
|
41
41
|
|
|
42
42
|
Throwing stubs surface mock escape: a test that accidentally reaches into
|
|
43
43
|
stub territory breaks immediately with a label-scoped error rather than
|
|
@@ -51,12 +51,15 @@ set only the fields the test cares about. `create_test_*` prefix avoids
|
|
|
51
51
|
collisions with real `account_queries.ts` factories. Override types widen
|
|
52
52
|
branded `Uuid` fields to `string` so tests pass literal ids without per-site
|
|
53
53
|
casts — the factory brands internally. Exported as `TestAccountOverrides` /
|
|
54
|
-
`TestActorOverrides` / `TestRoleGrantOverrides` / `TestAuditEventOverrides
|
|
54
|
+
`TestActorOverrides` / `TestRoleGrantOverrides` / `TestAuditEventOverrides` /
|
|
55
|
+
`TestValidatedApiTokenOverrides`.
|
|
55
56
|
|
|
56
57
|
- `create_test_account(overrides?)` — `{id: 'acct-test', username: 'test_user', …}`
|
|
57
58
|
- `create_test_actor(overrides?)` — `{id: 'actor-test', account_id: 'acct-test', …}`
|
|
58
59
|
- `create_test_role_grant(overrides?)` — `{id: 'role-grant-test', actor_id: 'actor-test', role: 'admin', scope_id: null, …}`
|
|
59
60
|
- `create_test_context(role_grants?)` — `{account, actor, role_grants}`; pass `[{role: 'keeper'}, {role: 'admin'}]` for multi-role.
|
|
61
|
+
- `create_test_raw_api_token(label?)` — a well-formed raw bearer value (`secret_fuz_token_` + `label` padded with `A` to 43 chars) that passes `is_api_token_shaped`, so a mocked-lookup test reaches token validation; distinct labels give distinct tokens.
|
|
62
|
+
- `create_test_validated_api_token(overrides?)` — a `ValidatedApiToken` (what `query_validate_api_token` returns) with a `full` scope; override `scope` to test a narrowed one.
|
|
60
63
|
- `create_test_audit_event(overrides?)` — `{id: 'evt-test', event_type: 'login', outcome: 'success', …}`, for SSE guard / audit tests.
|
|
61
64
|
|
|
62
65
|
### `mock_fs.ts` — in-memory filesystem
|
|
@@ -106,9 +109,9 @@ mismatch.
|
|
|
106
109
|
|
|
107
110
|
### `connection_closer_helpers.ts` — `ConnectionCloser` test doubles
|
|
108
111
|
|
|
109
|
-
- `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing (`
|
|
112
|
+
- `create_recording_closer()` — `{closer, calls}`; every method on `closer` records `{method, id}` into `calls` and closes nothing (`close_sockets_for_role` records `{method: 'role', id, role}`, `close_all_sockets` `{method: 'all', id: null}`). 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).
|
|
110
113
|
- `assert_close_call(call, method, id)` — pins `{method, id}` on a recorded close call; handles the missing-element case.
|
|
111
|
-
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id: string} | {method: 'all', id: null}`.
|
|
114
|
+
- `RecordedClose` — `{method: 'session' | 'token' | 'account', id: string} | {method: 'role', id: string, role: string} | {method: 'all', id: null}`.
|
|
112
115
|
- `RecordingCloser` — `{closer, calls}`.
|
|
113
116
|
|
|
114
117
|
## Database — `db.ts`
|
|
@@ -483,7 +486,7 @@ file that imports from `middleware.ts` gets these mocks globally. Pair with
|
|
|
483
486
|
- `BearerAuthTestOptions`, `BearerAuthTestCase` — test-case table shape for the bearer auth runner.
|
|
484
487
|
- `create_bearer_auth_mocks(tc)` — configures the module-level mocks per test case; returns spy references.
|
|
485
488
|
- `TEST_CLIENT_IP = '127.0.0.1'` — IP set by the proxy stub in `create_bearer_auth_test_app`.
|
|
486
|
-
- `create_bearer_auth_test_app(tc)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, request_context_set}` — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
|
|
489
|
+
- `create_bearer_auth_test_app(tc)` — Hono app with bearer middleware + echo route at `/api/test` returning `{ok, account_id, credential_type, api_token_id, token_scope, credential_deadline_in_ms, request_context_set}` (`credential_deadline_in_ms` — how far ahead `CREDENTIAL_DEADLINE_KEY` lies, `null` for none; `BearerAuthTestCase.expected_credential_deadline_ms` asserts it) — the account-grain identity bearer auth writes, plus a flag for tests that pre-populate `REQUEST_CONTEXT_KEY` via `pre_context`.
|
|
487
490
|
- `describe_bearer_auth_cases(suite_name, cases)` — table-driven runner; one `test()` per case; asserts status, error, body fields, `api_token_id`, context preservation.
|
|
488
491
|
- `TEST_MIDDLEWARE_PATH = '/api/test'` — path used by the echo route in the stack factory.
|
|
489
492
|
- `create_test_middleware_stack_app(options?)` — real proxy + origin + bearer middleware for integration-shape testing. Echo route returns `{ok, client_ip, has_context}`.
|
|
@@ -532,7 +535,7 @@ method and assert the stream closes within 2s.
|
|
|
532
535
|
|
|
533
536
|
`SseRouteTestSpec` per route: `{path, trigger, event_specs?, assert_closes_on_revoke?}`.
|
|
534
537
|
A consumer registry closes on the revoke when its route joined it to the
|
|
535
|
-
backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry))`)
|
|
538
|
+
backend's closer (`ctx.deps.connection_closer.add(create_sse_connection_closer(registry, role))`)
|
|
536
539
|
or when the suite's `on_audit_event` option wires a guard (e.g. `create_sse_auth_guard`); with
|
|
537
540
|
neither, the revoke assertion fails.
|
|
538
541
|
|
|
@@ -929,9 +932,10 @@ source of truth for wire-shape conformance.
|
|
|
929
932
|
(the **gating** flags `ws` / `sse` / `cell_crud` / `cell_relations` /
|
|
930
933
|
`cell_gated_create` / `account_lifecycle` / `fact_serving` / `ready` /
|
|
931
934
|
`account_status` / `peer_request` /
|
|
932
|
-
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply`
|
|
933
|
-
each has a `test_if`
|
|
934
|
-
reader (`ws_self_revocation_reply` forks one case's assertion instead
|
|
935
|
+
`ws_handshake_pipelining` / `ws_account_actions` / `ws_self_revocation_reply` /
|
|
936
|
+
`ws_role_gated` / `ws_refusal_body` — each has a `test_if`
|
|
937
|
+
reader (`ws_self_revocation_reply` forks one case's assertion instead, and
|
|
938
|
+
`ws_refusal_body` how the front-door table reads a WS refusal); `peer_request` (server-initiated requests — the ActionPeer
|
|
935
939
|
`peer/ping` round-trip) is `true` for both the Rust spine and the TS spine
|
|
936
940
|
(`BackendWebsocketTransport.request_connection`); `ts_default_capabilities`
|
|
937
941
|
keeps it `false` like sse/ready until a backend wires the `peer/ping` HTTP +
|
|
@@ -1034,7 +1038,7 @@ error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case i
|
|
|
1034
1038
|
resolves its `input`/`output` from the live registry (RPC) or `RouteSpec`
|
|
1035
1039
|
(the 6 REST auth routes), so the case never carries a schema. `as` is the
|
|
1036
1040
|
closed `ConformancePrincipal` enum (`keeper` / `daemon` / `invalid_daemon` /
|
|
1037
|
-
`daemon_browser` / `token` / `bearer_browser` / `anonymous` /
|
|
1041
|
+
`daemon_browser` / `token` / `bearer_browser` / `malformed_bearer` / `scoped_token` / `anonymous` /
|
|
1038
1042
|
`fresh_non_admin` / `role_holder` / `wrong_role` / `expired_session`) —
|
|
1039
1043
|
fixture accessors, never inline credential minting.
|
|
1040
1044
|
`invalid_daemon` sends a malformed `X-Daemon-Token` alongside the keeper
|
|
@@ -1054,8 +1058,11 @@ error_reason?, fields?, headers?, equivalence_group?}, note?, xfail?}`. A case i
|
|
|
1054
1058
|
`bearer_browser` is the bearer twin: a _valid_ bearer api-token in a browser
|
|
1055
1059
|
context (default `Origin` present), fresh jar (no session) — the bearer
|
|
1056
1060
|
middleware discards it, so an authed action 401s, wire-indistinguishable from
|
|
1057
|
-
`anonymous` (pinned by the `
|
|
1061
|
+
`anonymous` (pinned by the `bearer_soft_fail` equivalence group). The
|
|
1058
1062
|
`token` principal is the honored counterpart (suppresses `Origin`).
|
|
1063
|
+
`malformed_bearer` is the keeper's token minus its last character over a
|
|
1064
|
+
no-Origin, empty-jar transport — refused by the shape precheck before any
|
|
1065
|
+
database work, so it joins `bearer_soft_fail` too.
|
|
1059
1066
|
`expired_session` is the keeper behind an expired server-side session
|
|
1060
1067
|
(`fixture.mint_expired_session()`: a backdated `auth_session` row behind a
|
|
1061
1068
|
still-valid signed cookie, so the DB-row expiry gate is what refuses it).
|
|
@@ -1157,7 +1164,7 @@ the seven server-initiated notifications (`received` → recipient,
|
|
|
1157
1164
|
`role_grant_revoke` → revokee, and `role_grant_offer_supersede` → each
|
|
1158
1165
|
superseded sibling's grantor on **both** the accept- and revoke-cascade paths).
|
|
1159
1166
|
`describe_role_grant_offer_notification_ws_tests({setup_test, capabilities,
|
|
1160
|
-
base_url, ws_path})` opens the affected counterparty's socket
|
|
1167
|
+
base_url, ws_path, ws_required_roles?})` opens the affected counterparty's socket
|
|
1161
1168
|
(`create_admitted_ws_transport` — a targeted notification reaches only an
|
|
1162
1169
|
admitted connection, and the handshake alone doesn't say it is one), drives the
|
|
1163
1170
|
lifecycle RPC over `fixture.transport`, then
|
|
@@ -1177,8 +1184,36 @@ Sends queue on the post-commit drain, so `WsClient.wait_for` polls + waits
|
|
|
1177
1184
|
(method + predicate filter ignores unrelated frames). Accounts are seeded with
|
|
1178
1185
|
`ROLE_ADMIN` (the only admin-grantable role) so they can open an admin-gated WS
|
|
1179
1186
|
where one exists (forge) and be offered the role; harmless on the auth-only
|
|
1180
|
-
spine.
|
|
1181
|
-
`
|
|
1187
|
+
spine. A backend whose `ws_path` is gated on `ROLE_ADMIN` declares it in
|
|
1188
|
+
`ws_required_roles`: the revoke case then revokes the revokee's only admin
|
|
1189
|
+
grant and expects its socket to close with `WS_CLOSE_PERMISSION_REVOKED` (the
|
|
1190
|
+
notification may or may not land first, and is held to the flat shape when it
|
|
1191
|
+
does), where an ungated socket expects the frame. The offer/accept fixtures are `create_pending_role_grant_offer` /
|
|
1192
|
+
`create_active_role_grant` (`cross_backend/role_grant_cross_helpers.ts`).
|
|
1193
|
+
Gated on `capabilities.ws`; cross-process only. fuz_app's own wiring is
|
|
1194
|
+
`src/test/cross_backend/role_grant_offer_notification_ws.cross.test.ts`, and
|
|
1195
|
+
`role_grant_offer_notification_ws_gated.cross.test.ts` runs it again over the
|
|
1196
|
+
spine binaries' role-gated endpoint with `ws_required_roles` declared.
|
|
1197
|
+
|
|
1198
|
+
### `cross_backend/ws_role_gated.ts` — `describe_ws_role_gated_cross_tests`
|
|
1199
|
+
|
|
1200
|
+
Real-upgrade parity for a **role-gated WebSocket losing its role**: a revoked
|
|
1201
|
+
global grant of a role an endpoint's `required_roles` lists closes the account's
|
|
1202
|
+
sockets on that endpoint with `WS_CLOSE_PERMISSION_REVOKED` (and its reason),
|
|
1203
|
+
while the account's ungated socket on the same transport stays open,
|
|
1204
|
+
dispatching, and receiving the account's pushes, and a re-upgrade is refused at
|
|
1205
|
+
the gate (403); a scoped revoke of a listed role closes nothing — the case
|
|
1206
|
+
first opens the gated socket on a global grant of `scoped_role` alone, which
|
|
1207
|
+
proves the endpoint lists it. That a closed registration is sent nothing more
|
|
1208
|
+
is beyond what a client sees once it has read the close frame; each spine's
|
|
1209
|
+
transport tests pin it.
|
|
1210
|
+
`describe_ws_role_gated_cross_tests({setup_test, capabilities, base_url,
|
|
1211
|
+
ws_path, gated_ws_path, gated_roles, scoped_role, rpc_path?})` — `gated_roles`
|
|
1212
|
+
must hold `ROLE_ADMIN` and `scoped_role` (a role an admin can grant scoped).
|
|
1213
|
+
fuz_app's spine binaries mount `SPINE_ROLE_GATED_WS_PATH` gated on
|
|
1214
|
+
`SPINE_ROLE_GATED_WS_ROLES` (`admin` + `participant`). Gated on
|
|
1215
|
+
`capabilities.ws` and `capabilities.ws_role_gated`; cross-process only.
|
|
1216
|
+
fuz_app's own wiring is `src/test/cross_backend/ws_role_gated.cross.test.ts`.
|
|
1182
1217
|
|
|
1183
1218
|
### `cross_backend/peer_ping_ws.ts` — `describe_peer_ping_ws_tests`
|
|
1184
1219
|
|
|
@@ -1386,7 +1421,14 @@ moderation_required}`, with `moderation` set per the verdict (`pending`+private
|
|
|
1386
1421
|
**visible** one **403**s; plain parentless creates stay open. The moderate
|
|
1387
1422
|
suite then proves the `pending → approved | rejected` transition: an admin
|
|
1388
1423
|
(root manager) approves → public, the **author** self-approve → 403
|
|
1389
|
-
(anti-self-approval), a non-viewer → 404, reject → stays private.
|
|
1424
|
+
(anti-self-approval), a non-viewer → 404, reject → stays private. It also
|
|
1425
|
+
pins the moderation read gate (a pending or rejected post reaches no
|
|
1426
|
+
non-steward reader past its author and admins on `cell_get`, `cell_list`,
|
|
1427
|
+
`cell_item_list`, and fact serving, even flipped public or granted while
|
|
1428
|
+
approved), `cell_grant_create`'s
|
|
1429
|
+
403 `cell_grant_unapproved`, and the steward arm (a non-admin root owner — a
|
|
1430
|
+
non-`space` root the policy leaves open — reads the queue, `cell_get`, and
|
|
1431
|
+
the fact, and moderates, but still 404s on clone and create-under). The
|
|
1390
1432
|
authorizer _behavior_ adds no method/wire shape, so schema + manifest parity
|
|
1391
1433
|
are **blind** to a divergence — these are the only gates that catch one (the
|
|
1392
1434
|
`cell_moderate` verb itself is manifest-covered). In-process hook coverage is
|
|
@@ -1565,6 +1607,27 @@ are `src/test/auth/cell_crud_parity.db.test.ts`
|
|
|
1565
1607
|
payload-expiry tests never reached. Daemon-token-gated like its siblings; the
|
|
1566
1608
|
Rust mirror is `fuz_testing::create_testing_mint_session_action_spec`.
|
|
1567
1609
|
|
|
1610
|
+
Also in the module and **not** bundled: `_testing_origin_check`
|
|
1611
|
+
(`create_testing_origin_check_action()`, `run_testing_origin_check(input)`,
|
|
1612
|
+
`TestingOriginCheckInput` / `TestingOriginCheckOutput`) — answers the origin
|
|
1613
|
+
allowlist's parse and request check for one `{patterns, origin}` pair:
|
|
1614
|
+
`{ok: true, allowed}` or `{ok: false, errors: [{index?, kind}]}`. Pure, no
|
|
1615
|
+
database. Kept out of the bundle so a consumer whose Rust binary doesn't
|
|
1616
|
+
mount the mirror (`fuz_testing::create_testing_origin_check_action_spec`)
|
|
1617
|
+
keeps an exact action-manifest gate; the spine binary appends it in
|
|
1618
|
+
`build_full_spine_rpc_actions`.
|
|
1619
|
+
|
|
1620
|
+
Also **not** bundled: `_testing_shorten_credential_expiry`
|
|
1621
|
+
(`create_testing_shorten_credential_expiry_action()`) — pulls an account's
|
|
1622
|
+
`auth_session` and `api_token` expiries in to `seconds` (1–60) from now,
|
|
1623
|
+
shorten-only (`LEAST`, an eternal token gaining one), answering
|
|
1624
|
+
`{sessions, api_tokens}` counts. Applied before a connection opens, since
|
|
1625
|
+
the deadline is read when the credential resolves. Backs
|
|
1626
|
+
`describe_credential_deadline_cross_tests`; appended in
|
|
1627
|
+
`build_full_spine_rpc_actions` for the same manifest reason as
|
|
1628
|
+
`_testing_origin_check`. The Rust mirror is
|
|
1629
|
+
`fuz_testing::create_testing_shorten_credential_expiry_action_spec`.
|
|
1630
|
+
|
|
1568
1631
|
Also in the module — but **not** bundled by `create_testing_actions` —
|
|
1569
1632
|
`_testing_action_manifest` (+ `create_testing_action_manifest_action(mounted)`):
|
|
1570
1633
|
dumps the live RPC registry as a normalized `ActionManifest` for the
|
|
@@ -1611,19 +1674,95 @@ serve a body-parity gate (its per-spine coverage stays in the two surface
|
|
|
1611
1674
|
censuses). Cross-process only; fuz_app's own wiring is
|
|
1612
1675
|
`src/test/cross_backend/token_scope_surface.cross.test.ts`.
|
|
1613
1676
|
|
|
1677
|
+
### Front-door contract — `cross_backend/transport_composition.ts`
|
|
1678
|
+
|
|
1679
|
+
`describe_transport_composition_cross_tests({setup_test, capabilities, base_url,
|
|
1680
|
+
rpc_path?, origin?, rest_path?, password_path?, ws_path?, sse_path?})` — the per-transport
|
|
1681
|
+
composition table: what each transport answers before per-action dispatch,
|
|
1682
|
+
when one request trips several refusals. Rows are REST `POST` (session-only
|
|
1683
|
+
`/api/account/logout`), RPC `POST`, RPC `GET`, the WS upgrade (gated on
|
|
1684
|
+
`capabilities.ws`) and the audit stream (`capabilities.sse`); columns are a
|
|
1685
|
+
foreign `Origin` with the keeper's session (→ 403 `forbidden_origin`), an
|
|
1686
|
+
over-cap declared length with a foreign `Origin` and no credential (→ 413
|
|
1687
|
+
`payload_too_large`, body-carrying rows only), no credential (→ 401
|
|
1688
|
+
`authentication_required`), and a narrowed bearer with no `Origin` (→ 403
|
|
1689
|
+
`token_scope_required` naming `rpc:<method>` on RPC and `surface:ws_upgrade` on
|
|
1690
|
+
the upgrade; 403 `credential_type_required` on the session-only REST route and
|
|
1691
|
+
audit stream); a `/password` cell pins that route's 401 beside `/logout`'s.
|
|
1692
|
+
Each case asserts the status and the body — flat `{error}` or the JSON-RPC
|
|
1693
|
+
envelope's `error.data`. A WS refusal's body is required where the backend
|
|
1694
|
+
declares `capabilities.ws_refusal_body`; where it doesn't (Node's
|
|
1695
|
+
`@hono/node-ws` drops the body), an empty body passes on its status and a sent
|
|
1696
|
+
one is still asserted. The table
|
|
1697
|
+
is the exported `transport_composition_cases` data. Given `base_url`, every
|
|
1698
|
+
probe is written to the socket (`raw_http_exchange`) and an over-cap request
|
|
1699
|
+
sends its head alone; with `base_url: null` the probes go through the
|
|
1700
|
+
fixture's transport and the WS rows skip. Not in the standard bundle. fuz_app's
|
|
1701
|
+
own wiring is `src/test/cross_backend/transport_composition.cross.test.ts` and
|
|
1702
|
+
the in-process `src/test/auth/transport_composition_parity.db.test.ts`.
|
|
1703
|
+
|
|
1704
|
+
`cross_backend/narrowed_api_token.ts` — `mint_narrowed_api_token({app,
|
|
1705
|
+
rpc_path, session_headers, methods, name?})` mints a method-scoped api token
|
|
1706
|
+
through the production `account_token_create` path and returns the raw bearer
|
|
1707
|
+
(throws when the mint is refused). Shared by the front-door table, the
|
|
1708
|
+
token-scope surface suite, and the conformance table's `scoped_token`
|
|
1709
|
+
principal.
|
|
1710
|
+
|
|
1711
|
+
### Credential deadline parity — `cross_backend/credential_deadline.ts`
|
|
1712
|
+
|
|
1713
|
+
`describe_credential_deadline_cross_tests({setup_test, capabilities, base_url,
|
|
1714
|
+
ws_path, sse_path?, rpc_path?})` — a connection closes when the credential it
|
|
1715
|
+
opened on expires: each case pulls a fresh account's credential expiries in to
|
|
1716
|
+
two seconds with `_testing_shorten_credential_expiry` before the open, then
|
|
1717
|
+
checks the connection still dispatches short of the deadline and closes at it.
|
|
1718
|
+
A WebSocket on a session and one on an API token (`capabilities.ws`) close with
|
|
1719
|
+
`WS_CLOSE_SESSION_REVOKED` and `WS_CLOSE_CREDENTIAL_EXPIRED_REASON`; an audit
|
|
1720
|
+
stream on a session (`capabilities.sse`) ends; and a second seam call asking
|
|
1721
|
+
for a later expiry moves nothing (the seam only shortens). API token × audit
|
|
1722
|
+
stream is not reachable over the wire — the stock stream is session-only — so
|
|
1723
|
+
the Rust spine pins it on a widened gate and the TS twin shares one deadline
|
|
1724
|
+
wait across both transports. Cross-process only; fuz_app's own wiring is
|
|
1725
|
+
`src/test/cross_backend/credential_deadline.cross.test.ts`.
|
|
1726
|
+
|
|
1727
|
+
### Origin check scope parity — `cross_backend/origin_scope.ts`
|
|
1728
|
+
|
|
1729
|
+
`describe_origin_scope_cross_tests({setup_test, capabilities, probe_path?})` —
|
|
1730
|
+
the origin check covers paths outside `/api`: on `GET /ready` (`probe_path`), a
|
|
1731
|
+
foreign `Origin` → 403 `forbidden_origin`, an absent or allowed one → 200. Gated
|
|
1732
|
+
on `capabilities.ready`. Runs both legs (the in-process
|
|
1733
|
+
`cross_backend/origin_scope_parity.db.test.ts` + the cross-process
|
|
1734
|
+
`origin_scope.cross.test.ts`).
|
|
1735
|
+
|
|
1614
1736
|
### Origin verification parity — `cross_backend/origin.ts`
|
|
1615
1737
|
|
|
1616
1738
|
`describe_origin_cross_tests({setup_test, rpc_path?})` — the
|
|
1617
|
-
imperative Origin-verification suite:
|
|
1618
|
-
|
|
1619
|
-
Imperative (not a conformance-table row) because
|
|
1620
|
-
|
|
1621
|
-
and absent-Origin needs `fresh_transport({origin: null})`. Runs both legs (the
|
|
1739
|
+
imperative Origin-verification suite: absent `Origin` → request passes
|
|
1740
|
+
(non-browser direct access); the disallowed-`Origin` 403 is the front-door
|
|
1741
|
+
table's. Imperative (not a conformance-table row) because absent-Origin needs
|
|
1742
|
+
`fresh_transport({origin: null})`. Runs both legs (the
|
|
1622
1743
|
in-process `auth/origin_parity.db.test.ts` + the cross-process
|
|
1623
1744
|
`origin.cross.test.ts`). The promotion surfaced a twin-impl divergence — the
|
|
1624
1745
|
Rust spine returned a plain-text body — now converged to the canonical TS
|
|
1625
1746
|
`{error: "forbidden_origin"}` via `fuz_http::forbidden_origin_response()`.
|
|
1626
1747
|
|
|
1748
|
+
### Origin allowlist conformance — `cross_backend/origin_conformance.ts`
|
|
1749
|
+
|
|
1750
|
+
`cross_backend/origin_conformance.json` is the shared table both spines' origin
|
|
1751
|
+
allowlist must satisfy: **parse** rows `{raw, ok: true}` or `{raw, errors:
|
|
1752
|
+
[{index?, kind}]}` (every refusal, 1-based position, `AllowedOriginsErrorKind`)
|
|
1753
|
+
and **match** rows `{patterns, origin, allowed}` (`origin: null` — no
|
|
1754
|
+
`Origin` — passes). A row may carry a `note`. `load_origin_conformance()` reads
|
|
1755
|
+
and validates it (`OriginConformance`), `to_origin_conformance_checks(table)`
|
|
1756
|
+
flattens it into `_testing_origin_check` inputs with their expected answers, and
|
|
1757
|
+
`assert_origin_conformance(checks, answers, backend)` fails once listing every
|
|
1758
|
+
row that disagrees. `describe_origin_conformance_cross_tests({setup_test,
|
|
1759
|
+
rpc_path?})` sends every row to a backend's `_testing_origin_check` over the
|
|
1760
|
+
keeper's daemon-token channel; the TS unit leg runs the same rows through
|
|
1761
|
+
`run_testing_origin_check` (`src/test/http/origin.conformance.test.ts`), so the
|
|
1762
|
+
Rust spine is held to the table without reading the file. Ungated; edit the
|
|
1763
|
+
table by hand — it is the statement of the grammar, not generated output.
|
|
1764
|
+
fuz_app's own wiring is `src/test/cross_backend/origin_conformance.cross.test.ts`.
|
|
1765
|
+
|
|
1627
1766
|
### CORS simple-request parity — `cross_backend/simple_request.ts`
|
|
1628
1767
|
|
|
1629
1768
|
`describe_simple_request_cross_tests({setup_test, rpc_path?, login_path?, logout_path?})`
|
|
@@ -1714,9 +1853,9 @@ so one fixture is the cross-impl contract): the TS spine via
|
|
|
1714
1853
|
`import.meta.url` constant off `spine_surface_constants.ts`; the Rust
|
|
1715
1854
|
`testing_spine_stub` via the absolute path `rust_spine_stub_backend_config`
|
|
1716
1855
|
passes through `FUZ_RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH`. The fixture covers the full
|
|
1717
|
-
spine bootstrap (auth + cell +
|
|
1856
|
+
spine bootstrap (auth + cell + fact) and is regenerated +
|
|
1718
1857
|
drift-guarded by `src/test/cross_backend/spine_expected_schema.db.test.ts`
|
|
1719
|
-
(`UPDATE_SCHEMA_READY=1
|
|
1858
|
+
(`UPDATE_SCHEMA_READY=1`).
|
|
1720
1859
|
|
|
1721
1860
|
### Testing-backdoor credential gate — `cross_backend/testing_backdoor.ts`
|
|
1722
1861
|
|
|
@@ -1935,7 +2074,7 @@ re-roll the serve / daemon-info / WS-attach / drain boilerplate:
|
|
|
1935
2074
|
- `testing/cross_backend/full_spine_mount.ts` — `build_full_spine_rpc_actions(deps, 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). `build_full_spine_mount(deps, options)` builds it once for both of the binary's endpoints, returning `FullSpineMount` (`{rpc_actions, ws_actions}`): the WS list is `protocol_actions` plus every RPC action not already among them, twinning the Rust `testing_spine_stub`, which serves one registry on RPC and WS (the one difference is `cancel` — a registered protocol action on TS, owned by the read loop on Rust). Single-sources the mount so the binary and the `spine_method_coverage` reconciliation test build the same list.
|
|
1936
2075
|
- `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 pglet, no external infra), the TS analog of `rust_spine_stub_backend_config()`.
|
|
1937
2076
|
|
|
1938
|
-
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` (`rate_limiters: 'disabled_for_testing'` plus whichever login / action limiters its env toggles built, `build_full_spine_mount`'s two lists on one `rpc_endpoints` entry and
|
|
2077
|
+
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` (`rate_limiters: 'disabled_for_testing'` plus whichever login / action limiters its env toggles built, `build_full_spine_mount`'s two lists on one `rpc_endpoints` entry and two `ws_endpoints` entries — `/api/ws` and the role-gated `SPINE_ROLE_GATED_WS_PATH` — sharing one `transport`, which is also the role-grant-offer `notification_sender`), reusing `default_spine_surface`. `src/test/cross_backend/testing_spine_server.db.test.ts` builds it with the Node adapter in-process and asserts its surface lists both WS endpoints (the gated one with its `required_roles`) and passes the surface invariants. 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.
|
|
1939
2078
|
|
|
1940
2079
|
### Live-method coverage reconciliation — `method_coverage.ts`
|
|
1941
2080
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAYzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAiE7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
|
|
@@ -10,6 +10,13 @@ import './assert_dev_env.js';
|
|
|
10
10
|
import { test, assert, describe } from 'vitest';
|
|
11
11
|
import { ApiError, ERROR_FORBIDDEN_ORIGIN } from '../http/error_schemas.js';
|
|
12
12
|
import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH } from './middleware.js';
|
|
13
|
+
import { create_test_raw_api_token } from './entities.js';
|
|
14
|
+
/**
|
|
15
|
+
* A well-formed bearer value — it passes the `is_api_token_shaped` precheck,
|
|
16
|
+
* so a `not_called` case proves an earlier layer short-circuited, not that the
|
|
17
|
+
* token's shape was refused.
|
|
18
|
+
*/
|
|
19
|
+
const ADVERSARIAL_BEARER_TOKEN = create_test_raw_api_token('adversarial');
|
|
13
20
|
// --- Standard adversarial header cases ---
|
|
14
21
|
/**
|
|
15
22
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
@@ -26,7 +33,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
26
33
|
{
|
|
27
34
|
name: 'bearer token with Origin header is rejected before token validation',
|
|
28
35
|
headers: {
|
|
29
|
-
Authorization:
|
|
36
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
30
37
|
Origin: 'https://attacker.com'
|
|
31
38
|
},
|
|
32
39
|
expected_status: 403,
|
|
@@ -36,7 +43,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
36
43
|
{
|
|
37
44
|
name: 'bearer token with allowed Origin — bearer silently discarded (browser context)',
|
|
38
45
|
headers: {
|
|
39
|
-
Authorization:
|
|
46
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
40
47
|
Origin: allowed_origin
|
|
41
48
|
},
|
|
42
49
|
expected_status: 200,
|
|
@@ -51,7 +58,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
51
58
|
{
|
|
52
59
|
name: 'empty Origin header is rejected by origin middleware before bearer auth (defense-in-depth)',
|
|
53
60
|
headers: {
|
|
54
|
-
Authorization:
|
|
61
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
55
62
|
Origin: ''
|
|
56
63
|
},
|
|
57
64
|
expected_status: 403,
|
|
@@ -61,7 +68,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
61
68
|
{
|
|
62
69
|
name: 'lowercase bearer scheme is recognized (case-insensitive per RFC 7235), soft-fails',
|
|
63
70
|
headers: {
|
|
64
|
-
Authorization:
|
|
71
|
+
Authorization: `bearer ${ADVERSARIAL_BEARER_TOKEN}`
|
|
65
72
|
},
|
|
66
73
|
expected_status: 200,
|
|
67
74
|
validate_expectation: 'called'
|
|
@@ -72,7 +79,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
72
79
|
// treats Referer presence as a browser-context indicator and discards
|
|
73
80
|
// the token, so the request reaches the route as unauthenticated.
|
|
74
81
|
headers: {
|
|
75
|
-
Authorization:
|
|
82
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
76
83
|
Referer: 'https://attacker.com/page'
|
|
77
84
|
},
|
|
78
85
|
expected_status: 200,
|
|
@@ -81,7 +88,7 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
81
88
|
{
|
|
82
89
|
name: 'bearer token with allowed Referer (no Origin) — bearer silently discarded (browser context)',
|
|
83
90
|
headers: {
|
|
84
|
-
Authorization:
|
|
91
|
+
Authorization: `Bearer ${ADVERSARIAL_BEARER_TOKEN}`,
|
|
85
92
|
Referer: `${allowed_origin}/page`
|
|
86
93
|
},
|
|
87
94
|
expected_status: 200,
|
|
@@ -2,11 +2,16 @@ import './assert_dev_env.js';
|
|
|
2
2
|
import type { ConnectionCloser } from '../actions/connection_closer.js';
|
|
3
3
|
/**
|
|
4
4
|
* Record of a single `ConnectionCloser` method invocation — a credential
|
|
5
|
-
* close carries the id it was given,
|
|
5
|
+
* close carries the id it was given, a role close (`'role'`) the account id
|
|
6
|
+
* and the role, `close_all_sockets` (`'all'`) none.
|
|
6
7
|
*/
|
|
7
8
|
export type RecordedClose = {
|
|
8
9
|
method: 'session' | 'token' | 'account';
|
|
9
10
|
id: string;
|
|
11
|
+
} | {
|
|
12
|
+
method: 'role';
|
|
13
|
+
id: string;
|
|
14
|
+
role: string;
|
|
10
15
|
} | {
|
|
11
16
|
method: 'all';
|
|
12
17
|
id: null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE
|
|
1
|
+
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACtB;IAAE,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAO,eAyB1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,aAAa,CAAC,QAAQ,CAAC,EAC/B,IAAI,MAAM,GAAG,IAAI,KACf,IAIF,CAAC"}
|
|
@@ -25,6 +25,10 @@ export const create_recording_closer = () => {
|
|
|
25
25
|
calls.push({ method: 'account', id });
|
|
26
26
|
return 1;
|
|
27
27
|
},
|
|
28
|
+
close_sockets_for_role: (id, role) => {
|
|
29
|
+
calls.push({ method: 'role', id, role });
|
|
30
|
+
return 1;
|
|
31
|
+
},
|
|
28
32
|
close_all_sockets: () => {
|
|
29
33
|
calls.push({ method: 'all', id: null });
|
|
30
34
|
return 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAqZF,CAAC"}
|
|
@@ -2,7 +2,9 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Cross-backend parity suite for the account-lifecycle admin verbs:
|
|
4
4
|
* `account_delete` (soft), `account_undelete` (reactivation), and
|
|
5
|
-
* `account_purge` (keeper hard-delete), plus the keeper guard
|
|
5
|
+
* `account_purge` (keeper hard-delete), plus the keeper guard and the
|
|
6
|
+
* session-only self-delete (an API token can't end its own account; an admin
|
|
7
|
+
* token still deletes another).
|
|
6
8
|
*
|
|
7
9
|
* Like the cell suites, these verbs can't ride the generic
|
|
8
10
|
* `describe_rpc_round_trip_tests`: they're stateful and destructive (a
|
|
@@ -16,7 +18,8 @@ import '../assert_dev_env.js';
|
|
|
16
18
|
*/
|
|
17
19
|
import { describe, assert } from 'vitest';
|
|
18
20
|
import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, AuditLogListOutput, ERROR_CANNOT_DELETE_KEEPER } from '../../auth/admin_action_specs.js';
|
|
19
|
-
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED } from '../../http/error_schemas.js';
|
|
21
|
+
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED } from '../../http/error_schemas.js';
|
|
22
|
+
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
20
23
|
import { test_if } from './capabilities.js';
|
|
21
24
|
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
22
25
|
import { assert_iso8601_seconds } from './wire_shapes.js';
|
|
@@ -55,6 +58,38 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
55
58
|
assert.strictEqual(purge.ok, false, 'purge of keeper account must be refused');
|
|
56
59
|
assert.strictEqual(error_reason(purge), ERROR_CANNOT_DELETE_KEEPER);
|
|
57
60
|
});
|
|
61
|
+
test_if(capabilities.account_lifecycle, 'self-delete is session-only; an admin bearer still deletes another account', async () => {
|
|
62
|
+
const fixture = await setup_test();
|
|
63
|
+
const member = await fixture.create_account({ username: 'lifecycle_self_bearer' });
|
|
64
|
+
// The member's own bearer may not end its account (handler-enforced,
|
|
65
|
+
// the dispatcher's credential-type refusal).
|
|
66
|
+
const refused = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'account_delete', {}, member.create_bearer_headers());
|
|
67
|
+
assert.strictEqual(refused.ok, false, 'a bearer self-delete must be refused');
|
|
68
|
+
assert.strictEqual(refused.error?.code, JSONRPC_ERROR_CODES.forbidden);
|
|
69
|
+
assert.strictEqual(error_reason(refused), ERROR_CREDENTIAL_TYPE_REQUIRED);
|
|
70
|
+
assert.deepStrictEqual(refused.error?.data
|
|
71
|
+
?.required_credential_types, ['session']);
|
|
72
|
+
// An explicit own id is self in either case (`Uuid` admits both) — the
|
|
73
|
+
// keeper's too, whose refusal is the credential one, not the keeper guard.
|
|
74
|
+
for (const [label, account_id, headers] of [
|
|
75
|
+
['member', member.account.id, member.create_bearer_headers()],
|
|
76
|
+
['keeper', fixture.account.id, fixture.create_bearer_headers()]
|
|
77
|
+
]) {
|
|
78
|
+
const upper = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'account_delete', { account_id: account_id.toUpperCase() }, headers);
|
|
79
|
+
assert.strictEqual(upper.ok, false, `${label}: an uppercased own id is self`);
|
|
80
|
+
assert.deepStrictEqual(upper.error, refused.error, label);
|
|
81
|
+
}
|
|
82
|
+
// Nothing was tombstoned: the member's session still verifies.
|
|
83
|
+
const still = await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_verify', undefined, member.create_session_headers());
|
|
84
|
+
assert.ok(still.ok, 'the refused self-delete leaves the account live');
|
|
85
|
+
// The keeper's bearer (admin-capable) deletes another account.
|
|
86
|
+
const deleted = expect_output(await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, 'account_delete', { account_id: member.account.id }, fixture.create_bearer_headers()), AccountDeleteOutput);
|
|
87
|
+
assert.strictEqual(deleted.deleted, true);
|
|
88
|
+
// A self-delete over a session goes through.
|
|
89
|
+
const self = await fixture.create_account({ username: 'lifecycle_self_session' });
|
|
90
|
+
const self_deleted = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'account_delete', {}, self.create_session_headers()), AccountDeleteOutput);
|
|
91
|
+
assert.strictEqual(self_deleted.deleted, true);
|
|
92
|
+
});
|
|
58
93
|
test_if(capabilities.account_lifecycle, 'fail-closed: a soft-deleted account’s session + bearer credentials no longer authenticate', async () => {
|
|
59
94
|
const fixture = await setup_test();
|
|
60
95
|
const victim = await fixture.create_account({ username: 'lifecycle_failclosed' });
|
|
@@ -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;AA6F9B,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;AAwHD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA+HlF,CAAC"}
|