@fuzdev/fuz_app 0.55.0 → 0.57.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 +211 -155
- package/dist/actions/action_bridge.d.ts +8 -5
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +1 -11
- package/dist/actions/action_codegen.d.ts +19 -0
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +20 -14
- package/dist/actions/action_registry.d.ts.map +1 -1
- package/dist/actions/action_registry.js +5 -2
- package/dist/actions/action_rpc.d.ts +110 -44
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +92 -287
- package/dist/actions/action_spec.d.ts +55 -16
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +16 -11
- package/dist/actions/action_types.d.ts +28 -60
- package/dist/actions/action_types.d.ts.map +1 -1
- package/dist/actions/action_types.js +13 -5
- package/dist/actions/broadcast_api.d.ts +2 -2
- package/dist/actions/broadcast_api.js +2 -2
- package/dist/actions/compile_action_registry.d.ts +50 -0
- package/dist/actions/compile_action_registry.d.ts.map +1 -0
- package/dist/actions/compile_action_registry.js +69 -0
- package/dist/actions/heartbeat.d.ts +8 -4
- package/dist/actions/heartbeat.d.ts.map +1 -1
- package/dist/actions/heartbeat.js +5 -4
- package/dist/actions/perform_action.d.ts +145 -0
- package/dist/actions/perform_action.d.ts.map +1 -0
- package/dist/actions/perform_action.js +258 -0
- package/dist/actions/register_action_ws.d.ts +44 -38
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +101 -159
- package/dist/actions/register_ws_endpoint.d.ts +2 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +32 -10
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -1
- package/dist/actions/transports_ws_backend.d.ts +1 -1
- package/dist/actions/transports_ws_backend.js +1 -1
- package/dist/auth/CLAUDE.md +673 -442
- package/dist/auth/account_action_specs.d.ts +28 -7
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +7 -7
- package/dist/auth/account_actions.d.ts +8 -14
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +26 -32
- package/dist/auth/account_queries.d.ts +46 -13
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +73 -33
- package/dist/auth/account_routes.d.ts +4 -3
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +58 -33
- package/dist/auth/account_schema.d.ts +46 -54
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +21 -48
- package/dist/auth/admin_action_specs.d.ts +55 -21
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +42 -26
- package/dist/auth/admin_actions.d.ts +14 -21
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +47 -44
- package/dist/auth/audit_emitter.d.ts +160 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -0
- package/dist/auth/audit_emitter.js +83 -0
- package/dist/auth/audit_log_queries.d.ts +17 -87
- package/dist/auth/audit_log_queries.d.ts.map +1 -1
- package/dist/auth/audit_log_queries.js +17 -96
- package/dist/auth/audit_log_routes.d.ts +1 -1
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +7 -3
- package/dist/auth/audit_log_schema.d.ts +48 -42
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +56 -43
- package/dist/auth/auth_guard_resolver.d.ts +44 -0
- package/dist/auth/auth_guard_resolver.d.ts.map +1 -0
- package/dist/auth/auth_guard_resolver.js +56 -0
- package/dist/auth/bootstrap_account.d.ts +7 -7
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +7 -7
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +11 -10
- package/dist/auth/cleanup.d.ts +20 -26
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +33 -47
- package/dist/auth/credential_type_schema.d.ts +115 -0
- package/dist/auth/credential_type_schema.d.ts.map +1 -0
- package/dist/auth/credential_type_schema.js +127 -0
- package/dist/auth/daemon_token_middleware.d.ts +1 -1
- package/dist/auth/daemon_token_middleware.js +3 -3
- package/dist/auth/ddl.d.ts +2 -2
- package/dist/auth/ddl.d.ts.map +1 -1
- package/dist/auth/ddl.js +6 -6
- package/dist/auth/deps.d.ts +7 -32
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/grant_path_schema.d.ts +117 -0
- package/dist/auth/grant_path_schema.d.ts.map +1 -0
- package/dist/auth/grant_path_schema.js +137 -0
- package/dist/auth/invite_queries.d.ts +12 -1
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +12 -1
- package/dist/auth/invite_schema.d.ts +1 -1
- package/dist/auth/invite_schema.d.ts.map +1 -1
- package/dist/auth/invite_schema.js +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +5 -2
- package/dist/auth/migrations.d.ts +22 -7
- package/dist/auth/migrations.d.ts.map +1 -1
- package/dist/auth/migrations.js +64 -25
- package/dist/auth/request_context.d.ts +157 -170
- package/dist/auth/request_context.d.ts.map +1 -1
- package/dist/auth/request_context.js +224 -268
- package/dist/auth/{permit_offer_action_specs.d.ts → role_grant_offer_action_specs.d.ts} +130 -100
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_action_specs.js +262 -0
- package/dist/auth/role_grant_offer_actions.d.ts +104 -0
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -0
- package/dist/auth/{permit_offer_actions.js → role_grant_offer_actions.js} +153 -140
- package/dist/auth/{permit_offer_notifications.d.ts → role_grant_offer_notifications.d.ts} +80 -70
- package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -0
- package/dist/auth/role_grant_offer_notifications.js +182 -0
- package/dist/auth/{permit_offer_queries.d.ts → role_grant_offer_queries.d.ts} +64 -64
- package/dist/auth/role_grant_offer_queries.d.ts.map +1 -0
- package/dist/auth/{permit_offer_queries.js → role_grant_offer_queries.js} +136 -123
- package/dist/auth/role_grant_offer_schema.d.ts +150 -0
- package/dist/auth/role_grant_offer_schema.d.ts.map +1 -0
- package/dist/auth/{permit_offer_schema.js → role_grant_offer_schema.js} +55 -36
- package/dist/auth/role_grant_queries.d.ts +231 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -0
- package/dist/auth/role_grant_queries.js +320 -0
- package/dist/auth/role_schema.d.ts +150 -40
- package/dist/auth/role_schema.d.ts.map +1 -1
- package/dist/auth/role_schema.js +144 -45
- package/dist/auth/scope_kind_schema.d.ts +96 -0
- package/dist/auth/scope_kind_schema.d.ts.map +1 -0
- package/dist/auth/scope_kind_schema.js +94 -0
- package/dist/auth/self_service_role_action_specs.d.ts +4 -1
- package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
- package/dist/auth/self_service_role_action_specs.js +2 -2
- package/dist/auth/self_service_role_actions.d.ts +35 -29
- package/dist/auth/self_service_role_actions.d.ts.map +1 -1
- package/dist/auth/self_service_role_actions.js +58 -48
- package/dist/auth/session_cookie.d.ts +43 -6
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +31 -5
- package/dist/auth/session_middleware.d.ts +37 -3
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +33 -7
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +48 -19
- package/dist/auth/standard_action_specs.d.ts +2 -2
- package/dist/auth/standard_action_specs.js +4 -4
- package/dist/auth/standard_rpc_actions.d.ts +23 -19
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +12 -12
- package/dist/db/migrate.d.ts +1 -1
- package/dist/db/migrate.js +1 -1
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.d.ts.map +1 -1
- package/dist/dev/setup.js +4 -4
- package/dist/env/load.d.ts +1 -1
- package/dist/env/load.js +1 -1
- package/dist/hono_context.d.ts +27 -45
- package/dist/hono_context.d.ts.map +1 -1
- package/dist/hono_context.js +14 -28
- package/dist/http/CLAUDE.md +235 -121
- package/dist/http/auth_shape.d.ts +191 -0
- package/dist/http/auth_shape.d.ts.map +1 -0
- package/dist/http/auth_shape.js +237 -0
- package/dist/http/common_routes.js +3 -3
- package/dist/http/db_routes.d.ts +4 -0
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +44 -7
- package/dist/http/error_schemas.d.ts +72 -39
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +81 -33
- package/dist/http/pending_effects.d.ts +71 -18
- package/dist/http/pending_effects.d.ts.map +1 -1
- package/dist/http/pending_effects.js +87 -18
- package/dist/http/proxy.d.ts +52 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +92 -14
- package/dist/http/route_spec.d.ts +89 -75
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +54 -72
- package/dist/http/schema_helpers.d.ts +3 -14
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +2 -14
- package/dist/http/surface.d.ts +2 -10
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +3 -4
- package/dist/http/surface_query.d.ts +39 -35
- package/dist/http/surface_query.d.ts.map +1 -1
- package/dist/http/surface_query.js +79 -36
- package/dist/primitive_schemas.d.ts +39 -0
- package/dist/primitive_schemas.d.ts.map +1 -0
- package/dist/primitive_schemas.js +40 -0
- package/dist/realtime/sse_auth_guard.d.ts +5 -5
- package/dist/realtime/sse_auth_guard.js +9 -9
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +14 -11
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +12 -8
- package/dist/server/app_server.d.ts +7 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +35 -40
- package/dist/server/validate_nginx.d.ts +1 -1
- package/dist/server/validate_nginx.js +1 -1
- package/dist/testing/CLAUDE.md +50 -38
- package/dist/testing/admin_integration.d.ts +5 -6
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +87 -85
- package/dist/testing/app_server.d.ts +11 -14
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +16 -15
- package/dist/testing/assertions.d.ts.map +1 -1
- package/dist/testing/assertions.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +15 -9
- package/dist/testing/audit_completeness.d.ts +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +36 -36
- package/dist/testing/auth_apps.d.ts +5 -4
- package/dist/testing/auth_apps.d.ts.map +1 -1
- package/dist/testing/auth_apps.js +22 -19
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +5 -5
- package/dist/testing/db.d.ts +1 -1
- package/dist/testing/db.d.ts.map +1 -1
- package/dist/testing/db.js +4 -4
- package/dist/testing/db_entities.d.ts +22 -0
- package/dist/testing/db_entities.d.ts.map +1 -0
- package/dist/testing/db_entities.js +28 -0
- package/dist/testing/entities.d.ts +8 -7
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +21 -18
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +13 -14
- package/dist/testing/integration_helpers.d.ts +4 -4
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +20 -18
- package/dist/testing/middleware.d.ts +4 -4
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +12 -11
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +40 -24
- package/dist/testing/rpc_round_trip.d.ts +1 -1
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +14 -13
- package/dist/testing/sse_round_trip.d.ts +3 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +7 -11
- package/dist/testing/standard.d.ts +1 -1
- package/dist/testing/stubs.d.ts +25 -0
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +43 -2
- package/dist/testing/surface_invariants.d.ts +14 -6
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +119 -43
- package/dist/testing/ws_round_trip.d.ts +12 -13
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -11
- package/dist/ui/AdminAccounts.svelte +23 -20
- package/dist/ui/AdminOverview.svelte +15 -13
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/{AdminPermitHistory.svelte → AdminRoleGrantHistory.svelte} +12 -12
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts +4 -0
- package/dist/ui/AdminRoleGrantHistory.svelte.d.ts.map +1 -0
- package/dist/ui/BootstrapForm.svelte +1 -1
- package/dist/ui/CLAUDE.md +60 -60
- package/dist/ui/{PermitOfferForm.svelte → RoleGrantOfferForm.svelte} +27 -26
- package/dist/ui/{PermitOfferForm.svelte.d.ts → RoleGrantOfferForm.svelte.d.ts} +7 -7
- package/dist/ui/RoleGrantOfferForm.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferHistory.svelte → RoleGrantOfferHistory.svelte} +12 -12
- package/dist/ui/{PermitOfferHistory.svelte.d.ts → RoleGrantOfferHistory.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -0
- package/dist/ui/{PermitOfferInbox.svelte → RoleGrantOfferInbox.svelte} +14 -14
- package/dist/ui/{PermitOfferInbox.svelte.d.ts → RoleGrantOfferInbox.svelte.d.ts} +4 -4
- package/dist/ui/RoleGrantOfferInbox.svelte.d.ts.map +1 -0
- package/dist/ui/SignupForm.svelte +1 -1
- package/dist/ui/SurfaceExplorer.svelte +35 -15
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +2 -3
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +2 -3
- package/dist/ui/admin_accounts_state.svelte.d.ts +18 -18
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -16
- package/dist/ui/admin_rpc_adapters.d.ts +20 -20
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +17 -17
- package/dist/ui/admin_sessions_state.svelte.d.ts +2 -2
- package/dist/ui/admin_sessions_state.svelte.js +2 -2
- package/dist/ui/audit_log_state.svelte.d.ts +7 -7
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +6 -6
- package/dist/ui/auth_state.svelte.d.ts +3 -3
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +6 -6
- package/dist/ui/format_scope.d.ts +2 -2
- package/dist/ui/format_scope.js +2 -2
- package/dist/ui/{permit_offers_state.svelte.d.ts → role_grant_offers_state.svelte.d.ts} +30 -30
- package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -0
- package/dist/ui/{permit_offers_state.svelte.js → role_grant_offers_state.svelte.js} +18 -18
- package/dist/ui/ui_format.js +2 -2
- package/package.json +3 -3
- package/dist/auth/permit_offer_action_specs.d.ts.map +0 -1
- package/dist/auth/permit_offer_action_specs.js +0 -258
- package/dist/auth/permit_offer_actions.d.ts +0 -110
- package/dist/auth/permit_offer_actions.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.d.ts.map +0 -1
- package/dist/auth/permit_offer_notifications.js +0 -182
- package/dist/auth/permit_offer_queries.d.ts.map +0 -1
- package/dist/auth/permit_offer_schema.d.ts +0 -125
- package/dist/auth/permit_offer_schema.d.ts.map +0 -1
- package/dist/auth/permit_queries.d.ts +0 -222
- package/dist/auth/permit_queries.d.ts.map +0 -1
- package/dist/auth/permit_queries.js +0 -305
- package/dist/auth/require_keeper.d.ts +0 -20
- package/dist/auth/require_keeper.d.ts.map +0 -1
- package/dist/auth/require_keeper.js +0 -35
- package/dist/auth/route_guards.d.ts +0 -27
- package/dist/auth/route_guards.d.ts.map +0 -1
- package/dist/auth/route_guards.js +0 -38
- package/dist/auth/session_lifecycle.d.ts +0 -37
- package/dist/auth/session_lifecycle.d.ts.map +0 -1
- package/dist/auth/session_lifecycle.js +0 -29
- package/dist/ui/AdminPermitHistory.svelte.d.ts +0 -4
- package/dist/ui/AdminPermitHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferForm.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferHistory.svelte.d.ts.map +0 -1
- package/dist/ui/PermitOfferInbox.svelte.d.ts.map +0 -1
- package/dist/ui/permit_offers_state.svelte.d.ts.map +0 -1
package/dist/testing/stubs.js
CHANGED
|
@@ -14,6 +14,8 @@ import { prefix_route_specs } from '../http/route_spec.js';
|
|
|
14
14
|
import { create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
|
|
15
15
|
import { create_rpc_endpoint } from '../actions/action_rpc.js';
|
|
16
16
|
import { create_app_surface_spec, } from '../http/surface.js';
|
|
17
|
+
import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
|
|
18
|
+
import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
|
|
17
19
|
import { BaseServerEnv } from '../server/env.js';
|
|
18
20
|
/* eslint-disable @typescript-eslint/require-await */
|
|
19
21
|
/**
|
|
@@ -88,6 +90,45 @@ export const stub_handler = () => new Response('stub');
|
|
|
88
90
|
/** Stub middleware that passes through. */
|
|
89
91
|
export const stub_mw = async (_c, next) => next();
|
|
90
92
|
const stub_db = create_noop_stub('stub_db');
|
|
93
|
+
/**
|
|
94
|
+
* Build a no-op `AuditEmitter` for tests that don't assert on audit fan-out.
|
|
95
|
+
*
|
|
96
|
+
* `emit` / `emit_role_grant_target` are no-ops; `emit_pool` resolves
|
|
97
|
+
* immediately; `notify` is a no-op; `on_event_chain` is a frozen empty
|
|
98
|
+
* array — pushing onto it throws at runtime, so a test that wires a
|
|
99
|
+
* listener fails loudly instead of silently never firing. Tests asserting
|
|
100
|
+
* on real audit-row persistence (or on listener fan-out) build a real
|
|
101
|
+
* emitter via `create_audit_emitter` against a stub or real DB —
|
|
102
|
+
* `create_test_app` already does this on the test backend.
|
|
103
|
+
*/
|
|
104
|
+
export const create_test_audit_emitter = () => ({
|
|
105
|
+
emit: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
106
|
+
emit_role_grant_target: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
107
|
+
emit_pool: async () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
108
|
+
notify: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
109
|
+
on_event_chain: Object.freeze([]),
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Build a no-op `AuditLogSse` for tests that wire `audit_sse` into the
|
|
113
|
+
* surface helper but don't assert on SSE fan-out or subscriber state.
|
|
114
|
+
*
|
|
115
|
+
* `subscribe` returns a no-op cleanup; `on_audit_event` is a no-op; the
|
|
116
|
+
* `registry` is a fresh `SubscriberRegistry` instance (call sites that
|
|
117
|
+
* inspect `.size` or call `.close_*` see a real registry, so writes are
|
|
118
|
+
* isolated per test). Tests that need real SSE plumbing build it via
|
|
119
|
+
* `create_audit_log_sse` against `create_test_app`.
|
|
120
|
+
*/
|
|
121
|
+
export const create_stub_audit_sse = () => {
|
|
122
|
+
const registry = new SubscriberRegistry({
|
|
123
|
+
max_per_scope: AUDIT_LOG_SSE_MAX_PER_SCOPE,
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
subscribe: () => () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
127
|
+
log: new Logger('test:audit_sse', { level: 'off' }),
|
|
128
|
+
on_audit_event: () => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
129
|
+
registry,
|
|
130
|
+
};
|
|
131
|
+
};
|
|
91
132
|
/** Stub `AppDeps` for auth surface tests — throws on any method access. */
|
|
92
133
|
export const stub_app_deps = {
|
|
93
134
|
stat: create_throwing_stub('stat'),
|
|
@@ -97,7 +138,7 @@ export const stub_app_deps = {
|
|
|
97
138
|
password: create_throwing_stub('password'),
|
|
98
139
|
db: create_throwing_stub('db'),
|
|
99
140
|
log: create_throwing_stub('log'),
|
|
100
|
-
|
|
141
|
+
audit: create_test_audit_emitter(),
|
|
101
142
|
};
|
|
102
143
|
/**
|
|
103
144
|
* Create no-op `AppDeps` for auth surface testing.
|
|
@@ -110,7 +151,7 @@ export const create_stub_app_deps = () => ({
|
|
|
110
151
|
password: create_noop_stub('password'),
|
|
111
152
|
db: stub_db,
|
|
112
153
|
log: new Logger('test', { level: 'off' }),
|
|
113
|
-
|
|
154
|
+
audit: create_test_audit_emitter(),
|
|
114
155
|
});
|
|
115
156
|
/** Create the API middleware stub array matching `create_auth_middleware_specs` output. */
|
|
116
157
|
export const create_stub_api_middleware = (options) => {
|
|
@@ -37,6 +37,10 @@ export declare const assert_middleware_errors_propagated: (surface: AppSurface)
|
|
|
37
37
|
* Every route's declared error schemas must have an `error` field at the top level
|
|
38
38
|
* (conforming to the `ApiError` base shape `{error: string}`).
|
|
39
39
|
*
|
|
40
|
+
* Walks union branches (`anyOf` from `z.union`, `oneOf` from
|
|
41
|
+
* `z.discriminatedUnion`) so every emit shape inside a merged 400 / 404
|
|
42
|
+
* is checked, not just the top-level wrapper.
|
|
43
|
+
*
|
|
40
44
|
* Catches typos in error schema definitions and ensures consumers can always
|
|
41
45
|
* read `.error` from error responses.
|
|
42
46
|
*/
|
|
@@ -46,11 +50,15 @@ export declare const assert_error_schemas_structurally_valid: (surface: AppSurfa
|
|
|
46
50
|
* across routes.
|
|
47
51
|
*
|
|
48
52
|
* Extracts `const` values from error schema `error` properties (which correspond to
|
|
49
|
-
* `z.literal()` in the Zod source).
|
|
50
|
-
*
|
|
53
|
+
* `z.literal()` in the Zod source). Walks union branches (`anyOf` from `z.union`,
|
|
54
|
+
* `oneOf` from `z.discriminatedUnion`) so literal codes nested inside merged unions
|
|
55
|
+
* (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the
|
|
56
|
+
* same literal appears at different status codes — e.g., `ERROR_INVALID_CREDENTIALS`
|
|
57
|
+
* at both 401 and 403 would be a bug.
|
|
51
58
|
*
|
|
52
|
-
* Only checks
|
|
53
|
-
*
|
|
59
|
+
* Only checks `const` values (literal schemas). Generic `z.string()` schemas
|
|
60
|
+
* (which produce `{type: 'string'}`) and `z.enum()` schemas are ignored — the
|
|
61
|
+
* literal-only narrow keeps the check unambiguous.
|
|
54
62
|
*/
|
|
55
63
|
export declare const assert_error_code_status_consistency: (surface: AppSurface) => void;
|
|
56
64
|
/**
|
|
@@ -97,8 +105,8 @@ export interface SurfaceSecurityPolicyOptions {
|
|
|
97
105
|
/**
|
|
98
106
|
* Path patterns for routes that should be rate-limited.
|
|
99
107
|
* Default: common sensitive REST patterns (login, password, bootstrap).
|
|
100
|
-
* `account_token_create`
|
|
101
|
-
*
|
|
108
|
+
* `account_token_create` lives on the RPC surface; per-method RPC rate
|
|
109
|
+
* limiting is a separate invariant if consumers want it.
|
|
102
110
|
*/
|
|
103
111
|
sensitive_route_patterns?: Array<string | RegExp>;
|
|
104
112
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAuB7B,OAAO,KAAK,EAAC,UAAU,EAAuB,MAAM,oBAAoB,CAAC;AAczE;;GAEG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAQzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,UAAU,KAAG,IASpE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,UAAU,KAAG,IAQtE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,UAAU,KAAG,IAIjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS,UAAU,KAAG,IAOhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAezE,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
|
|
@@ -105,6 +105,10 @@ export const assert_middleware_errors_propagated = (surface) => {
|
|
|
105
105
|
* Every route's declared error schemas must have an `error` field at the top level
|
|
106
106
|
* (conforming to the `ApiError` base shape `{error: string}`).
|
|
107
107
|
*
|
|
108
|
+
* Walks union branches (`anyOf` from `z.union`, `oneOf` from
|
|
109
|
+
* `z.discriminatedUnion`) so every emit shape inside a merged 400 / 404
|
|
110
|
+
* is checked, not just the top-level wrapper.
|
|
111
|
+
*
|
|
108
112
|
* Catches typos in error schema definitions and ensures consumers can always
|
|
109
113
|
* read `.error` from error responses.
|
|
110
114
|
*/
|
|
@@ -113,15 +117,24 @@ export const assert_error_schemas_structurally_valid = (surface) => {
|
|
|
113
117
|
if (!route.error_schemas)
|
|
114
118
|
continue;
|
|
115
119
|
for (const [status, schema] of Object.entries(route.error_schemas)) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
assert_branch_has_error_property(schema, format_route_key(route), status);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const assert_branch_has_error_property = (schema, route_key, status) => {
|
|
125
|
+
const branches = get_union_branches(schema);
|
|
126
|
+
if (branches) {
|
|
127
|
+
for (const branch of branches) {
|
|
128
|
+
assert_branch_has_error_property(branch, route_key, status);
|
|
124
129
|
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (typeof schema !== 'object' || schema === null)
|
|
133
|
+
return;
|
|
134
|
+
const s = schema;
|
|
135
|
+
if (s.type === 'object' && s.properties && typeof s.properties === 'object') {
|
|
136
|
+
const props = s.properties;
|
|
137
|
+
assert.ok('error' in props, `${route_key} error schema for status ${status} missing 'error' property`);
|
|
125
138
|
}
|
|
126
139
|
};
|
|
127
140
|
/**
|
|
@@ -129,44 +142,42 @@ export const assert_error_schemas_structurally_valid = (surface) => {
|
|
|
129
142
|
* across routes.
|
|
130
143
|
*
|
|
131
144
|
* Extracts `const` values from error schema `error` properties (which correspond to
|
|
132
|
-
* `z.literal()` in the Zod source).
|
|
133
|
-
*
|
|
145
|
+
* `z.literal()` in the Zod source). Walks union branches (`anyOf` from `z.union`,
|
|
146
|
+
* `oneOf` from `z.discriminatedUnion`) so literal codes nested inside merged unions
|
|
147
|
+
* (e.g. validation 400 + actor-resolution 400) are still tracked. Flags when the
|
|
148
|
+
* same literal appears at different status codes — e.g., `ERROR_INVALID_CREDENTIALS`
|
|
149
|
+
* at both 401 and 403 would be a bug.
|
|
134
150
|
*
|
|
135
|
-
* Only checks
|
|
136
|
-
*
|
|
151
|
+
* Only checks `const` values (literal schemas). Generic `z.string()` schemas
|
|
152
|
+
* (which produce `{type: 'string'}`) and `z.enum()` schemas are ignored — the
|
|
153
|
+
* literal-only narrow keeps the check unambiguous.
|
|
137
154
|
*/
|
|
138
155
|
export const assert_error_code_status_consistency = (surface) => {
|
|
139
156
|
// Map from error code literal → Set of status codes where it appears
|
|
140
157
|
const code_to_statuses = new Map();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
for (const [status, schema] of Object.entries(route.error_schemas)) {
|
|
145
|
-
const error_const = extract_error_const(schema);
|
|
146
|
-
if (error_const === null)
|
|
147
|
-
continue;
|
|
148
|
-
let statuses = code_to_statuses.get(error_const);
|
|
158
|
+
const record = (status, schema) => {
|
|
159
|
+
for (const code of extract_error_consts(schema)) {
|
|
160
|
+
let statuses = code_to_statuses.get(code);
|
|
149
161
|
if (!statuses) {
|
|
150
162
|
statuses = new Set();
|
|
151
|
-
code_to_statuses.set(
|
|
163
|
+
code_to_statuses.set(code, statuses);
|
|
152
164
|
}
|
|
153
165
|
statuses.add(status);
|
|
154
166
|
}
|
|
167
|
+
};
|
|
168
|
+
for (const route of surface.routes) {
|
|
169
|
+
if (!route.error_schemas)
|
|
170
|
+
continue;
|
|
171
|
+
for (const [status, schema] of Object.entries(route.error_schemas)) {
|
|
172
|
+
record(status, schema);
|
|
173
|
+
}
|
|
155
174
|
}
|
|
156
175
|
// Also check middleware error schemas
|
|
157
176
|
for (const mw of surface.middleware) {
|
|
158
177
|
if (!mw.error_schemas)
|
|
159
178
|
continue;
|
|
160
179
|
for (const [status, schema] of Object.entries(mw.error_schemas)) {
|
|
161
|
-
|
|
162
|
-
if (error_const === null)
|
|
163
|
-
continue;
|
|
164
|
-
let statuses = code_to_statuses.get(error_const);
|
|
165
|
-
if (!statuses) {
|
|
166
|
-
statuses = new Set();
|
|
167
|
-
code_to_statuses.set(error_const, statuses);
|
|
168
|
-
}
|
|
169
|
-
statuses.add(status);
|
|
180
|
+
record(status, schema);
|
|
170
181
|
}
|
|
171
182
|
}
|
|
172
183
|
for (const [code, statuses] of code_to_statuses) {
|
|
@@ -191,31 +202,59 @@ const get_error_property = (schema) => {
|
|
|
191
202
|
return props.error;
|
|
192
203
|
};
|
|
193
204
|
/**
|
|
194
|
-
*
|
|
205
|
+
* Read the branch array off a JSON Schema union, if present.
|
|
206
|
+
*
|
|
207
|
+
* Zod 4 emits `anyOf` for `z.union(...)` and `oneOf` for
|
|
208
|
+
* `z.discriminatedUnion(...)` via `z.toJSONSchema`; both are union-shaped
|
|
209
|
+
* for tightness/code-extraction purposes. Nested unions are NOT flattened
|
|
210
|
+
* by `toJSONSchema`, so every caller must recurse through the returned
|
|
211
|
+
* branches. Returns the branch array or `null` for non-union schemas.
|
|
212
|
+
*/
|
|
213
|
+
const get_union_branches = (schema) => {
|
|
214
|
+
if (typeof schema !== 'object' || schema === null)
|
|
215
|
+
return null;
|
|
216
|
+
const s = schema;
|
|
217
|
+
if (Array.isArray(s.anyOf))
|
|
218
|
+
return s.anyOf;
|
|
219
|
+
if (Array.isArray(s.oneOf))
|
|
220
|
+
return s.oneOf;
|
|
221
|
+
return null;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Extract every `const` value from a JSON Schema error property, walking
|
|
225
|
+
* union branches.
|
|
195
226
|
*
|
|
196
227
|
* Looks for `schema.properties.error.const` — the JSON Schema representation
|
|
197
|
-
* of `z.literal('some_error_code')
|
|
228
|
+
* of `z.literal('some_error_code')` — and recurses into `anyOf` / `oneOf`
|
|
229
|
+
* branches so literals nested inside `z.union` or `z.discriminatedUnion`
|
|
230
|
+
* are still tracked. Returns an empty array for schemas with no literal
|
|
231
|
+
* codes (`z.enum`, `z.string`, non-object schemas).
|
|
198
232
|
*/
|
|
199
|
-
const
|
|
233
|
+
const extract_error_consts = (schema) => {
|
|
234
|
+
const branches = get_union_branches(schema);
|
|
235
|
+
if (branches) {
|
|
236
|
+
const codes = [];
|
|
237
|
+
for (const branch of branches) {
|
|
238
|
+
codes.push(...extract_error_consts(branch));
|
|
239
|
+
}
|
|
240
|
+
return codes;
|
|
241
|
+
}
|
|
200
242
|
const error_prop = get_error_property(schema);
|
|
201
243
|
if (!error_prop)
|
|
202
|
-
return
|
|
244
|
+
return [];
|
|
203
245
|
if (typeof error_prop.const === 'string')
|
|
204
|
-
return error_prop.const;
|
|
205
|
-
return
|
|
246
|
+
return [error_prop.const];
|
|
247
|
+
return [];
|
|
206
248
|
};
|
|
207
249
|
/**
|
|
208
250
|
* Check if a JSON Schema error property uses specific error codes (`const` or `enum`),
|
|
209
251
|
* not just generic `z.string()` (`{type: 'string'}`).
|
|
210
252
|
*
|
|
211
|
-
* Returns `true` for `z.literal()` (`{const: '...'}`) and `z.enum()` (`{enum: [...]}`)
|
|
253
|
+
* Returns `true` for `z.literal()` (`{const: '...'}`) and `z.enum()` (`{enum: [...]}`),
|
|
254
|
+
* and for union schemas where every branch is specific. Defers to
|
|
255
|
+
* `classify_error_specificity` so the union walk stays in one place.
|
|
212
256
|
*/
|
|
213
|
-
const has_specific_error_schema = (schema) =>
|
|
214
|
-
const error_prop = get_error_property(schema);
|
|
215
|
-
if (!error_prop)
|
|
216
|
-
return false;
|
|
217
|
-
return typeof error_prop.const === 'string' || Array.isArray(error_prop.enum);
|
|
218
|
-
};
|
|
257
|
+
const has_specific_error_schema = (schema) => classify_error_specificity(schema) !== 'generic';
|
|
219
258
|
/**
|
|
220
259
|
* Routes declaring 404 error schemas should use specific `z.literal()` or `z.enum()`
|
|
221
260
|
* error codes, not generic `z.string()`.
|
|
@@ -245,8 +284,29 @@ export const assert_404_schemas_use_specific_errors = (surface) => {
|
|
|
245
284
|
* - `'literal'` — `z.literal()` (`{const: '...'}`)
|
|
246
285
|
* - `'enum'` — `z.enum()` (`{enum: [...]}`)
|
|
247
286
|
* - `'generic'` — `z.string()` or unrecognized
|
|
287
|
+
*
|
|
288
|
+
* Walks union branches (`anyOf` from `z.union`, `oneOf` from
|
|
289
|
+
* `z.discriminatedUnion`) — `derive_error_schemas` emits `anyOf` when it
|
|
290
|
+
* merges multiple shapes at one status (e.g. validation 400 +
|
|
291
|
+
* actor-resolution 400), and a consumer that explicitly declares a
|
|
292
|
+
* discriminated-union error schema emits `oneOf`. Reports the **minimum**
|
|
293
|
+
* specificity across branches — a union's contract is only as tight as
|
|
294
|
+
* its loosest member.
|
|
248
295
|
*/
|
|
249
296
|
const classify_error_specificity = (schema) => {
|
|
297
|
+
const branches = get_union_branches(schema);
|
|
298
|
+
if (branches) {
|
|
299
|
+
if (branches.length === 0)
|
|
300
|
+
return 'generic';
|
|
301
|
+
let min = 'literal';
|
|
302
|
+
for (const branch of branches) {
|
|
303
|
+
const branch_specificity = classify_error_specificity(branch);
|
|
304
|
+
if (SPECIFICITY_ORDER[branch_specificity] < SPECIFICITY_ORDER[min]) {
|
|
305
|
+
min = branch_specificity;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return min;
|
|
309
|
+
}
|
|
250
310
|
const error_prop = get_error_property(schema);
|
|
251
311
|
if (!error_prop)
|
|
252
312
|
return 'generic';
|
|
@@ -260,8 +320,24 @@ const classify_error_specificity = (schema) => {
|
|
|
260
320
|
* Extract error code values from a JSON Schema error property.
|
|
261
321
|
*
|
|
262
322
|
* Returns the literal value or enum array, or `null` for generic schemas.
|
|
323
|
+
*
|
|
324
|
+
* For union schemas (`anyOf` / `oneOf`), collects codes from every branch
|
|
325
|
+
* (deduped). If any branch is generic, returns `null` because the union
|
|
326
|
+
* admits arbitrary strings on that branch.
|
|
263
327
|
*/
|
|
264
328
|
const extract_error_codes = (schema) => {
|
|
329
|
+
const branches = get_union_branches(schema);
|
|
330
|
+
if (branches) {
|
|
331
|
+
const codes = new Set();
|
|
332
|
+
for (const branch of branches) {
|
|
333
|
+
const branch_codes = extract_error_codes(branch);
|
|
334
|
+
if (branch_codes === null)
|
|
335
|
+
return null;
|
|
336
|
+
for (const code of branch_codes)
|
|
337
|
+
codes.add(code);
|
|
338
|
+
}
|
|
339
|
+
return [...codes];
|
|
340
|
+
}
|
|
265
341
|
const error_prop = get_error_property(schema);
|
|
266
342
|
if (!error_prop)
|
|
267
343
|
return null;
|
|
@@ -43,7 +43,7 @@ import { type Uuid } from '@fuzdev/fuz_util/id.js';
|
|
|
43
43
|
import type { ActionSpecUnion } from '../actions/action_spec.js';
|
|
44
44
|
import type { Action } from '../actions/action_types.js';
|
|
45
45
|
import type { ActionEventEnvironment } from '../actions/action_event_types.js';
|
|
46
|
-
import { type
|
|
46
|
+
import { type RegisterActionWsOptions } from '../actions/register_action_ws.js';
|
|
47
47
|
import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
|
|
48
48
|
import { type RequestContext } from '../auth/request_context.js';
|
|
49
49
|
import { type CredentialType } from '../hono_context.js';
|
|
@@ -124,7 +124,7 @@ export interface WsConnectIdentity {
|
|
|
124
124
|
session_id?: string;
|
|
125
125
|
/** Api token id; set for bearer connections, null otherwise. */
|
|
126
126
|
api_token_id?: string | null;
|
|
127
|
-
/** Roles to grant via active
|
|
127
|
+
/** Roles to grant via active role_grants. Pass `[ROLE_KEEPER]` for keeper actions. */
|
|
128
128
|
roles?: Array<string>;
|
|
129
129
|
}
|
|
130
130
|
/** A mock WS client: send requests, inspect/await incoming messages. */
|
|
@@ -214,15 +214,14 @@ export declare const is_notification_with: <P>(method: string, match: (params: P
|
|
|
214
214
|
/** Predicate matching a JSON-RPC response frame (success or error) for the given request id. */
|
|
215
215
|
export declare const is_response_for: (id: number | string) => (msg: unknown) => boolean;
|
|
216
216
|
/** Options for `create_ws_test_harness`. */
|
|
217
|
-
export interface CreateWsTestHarnessOptions
|
|
217
|
+
export interface CreateWsTestHarnessOptions {
|
|
218
218
|
/**
|
|
219
219
|
* The actions registered on this endpoint — matches the shape
|
|
220
220
|
* `register_action_ws` accepts. Each entry is a `{spec, handler?}` tuple;
|
|
221
221
|
* shared fuz_app primitives (like `heartbeat_action`) can be spread in
|
|
222
222
|
* alongside consumer-specific actions.
|
|
223
223
|
*/
|
|
224
|
-
actions: ReadonlyArray<Action
|
|
225
|
-
extend_context?: RegisterActionWsOptions<TCtx>['extend_context'];
|
|
224
|
+
actions: ReadonlyArray<Action>;
|
|
226
225
|
/** Pass a pre-created transport to share with a broadcast API. */
|
|
227
226
|
transport?: BackendWebsocketTransport;
|
|
228
227
|
/**
|
|
@@ -230,13 +229,13 @@ export interface CreateWsTestHarnessOptions<TCtx extends BaseHandlerContext> {
|
|
|
230
229
|
* fake timers + receive-silence detection need explicit opt-in and per-
|
|
231
230
|
* test tuning to avoid spurious closes.
|
|
232
231
|
*/
|
|
233
|
-
heartbeat?: RegisterActionWsOptions
|
|
232
|
+
heartbeat?: RegisterActionWsOptions['heartbeat'];
|
|
234
233
|
/** Optional logger. Defaults to a silent `[ws-test]` logger. */
|
|
235
234
|
log?: Logger;
|
|
236
235
|
/** Threaded straight through to `register_action_ws`. */
|
|
237
|
-
on_socket_open?: RegisterActionWsOptions
|
|
236
|
+
on_socket_open?: RegisterActionWsOptions['on_socket_open'];
|
|
238
237
|
/** Threaded straight through to `register_action_ws`. */
|
|
239
|
-
on_socket_close?: RegisterActionWsOptions
|
|
238
|
+
on_socket_close?: RegisterActionWsOptions['on_socket_close'];
|
|
240
239
|
}
|
|
241
240
|
/** A harness instance — transport handle + connection factory. */
|
|
242
241
|
export interface WsTestHarness {
|
|
@@ -259,24 +258,24 @@ export interface WsTestHarness {
|
|
|
259
258
|
* auth identity. Returned clients drive the real
|
|
260
259
|
* `onOpen`/`onMessage`/`onClose` path against a real `WSContext`.
|
|
261
260
|
*/
|
|
262
|
-
export declare const create_ws_test_harness:
|
|
261
|
+
export declare const create_ws_test_harness: (options: CreateWsTestHarnessOptions) => WsTestHarness;
|
|
263
262
|
/** Convenience: default identity for keeper-authenticated connections. */
|
|
264
263
|
export declare const keeper_identity: () => WsConnectIdentity;
|
|
265
264
|
/**
|
|
266
265
|
* Wire a typed broadcast API against the harness's transport, matching
|
|
267
266
|
* how a consumer's real backend composes the stack. Returns the typed
|
|
268
|
-
* API so tests can call `.
|
|
267
|
+
* API so tests can call `.zap_run_created(...)` / `.workspace_changed(...)`
|
|
269
268
|
* etc. directly.
|
|
270
269
|
*
|
|
271
270
|
* ```ts
|
|
272
|
-
* const harness = create_ws_test_harness
|
|
271
|
+
* const harness = create_ws_test_harness({actions});
|
|
273
272
|
* const broadcast = build_broadcast_api<MyBackendActionsApi>({
|
|
274
273
|
* harness,
|
|
275
274
|
* specs: my_broadcast_action_specs,
|
|
276
275
|
* });
|
|
277
276
|
* const client = await harness.connect(keeper_identity());
|
|
278
|
-
* await broadcast.
|
|
279
|
-
* await client.wait_for(is_notification('
|
|
277
|
+
* await broadcast.zap_run_created({run_id: '...', ...});
|
|
278
|
+
* await client.wait_for(is_notification('zap_run_created'));
|
|
280
279
|
* ```
|
|
281
280
|
*/
|
|
282
281
|
export declare const build_broadcast_api: <TApi extends object>(options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAO,MAAM,MAAM,CAAC;AACxC,OAAO,EACN,SAAS,EAET,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACb,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAC/C,OAAO,EAAc,KAAK,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAEvD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,kCAAkC,CAAC;AAE7E,OAAO,
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/ws_round_trip.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,14 +41,15 @@ import { Logger } from '@fuzdev/fuz_util/log.js';
|
|
|
41
41
|
import { create_uuid } from '@fuzdev/fuz_util/id.js';
|
|
42
42
|
import { ActionPeer } from '../actions/action_peer.js';
|
|
43
43
|
import { create_broadcast_api } from '../actions/broadcast_api.js';
|
|
44
|
-
import { register_action_ws
|
|
44
|
+
import { register_action_ws } from '../actions/register_action_ws.js';
|
|
45
|
+
import { create_stub_db } from './stubs.js';
|
|
45
46
|
import { BackendWebsocketTransport } from '../actions/transports_ws_backend.js';
|
|
46
47
|
import { REQUEST_CONTEXT_KEY } from '../auth/request_context.js';
|
|
47
48
|
import { ROLE_KEEPER } from '../auth/role_schema.js';
|
|
48
49
|
import { ACCOUNT_ID_KEY, AUTH_API_TOKEN_ID_KEY, CREDENTIAL_TYPE_KEY, TEST_CONTEXT_PRESET_KEY, } from '../hono_context.js';
|
|
49
50
|
import { JSONRPC_VERSION } from '../http/jsonrpc.js';
|
|
50
51
|
import { create_jsonrpc_request, is_jsonrpc_error_response, is_jsonrpc_notification, is_jsonrpc_response, } from '../http/jsonrpc_helpers.js';
|
|
51
|
-
import { create_test_account, create_test_actor,
|
|
52
|
+
import { create_test_account, create_test_actor, create_test_role_grant } from './entities.js';
|
|
52
53
|
/**
|
|
53
54
|
* Build a real `WSContext` backed by in-memory `send`/`close` capture.
|
|
54
55
|
* Parsing of outgoing frames is left to the caller — `sends` holds the
|
|
@@ -160,7 +161,7 @@ export const is_notification_with = (method, match) => (msg) => is_jsonrpc_notif
|
|
|
160
161
|
export const is_response_for = (id) => (msg) => (is_jsonrpc_response(msg) || is_jsonrpc_error_response(msg)) && msg.id === id;
|
|
161
162
|
const DEFAULT_TIMEOUT_MS = 1000;
|
|
162
163
|
/**
|
|
163
|
-
* Build a `RequestContext` with a fresh UUID account/actor and
|
|
164
|
+
* Build a `RequestContext` with a fresh UUID account/actor and role_grants
|
|
164
165
|
* for the supplied roles. Used by the high-level harness so callers can
|
|
165
166
|
* pass `roles: [ROLE_KEEPER, 'admin']`.
|
|
166
167
|
*/
|
|
@@ -187,10 +188,11 @@ const build_multi_role_request_context = (account_id, roles) => {
|
|
|
187
188
|
updated_at: null,
|
|
188
189
|
updated_by: null,
|
|
189
190
|
},
|
|
190
|
-
|
|
191
|
+
role_grants: roles.map((role) => ({
|
|
191
192
|
id: create_uuid(),
|
|
192
193
|
actor_id,
|
|
193
194
|
role,
|
|
195
|
+
scope_kind: null,
|
|
194
196
|
scope_id: null,
|
|
195
197
|
created_at: now,
|
|
196
198
|
expires_at: null,
|
|
@@ -210,7 +212,7 @@ const build_multi_role_request_context = (account_id, roles) => {
|
|
|
210
212
|
const build_simple_request_context = (role) => ({
|
|
211
213
|
account: create_test_account({ id: 'acc_1', username: 'testuser' }),
|
|
212
214
|
actor: create_test_actor({ id: 'act_1', account_id: 'acc_1', name: 'testuser' }),
|
|
213
|
-
|
|
215
|
+
role_grants: role ? [create_test_role_grant({ id: 'perm_1', actor_id: 'act_1', role })] : [],
|
|
214
216
|
});
|
|
215
217
|
/**
|
|
216
218
|
* Create a WebSocket test harness for the given specs + handlers.
|
|
@@ -222,16 +224,22 @@ const build_simple_request_context = (role) => ({
|
|
|
222
224
|
* `onOpen`/`onMessage`/`onClose` path against a real `WSContext`.
|
|
223
225
|
*/
|
|
224
226
|
export const create_ws_test_harness = (options) => {
|
|
225
|
-
const { actions,
|
|
227
|
+
const { actions, transport = new BackendWebsocketTransport(), heartbeat = false, log = new Logger('[ws-test]', { level: 'off' }), on_socket_open, on_socket_close, } = options;
|
|
226
228
|
const stub = create_stub_upgrade();
|
|
227
229
|
// Minimal Hono stub — `register_action_ws` only needs `.get(path, handler)`.
|
|
228
230
|
const stub_app = { get: () => stub_app };
|
|
231
|
+
// Stub DB — the harness pre-bakes `RequestContext` via the test-preset
|
|
232
|
+
// escape hatch so `perform_action` skips the live authorization phase.
|
|
233
|
+
// `db.transaction(fn)` synchronously calls `fn(stub_db)` so handlers
|
|
234
|
+
// declaring `side_effects: true` execute under the same shape they
|
|
235
|
+
// would in production.
|
|
236
|
+
const stub_db = create_stub_db();
|
|
229
237
|
register_action_ws({
|
|
230
238
|
path: '/test/ws',
|
|
231
239
|
app: stub_app,
|
|
232
240
|
upgradeWebSocket: stub.upgradeWebSocket,
|
|
233
241
|
actions,
|
|
234
|
-
|
|
242
|
+
db: stub_db,
|
|
235
243
|
transport,
|
|
236
244
|
heartbeat,
|
|
237
245
|
log,
|
|
@@ -383,18 +391,18 @@ const make_peer = () => new ActionPeer({ environment: new MinimalActionEnvironme
|
|
|
383
391
|
/**
|
|
384
392
|
* Wire a typed broadcast API against the harness's transport, matching
|
|
385
393
|
* how a consumer's real backend composes the stack. Returns the typed
|
|
386
|
-
* API so tests can call `.
|
|
394
|
+
* API so tests can call `.zap_run_created(...)` / `.workspace_changed(...)`
|
|
387
395
|
* etc. directly.
|
|
388
396
|
*
|
|
389
397
|
* ```ts
|
|
390
|
-
* const harness = create_ws_test_harness
|
|
398
|
+
* const harness = create_ws_test_harness({actions});
|
|
391
399
|
* const broadcast = build_broadcast_api<MyBackendActionsApi>({
|
|
392
400
|
* harness,
|
|
393
401
|
* specs: my_broadcast_action_specs,
|
|
394
402
|
* });
|
|
395
403
|
* const client = await harness.connect(keeper_identity());
|
|
396
|
-
* await broadcast.
|
|
397
|
-
* await client.wait_for(is_notification('
|
|
404
|
+
* await broadcast.zap_run_created({run_id: '...', ...});
|
|
405
|
+
* await client.wait_for(is_notification('zap_run_created'));
|
|
398
406
|
* ```
|
|
399
407
|
*/
|
|
400
408
|
export const build_broadcast_api = (options) => {
|