@fuzdev/fuz_app 0.146.1 → 0.148.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 +6 -4
- package/dist/actions/action_codegen.d.ts +15 -1
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +23 -2
- package/dist/actions/perform_action.js +1 -1
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/bootstrap_account.d.ts +2 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +17 -6
- package/dist/auth/cell_action_specs.d.ts +3 -1
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +5 -5
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +2 -3
- package/dist/auth/cell_data_schema.d.ts +1 -1
- package/dist/auth/cell_data_schema.js +1 -1
- package/dist/auth/cell_field_action_specs.d.ts +4 -3
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_field_action_specs.js +5 -6
- package/dist/auth/cell_field_actions.d.ts.map +1 -1
- package/dist/auth/cell_field_actions.js +8 -1
- package/dist/auth/cell_item_action_specs.d.ts +4 -2
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_item_action_specs.js +5 -5
- package/dist/auth/cell_item_actions.d.ts.map +1 -1
- package/dist/auth/cell_item_actions.js +8 -1
- package/dist/auth/keyring.d.ts +35 -38
- package/dist/auth/keyring.d.ts.map +1 -1
- package/dist/auth/keyring.js +43 -50
- package/dist/auth/password_argon2.d.ts.map +1 -1
- package/dist/auth/password_argon2.js +11 -8
- package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +3 -1
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +5 -2
- package/dist/db/CLAUDE.md +18 -16
- package/dist/db/cell_queries.d.ts +3 -2
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +3 -2
- package/dist/db/fact_disk_storage.d.ts +15 -15
- package/dist/db/fact_disk_storage.d.ts.map +1 -1
- package/dist/db/fact_disk_storage.js +22 -28
- package/dist/db/fact_queries.d.ts +2 -3
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +2 -3
- package/dist/db/fact_store.d.ts +20 -18
- package/dist/db/fact_store.d.ts.map +1 -1
- package/dist/db/fact_store.js +30 -44
- package/dist/db/file_fact_url.d.ts +19 -4
- package/dist/db/file_fact_url.d.ts.map +1 -1
- package/dist/db/file_fact_url.js +19 -4
- package/dist/primitive_schemas.d.ts +1 -1
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +2 -1
- package/dist/schema_meta.d.ts +20 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +7 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +2 -2
- package/dist/server/serve_fact_route.d.ts +9 -5
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +15 -7
- package/dist/testing/CLAUDE.md +737 -114
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +73 -5
- package/dist/testing/adversarial_input.d.ts +0 -47
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +4 -340
- package/dist/testing/adversarial_input_cases.d.ts +64 -0
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
- package/dist/testing/adversarial_input_cases.js +348 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +24 -20
- package/dist/testing/audit_drift_guard.d.ts +27 -1
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +50 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +29 -12
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +7 -25
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
- package/dist/testing/cross_backend/app_settings.js +4 -6
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
- package/dist/testing/cross_backend/backend_config.d.ts +2 -1
- package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +6 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +26 -44
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
- package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
- package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
- package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +55 -59
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +20 -23
- package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
- package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
- package/dist/testing/cross_backend/cargo_binary.js +148 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +57 -1
- package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +9 -487
- package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +22 -12
- package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_moderate.js +511 -0
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +40 -19
- package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +29 -1
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
- package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +297 -65
- package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
- package/dist/testing/cross_backend/cookie_attributes.js +3 -2
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.js +32 -31
- package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
- package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
- package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +97 -91
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +2 -2
- package/dist/testing/cross_backend/differential.d.ts +126 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential.js +201 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential_normalize.js +153 -0
- package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +41 -7
- package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +55 -1
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +6 -5
- package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +10 -7
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +13 -18
- package/dist/testing/cross_backend/origin.js +1 -1
- package/dist/testing/cross_backend/origin_conformance.js +1 -1
- package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin_scope.js +13 -12
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
- package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
- package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
- package/dist/testing/cross_backend/postgres_database.js +140 -0
- package/dist/testing/cross_backend/provided_context.d.ts +28 -0
- package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
- package/dist/testing/cross_backend/provided_context.js +35 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/query_shape.js +10 -12
- package/dist/testing/cross_backend/ready.js +2 -2
- package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
- package/dist/testing/cross_backend/revocation_drivers.js +1 -1
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
- 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 +203 -111
- package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_participation.js +32 -21
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -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 +27 -18
- package/dist/testing/cross_backend/session_cap.d.ts +9 -8
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +9 -4
- package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
- package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
- package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
- package/dist/testing/cross_backend/setup.d.ts +56 -54
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +29 -29
- package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
- package/dist/testing/cross_backend/simple_request.js +4 -4
- package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
- package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
- package/dist/testing/cross_backend/spawn_backend.js +219 -60
- package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_suites.js +626 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +3 -4
- package/dist/testing/cross_backend/standard.d.ts +18 -1
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +2 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +7 -10
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +10 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +21 -12
- package/dist/testing/cross_backend/testing_server_bun.js +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +1 -1
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +9 -13
- package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
- package/dist/testing/cross_backend/transport_composition.js +81 -51
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -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 +17 -10
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
- package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
- package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_endpoint.js +54 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
- package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_role_gated.js +12 -21
- package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +29 -34
- package/dist/testing/db_entities.d.ts +16 -3
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +28 -5
- package/dist/testing/integration_helpers.d.ts +61 -2
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +75 -18
- package/dist/testing/round_trip.d.ts +17 -0
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +67 -24
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +13 -138
- package/dist/testing/rpc_envelope_cases.d.ts +111 -0
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
- package/dist/testing/rpc_envelope_cases.js +240 -0
- package/dist/testing/rpc_helpers.d.ts +31 -0
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +35 -14
- package/dist/testing/rpc_round_trip.d.ts +62 -6
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +151 -39
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +22 -2
- package/dist/testing/standard.d.ts +26 -0
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +4 -1
- package/dist/testing/transports/raw_http.d.ts +21 -0
- package/dist/testing/transports/raw_http.d.ts.map +1 -1
- package/dist/testing/transports/raw_http.js +30 -0
- package/package.json +10 -8
- package/dist/testing/cross_backend/actor_search.d.ts +0 -6
- package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
- package/dist/testing/cross_backend/actor_search.js +0 -90
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a spawned backend's WebSocket endpoint is, and how the cross suites
|
|
4
|
+
* open a socket on it as one account's session.
|
|
5
|
+
*
|
|
6
|
+
* The socket-driving cross suites take `WsEndpointTarget` as their endpoint
|
|
7
|
+
* options (flat, or nested as `ws` on a suite whose socket cases are
|
|
8
|
+
* optional), and open a socket on a fixture account's session through
|
|
9
|
+
* `open_session_ws`, so the cookie read, the missing-cookie assertion and the
|
|
10
|
+
* transport choice are written once. Sockets on another credential — a daemon
|
|
11
|
+
* token, a bearer, a session a case logged in on its own transport — and the
|
|
12
|
+
* raw-socket cases open their own way.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { assert } from 'vitest';
|
|
17
|
+
import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
|
|
18
|
+
/**
|
|
19
|
+
* The account's session cookie (`<name>=<value>`), asserted present.
|
|
20
|
+
*
|
|
21
|
+
* @param account - the account whose session headers carry the cookie
|
|
22
|
+
* @returns the `Cookie` header value
|
|
23
|
+
*/
|
|
24
|
+
export const to_session_cookie = (account) => {
|
|
25
|
+
const { cookie } = account.create_session_headers();
|
|
26
|
+
assert.ok(cookie, 'expected a session cookie for the WS upgrade');
|
|
27
|
+
return cookie;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Open a real-upgrade socket on `target` as `account`'s session: its session
|
|
31
|
+
* cookie on the upgrade, `target.origin` (or `base_url`) as the `Origin`.
|
|
32
|
+
*
|
|
33
|
+
* The cookie is the static one `account.create_session_headers` carries, not
|
|
34
|
+
* whatever `fixture.transport`'s jar holds now, so a case that rotates the
|
|
35
|
+
* session through `fixture.transport` first upgrades on the stale cookie.
|
|
36
|
+
*
|
|
37
|
+
* @param target - the backend's WebSocket endpoint
|
|
38
|
+
* @param account - the account whose session the socket upgrades on
|
|
39
|
+
* @param options - whether to wait for admission, plus transport extras
|
|
40
|
+
* @returns the open socket
|
|
41
|
+
* @throws Error if the account carries no session cookie, or the upgrade (or
|
|
42
|
+
* the admission heartbeat) fails
|
|
43
|
+
*/
|
|
44
|
+
export const open_session_ws = (target, account, options) => {
|
|
45
|
+
const { admitted, ...transport_options } = options;
|
|
46
|
+
const open = admitted ? create_admitted_ws_transport : create_ws_transport;
|
|
47
|
+
return open({
|
|
48
|
+
base_url: target.base_url,
|
|
49
|
+
ws_path: target.ws_path,
|
|
50
|
+
cookies: [to_session_cookie(account)],
|
|
51
|
+
origin: target.origin ?? target.base_url,
|
|
52
|
+
...transport_options
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { type BackendCapabilities } from './capabilities.js';
|
|
3
3
|
import type { SetupTest } from './setup.js';
|
|
4
|
+
import { type WsEndpointTarget } from './ws_endpoint.js';
|
|
4
5
|
/** Configuration for `describe_ws_inbound_dispatch_cross_tests`. */
|
|
5
|
-
export interface WsInboundDispatchCrossTestOptions {
|
|
6
|
+
export interface WsInboundDispatchCrossTestOptions extends WsEndpointTarget {
|
|
6
7
|
/** Per-test fixture producer (`default_cross_process_setup(handle)`). */
|
|
7
8
|
readonly setup_test: SetupTest;
|
|
8
9
|
/** Backend capability flags; every case gates on `capabilities.peer_request`. */
|
|
9
10
|
readonly capabilities: BackendCapabilities;
|
|
10
|
-
/** Base URL the backend is reachable at (e.g. `http://localhost:1177`). */
|
|
11
|
-
readonly base_url: string;
|
|
12
|
-
/** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
|
|
13
|
-
readonly ws_path: string;
|
|
14
11
|
}
|
|
15
12
|
/**
|
|
16
13
|
* Register the duplicate-id and in-flight-ceiling cases. Gated on
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_inbound_dispatch.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_inbound_dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_inbound_dispatch.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_inbound_dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAiB1E,oEAAoE;AACpE,MAAM,WAAW,iCAAkC,SAAQ,gBAAgB;IAC1E,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C;AAiED;;;GAGG;AACH,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAsGF,CAAC"}
|
|
@@ -30,9 +30,9 @@ import '../assert_dev_env.js';
|
|
|
30
30
|
*/
|
|
31
31
|
import { assert, describe } from 'vitest';
|
|
32
32
|
import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
|
|
33
|
-
import { create_ws_transport } from '../transports/ws_transport.js';
|
|
34
33
|
import { is_response_for } from '../transports/ws_client.js';
|
|
35
34
|
import { test_if } from './capabilities.js';
|
|
35
|
+
import { open_session_ws } from './ws_endpoint.js';
|
|
36
36
|
/** `data.reason` of the duplicate-id refusal. */
|
|
37
37
|
const REASON_DUPLICATE_REQUEST_ID = 'duplicate_request_id';
|
|
38
38
|
/** The per-socket in-flight ceiling on both spines. */
|
|
@@ -92,18 +92,12 @@ const is_error_for = (id) => (msg) => is_response_for(id)(msg) && 'error' in msg
|
|
|
92
92
|
* `capabilities.peer_request` (the cases hold requests with `peer/ping`).
|
|
93
93
|
*/
|
|
94
94
|
export const describe_ws_inbound_dispatch_cross_tests = (options) => {
|
|
95
|
-
const { setup_test, capabilities
|
|
96
|
-
const open_ws = (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
cookies: [cookie],
|
|
102
|
-
origin: base_url,
|
|
103
|
-
on_request,
|
|
104
|
-
default_timeout_ms: FRAME_TIMEOUT_MS
|
|
105
|
-
});
|
|
106
|
-
};
|
|
95
|
+
const { setup_test, capabilities } = options;
|
|
96
|
+
const open_ws = (fixture, on_request) => open_session_ws(options, fixture, {
|
|
97
|
+
admitted: false,
|
|
98
|
+
on_request,
|
|
99
|
+
default_timeout_ms: FRAME_TIMEOUT_MS
|
|
100
|
+
});
|
|
107
101
|
const send_ping = (ws, id, nonce) => ws.send(create_jsonrpc_request(PEER_PING_METHOD, { nonce }, id));
|
|
108
102
|
const send_heartbeat = (ws, id) => ws.send(create_jsonrpc_request(HEARTBEAT_METHOD, undefined, id));
|
|
109
103
|
const assert_duplicate_refusal = (frame, id) => {
|
|
@@ -117,7 +111,7 @@ export const describe_ws_inbound_dispatch_cross_tests = (options) => {
|
|
|
117
111
|
test_if(capabilities.peer_request, 'a request reusing a live id is refused with that id; the first keeps it, then the id frees', async () => {
|
|
118
112
|
const fixture = await setup_test();
|
|
119
113
|
const { responder, release, pinged } = create_holding_responder();
|
|
120
|
-
const ws = await open_ws(fixture
|
|
114
|
+
const ws = await open_ws(fixture, responder);
|
|
121
115
|
try {
|
|
122
116
|
const id = 'held';
|
|
123
117
|
await send_ping(ws, id, 41);
|
|
@@ -147,7 +141,7 @@ export const describe_ws_inbound_dispatch_cross_tests = (options) => {
|
|
|
147
141
|
test_if(capabilities.peer_request, 'past the in-flight ceiling a request is shed with queue_overflow and the socket keeps serving', async () => {
|
|
148
142
|
const fixture = await setup_test();
|
|
149
143
|
const { responder, release } = create_holding_responder();
|
|
150
|
-
const ws = await open_ws(fixture
|
|
144
|
+
const ws = await open_ws(fixture, responder);
|
|
151
145
|
try {
|
|
152
146
|
for (let n = 0; n < MAX_INBOUND_DISPATCHES; n++) {
|
|
153
147
|
await send_ping(ws, n, n);
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { type BackendCapabilities } from './capabilities.js';
|
|
3
3
|
import type { SetupTest } from './setup.js';
|
|
4
|
+
import { type WsEndpointTarget } from './ws_endpoint.js';
|
|
4
5
|
/** Options for the outbound-overflow parity suite. */
|
|
5
|
-
export interface WsOutboundOverflowCrossTestOptions {
|
|
6
|
+
export interface WsOutboundOverflowCrossTestOptions extends WsEndpointTarget {
|
|
6
7
|
/** Per-test fixture producer (`default_cross_process_setup(handle)`). */
|
|
7
8
|
readonly setup_test: SetupTest;
|
|
8
9
|
/** Backend capability flags; every WebSocket case gates on `capabilities.ws`. */
|
|
9
10
|
readonly capabilities: BackendCapabilities;
|
|
10
|
-
/** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
|
|
11
|
-
readonly base_url: string;
|
|
12
|
-
/** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
|
|
13
|
-
readonly ws_path: string;
|
|
14
|
-
/** Origin for the session socket's upgrade. Defaults to `base_url`. */
|
|
15
|
-
readonly origin?: string;
|
|
16
11
|
/**
|
|
17
12
|
* The backend's RPC endpoint path, for the over-HTTP refusal. Omitted, that
|
|
18
13
|
* case is skipped.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_outbound_overflow.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_outbound_overflow.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_outbound_overflow.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_outbound_overflow.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAoF1E,sDAAsD;AACtD,MAAM,WAAW,kCAAmC,SAAQ,gBAAgB;IAC3E,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,kCAAkC,KACzC,IAqNF,CAAC"}
|
|
@@ -62,7 +62,8 @@ import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
|
|
|
62
62
|
import { create_admitted_ws_transport } from '../transports/ws_transport.js';
|
|
63
63
|
import { is_notification, is_response_for } from '../transports/ws_client.js';
|
|
64
64
|
import { test_if } from './capabilities.js';
|
|
65
|
-
import { cross_rpc_call } from './
|
|
65
|
+
import { cross_rpc_call } from './cross_rpc.js';
|
|
66
|
+
import { open_session_ws } from './ws_endpoint.js';
|
|
66
67
|
/** The close code both spines send on an outbound overflow. */
|
|
67
68
|
const WS_CLOSE_OUTBOUND_OVERFLOW = 4005;
|
|
68
69
|
/** The close reason both spines send with it. */
|
|
@@ -139,7 +140,7 @@ const CLOSE_TIMEOUT_MS = 15_000;
|
|
|
139
140
|
* connection is unaffected.
|
|
140
141
|
*/
|
|
141
142
|
export const describe_ws_outbound_overflow_cross_tests = (options) => {
|
|
142
|
-
const { setup_test, capabilities, base_url, ws_path,
|
|
143
|
+
const { setup_test, capabilities, base_url, ws_path, rpc_path } = options;
|
|
143
144
|
/** A daemon-token socket the backend has admitted — the one that floods itself. */
|
|
144
145
|
const open_flooder = (fixture) => create_admitted_ws_transport({
|
|
145
146
|
base_url,
|
|
@@ -156,7 +157,7 @@ export const describe_ws_outbound_overflow_cross_tests = (options) => {
|
|
|
156
157
|
assert.deepStrictEqual(await client.request(id, HEARTBEAT_METHOD, {}), {}, message);
|
|
157
158
|
assert.strictEqual(client.close_code, null, message);
|
|
158
159
|
};
|
|
159
|
-
describe('websocket outbound overflow parity',
|
|
160
|
+
describe('websocket outbound overflow parity', () => {
|
|
160
161
|
test_if(capabilities.ws, 'a burst within the budget arrives in order, ahead of its response', async () => {
|
|
161
162
|
const fixture = await setup_test();
|
|
162
163
|
const client = await open_flooder(fixture);
|
|
@@ -206,12 +207,7 @@ export const describe_ws_outbound_overflow_cross_tests = (options) => {
|
|
|
206
207
|
test_if(capabilities.ws, 'a burst past the budget closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW and no response', async () => {
|
|
207
208
|
const fixture = await setup_test();
|
|
208
209
|
// a second connection on the same account (the keeper's session)
|
|
209
|
-
const bystander = await
|
|
210
|
-
base_url,
|
|
211
|
-
ws_path,
|
|
212
|
-
cookies: fixture.transport.cookies(),
|
|
213
|
-
origin
|
|
214
|
-
});
|
|
210
|
+
const bystander = await open_session_ws(options, fixture, { admitted: true });
|
|
215
211
|
const client = await open_flooder(fixture);
|
|
216
212
|
try {
|
|
217
213
|
await client.send(create_jsonrpc_request(FLOOD_METHOD, OVERFLOWS, 'overflows'));
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { type BackendCapabilities } from './capabilities.js';
|
|
3
3
|
import type { SetupTest } from './setup.js';
|
|
4
|
+
import { type WsEndpointTarget } from './ws_endpoint.js';
|
|
4
5
|
/** Configuration for `describe_ws_role_gated_cross_tests`. */
|
|
5
|
-
export interface WsRoleGatedCrossTestOptions {
|
|
6
|
+
export interface WsRoleGatedCrossTestOptions extends WsEndpointTarget {
|
|
7
|
+
/** The ungated WebSocket endpoint path (e.g. `/api/ws`). */
|
|
8
|
+
readonly ws_path: string;
|
|
6
9
|
/** Per-test fixture producer (`default_cross_process_setup(handle)`). */
|
|
7
10
|
readonly setup_test: SetupTest;
|
|
8
11
|
/** Backend capability flags; every case gates on `ws` and `ws_role_gated`. */
|
|
9
12
|
readonly capabilities: BackendCapabilities;
|
|
10
|
-
/** Base URL the backend is reachable at. */
|
|
11
|
-
readonly base_url: string;
|
|
12
|
-
/** The ungated WebSocket endpoint path (e.g. `/api/ws`). */
|
|
13
|
-
readonly ws_path: string;
|
|
14
13
|
/** The role-gated WebSocket endpoint path. */
|
|
15
14
|
readonly gated_ws_path: string;
|
|
16
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_role_gated.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_role_gated.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_role_gated.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_role_gated.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAMtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK1E,8DAA8D;AAC9D,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACpE,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,8CAA8C;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAAI,SAAS,2BAA2B,KAAG,IA+HzF,CAAC"}
|
|
@@ -31,10 +31,10 @@ import { ROLE_ADMIN } from '../../auth/role_schema.js';
|
|
|
31
31
|
import { ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD } from '../../auth/role_grant_offer_notifications.js';
|
|
32
32
|
import { rpc_call } from '../rpc_helpers.js';
|
|
33
33
|
import { is_notification_with } from '../transports/ws_client.js';
|
|
34
|
-
import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
|
|
35
34
|
import { test_if } from './capabilities.js';
|
|
36
35
|
import { create_active_role_grant, create_pending_role_grant_offer } from './role_grant_cross_helpers.js';
|
|
37
36
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
37
|
+
import { open_session_ws } from './ws_endpoint.js';
|
|
38
38
|
/** How long a socket that must stay open is watched for a close it must not get. */
|
|
39
39
|
const STAYS_OPEN_MS = 1500;
|
|
40
40
|
/**
|
|
@@ -49,30 +49,26 @@ const STAYS_OPEN_MS = 1500;
|
|
|
49
49
|
* it: each spine's own transport tests pin it.
|
|
50
50
|
*/
|
|
51
51
|
export const describe_ws_role_gated_cross_tests = (options) => {
|
|
52
|
-
const { setup_test, capabilities,
|
|
52
|
+
const { setup_test, capabilities, gated_ws_path, gated_roles, scoped_role, rpc_path = SPINE_RPC_PATH } = options;
|
|
53
53
|
const enabled = capabilities.ws && capabilities.ws_role_gated;
|
|
54
54
|
if (enabled && (!gated_roles.includes(ROLE_ADMIN) || !gated_roles.includes(scoped_role))) {
|
|
55
55
|
throw new Error(`describe_ws_role_gated_cross_tests: gated_roles must include ${ROLE_ADMIN} and ${scoped_role}`);
|
|
56
56
|
}
|
|
57
57
|
const rpc = (fixture, method, params, headers = fixture.create_session_headers()) => rpc_call({ app: fixture.transport, path: rpc_path, method, params, headers });
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
/** The role-gated endpoint, on the ungated one's backend and origin. */
|
|
59
|
+
const gated_target = {
|
|
60
|
+
base_url: options.base_url,
|
|
61
|
+
ws_path: gated_ws_path,
|
|
62
|
+
...(options.origin !== undefined && { origin: options.origin })
|
|
62
63
|
};
|
|
63
|
-
const open = (
|
|
64
|
-
base_url,
|
|
65
|
-
ws_path: path,
|
|
66
|
-
cookies: [cookie_of(account)],
|
|
67
|
-
origin: base_url
|
|
68
|
-
});
|
|
64
|
+
const open = (target, account) => open_session_ws(target, account, { admitted: true });
|
|
69
65
|
describe('role-gated websocket (cross-process)', () => {
|
|
70
66
|
test_if(enabled, 'revoking a global grant of a gating role closes the gated socket with WS_CLOSE_PERMISSION_REVOKED', async () => {
|
|
71
67
|
const fixture = await setup_test();
|
|
72
68
|
const target = await fixture.create_account({ roles: [ROLE_ADMIN] });
|
|
73
69
|
const role_grant_id = await create_active_role_grant(fixture, target, { rpc_path });
|
|
74
|
-
const gated = await open(
|
|
75
|
-
const ungated = await open(
|
|
70
|
+
const gated = await open(gated_target, target);
|
|
71
|
+
const ungated = await open(options, target);
|
|
76
72
|
try {
|
|
77
73
|
const revoked = await rpc(fixture, 'role_grant_revoke', {
|
|
78
74
|
actor_id: target.actor.id,
|
|
@@ -92,12 +88,7 @@ export const describe_ws_role_gated_cross_tests = (options) => {
|
|
|
92
88
|
assert.strictEqual(ungated.close_code, null);
|
|
93
89
|
assert.deepStrictEqual(await ungated.request(2, heartbeat_action_spec.method, {}), {});
|
|
94
90
|
// and the gate refuses the account a new gated socket
|
|
95
|
-
await assert_rejects(() =>
|
|
96
|
-
base_url,
|
|
97
|
-
ws_path: gated_ws_path,
|
|
98
|
-
cookies: [cookie_of(target)],
|
|
99
|
-
origin: base_url
|
|
100
|
-
}), /403/);
|
|
91
|
+
await assert_rejects(() => open_session_ws(gated_target, target, { admitted: false }), /403/);
|
|
101
92
|
}
|
|
102
93
|
finally {
|
|
103
94
|
await gated.close();
|
|
@@ -124,7 +115,7 @@ export const describe_ws_role_gated_cross_tests = (options) => {
|
|
|
124
115
|
scope_kind: 'space',
|
|
125
116
|
scope_id: '33333333-3333-4333-8333-333333333333'
|
|
126
117
|
});
|
|
127
|
-
const gated = await open(
|
|
118
|
+
const gated = await open(gated_target, target);
|
|
128
119
|
try {
|
|
129
120
|
const revoked = await rpc(fixture, 'role_grant_revoke', {
|
|
130
121
|
actor_id: target.actor.id,
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { type BackendCapabilities } from './capabilities.js';
|
|
3
3
|
import type { SetupTest } from './setup.js';
|
|
4
|
+
import { type WsEndpointTarget } from './ws_endpoint.js';
|
|
4
5
|
/** Configuration for `describe_cross_process_ws_tests`. */
|
|
5
|
-
export interface CrossProcessWsTestOptions {
|
|
6
|
+
export interface CrossProcessWsTestOptions extends WsEndpointTarget {
|
|
6
7
|
/**
|
|
7
8
|
* Per-test fixture producer (`default_cross_process_setup(handle)`).
|
|
8
|
-
* The authenticated
|
|
9
|
-
*
|
|
9
|
+
* The authenticated cases upgrade on the fresh-per-test keeper's session
|
|
10
|
+
* cookie (`open_session_ws`).
|
|
10
11
|
*/
|
|
11
12
|
readonly setup_test: SetupTest;
|
|
12
13
|
/** Backend capability flags; every case gates on `capabilities.ws`. */
|
|
13
14
|
readonly capabilities: BackendCapabilities;
|
|
14
|
-
/** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
|
|
15
|
-
readonly base_url: string;
|
|
16
|
-
/** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
|
|
17
|
-
readonly ws_path: string;
|
|
18
|
-
/** Origin for the authenticated upgrade. Defaults to `base_url`. */
|
|
19
|
-
readonly origin?: string;
|
|
20
15
|
/**
|
|
21
16
|
* RPC endpoint path (e.g. `/api/rpc`) used by the close-on-revoke case to
|
|
22
17
|
* fire `account_session_revoke_all` over the keeper's session channel.
|
|
@@ -41,6 +36,14 @@ export interface CrossProcessWsTestOptions {
|
|
|
41
36
|
* cap disabled) skips the case.
|
|
42
37
|
*/
|
|
43
38
|
readonly max_tokens?: number | null;
|
|
39
|
+
/**
|
|
40
|
+
* Roles each second account whose socket a revocation case watches is created
|
|
41
|
+
* with, for a
|
|
42
|
+
* backend whose WS upgrade requires a role (an admin-gated `/api/ws`
|
|
43
|
+
* refuses a roleless account's upgrade). Each must be admin-grantable — the
|
|
44
|
+
* fixture offers it. Default none.
|
|
45
|
+
*/
|
|
46
|
+
readonly ws_account_roles?: ReadonlyArray<string>;
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
46
49
|
* Register the cross-process WS round-trip suite over a real upgrade: authed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsH9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAItE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAKzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK1E,2DAA2D;AAC3D,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAClE;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IAsdpF,CAAC"}
|
|
@@ -85,16 +85,19 @@ import { heartbeat_action_spec } from '../../actions/heartbeat.js';
|
|
|
85
85
|
import { DEFAULT_WS_MAX_MESSAGE_BYTES, WS_CLOSE_MESSAGE_TOO_BIG, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON } from '../../actions/transports.js';
|
|
86
86
|
import { account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_revoke_action_spec, TokenCreateOutput, TokenRevokeOutput } from '../../auth/account_action_specs.js';
|
|
87
87
|
import { DEFAULT_MAX_TOKENS } from '../../auth/account_route_schema.js';
|
|
88
|
-
import { account_delete_action_spec
|
|
88
|
+
import { account_delete_action_spec } from '../../auth/admin_action_specs.js';
|
|
89
89
|
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
90
90
|
import { is_response_for } from '../transports/ws_client.js';
|
|
91
91
|
import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
|
|
92
92
|
import { connect_raw_ws, is_raw_ws_close, RAW_WS_OPCODE_TEXT } from '../transports/ws_raw_client.js';
|
|
93
93
|
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
94
94
|
import { DEFAULT_TEST_PASSWORD } from '../test_credentials.js';
|
|
95
|
+
import { list_audit_events } from './audit_cross_helpers.js';
|
|
95
96
|
import { test_if } from './capabilities.js';
|
|
96
|
-
import { cross_rpc_call, expect_output } from './
|
|
97
|
+
import { cross_rpc_call, expect_output } from './cross_rpc.js';
|
|
97
98
|
import { create_account_revocations } from './revocation_drivers.js';
|
|
99
|
+
import { SPINE_ACCOUNT_LOGOUT_PATH, SPINE_ACCOUNT_PASSWORD_PATH } from './spine_surface_constants.js';
|
|
100
|
+
import { open_session_ws } from './ws_endpoint.js';
|
|
98
101
|
/** Origin guaranteed to fail the `http://localhost:*` allowlist the test backends run with. */
|
|
99
102
|
const DISALLOWED_ORIGIN = 'http://disallowed.example';
|
|
100
103
|
/**
|
|
@@ -109,11 +112,11 @@ const DISALLOWED_ORIGIN = 'http://disallowed.example';
|
|
|
109
112
|
* audit row read back after `_testing_drain_effects`.
|
|
110
113
|
*/
|
|
111
114
|
export const describe_cross_process_ws_tests = (options) => {
|
|
112
|
-
const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES, logout_path =
|
|
115
|
+
const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES, logout_path = SPINE_ACCOUNT_LOGOUT_PATH, password_path = SPINE_ACCOUNT_PASSWORD_PATH, ws_account_roles = [] } = options;
|
|
113
116
|
const max_tokens = options.max_tokens === undefined ? DEFAULT_MAX_TOKENS : options.max_tokens;
|
|
114
117
|
const open_authed = async () => {
|
|
115
118
|
const fixture = await setup_test();
|
|
116
|
-
return
|
|
119
|
+
return open_session_ws(options, fixture, { admitted: false });
|
|
117
120
|
};
|
|
118
121
|
describe('cross-process websocket', () => {
|
|
119
122
|
test_if(capabilities.ws, 'authenticated upgrade round-trips heartbeat', async () => {
|
|
@@ -260,11 +263,7 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
260
263
|
origin: null,
|
|
261
264
|
headers: { Authorization: `Bearer ${token}` }
|
|
262
265
|
});
|
|
263
|
-
const open_session = (account) => {
|
|
264
|
-
const { cookie } = account.create_session_headers();
|
|
265
|
-
assert.ok(cookie, 'expected a session cookie');
|
|
266
|
-
return create_admitted_ws_transport({ base_url, ws_path, cookies: [cookie], origin });
|
|
267
|
-
};
|
|
266
|
+
const open_session = (account) => open_session_ws(options, account, { admitted: true });
|
|
268
267
|
/**
|
|
269
268
|
* Open each socket in turn, run `fn` over them, and close every socket
|
|
270
269
|
* that opened — also when a later open fails, so a refused upgrade leaves
|
|
@@ -299,12 +298,7 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
299
298
|
// account actions on the RPC endpoint).
|
|
300
299
|
test_if(capabilities.ws && rpc_path !== undefined, 'session revocation closes the live socket', async () => {
|
|
301
300
|
const fixture = await setup_test();
|
|
302
|
-
const client = await
|
|
303
|
-
base_url,
|
|
304
|
-
ws_path,
|
|
305
|
-
cookies: fixture.transport.cookies(),
|
|
306
|
-
origin
|
|
307
|
-
});
|
|
301
|
+
const client = await open_session_ws(options, fixture, { admitted: false });
|
|
308
302
|
try {
|
|
309
303
|
// Confirm the socket dispatches before revoking, so a failed
|
|
310
304
|
// close assertion can't be confused with a dead connection.
|
|
@@ -323,10 +317,11 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
323
317
|
// committed. The keeper deletes a second account whose socket is open.
|
|
324
318
|
test_if(capabilities.ws && capabilities.account_lifecycle && rpc_path !== undefined, 'an account delete closes the live socket of the deleted account', async () => {
|
|
325
319
|
const fixture = await setup_test();
|
|
326
|
-
const target = await fixture.create_account({
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
320
|
+
const target = await fixture.create_account({
|
|
321
|
+
username: 'ws_delete_target',
|
|
322
|
+
roles: [...ws_account_roles]
|
|
323
|
+
});
|
|
324
|
+
const client = await open_session_ws(options, target, { admitted: false });
|
|
330
325
|
try {
|
|
331
326
|
// admitted and dispatching before the delete, so a failed close
|
|
332
327
|
// assertion can't be confused with a dead connection
|
|
@@ -350,7 +345,10 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
350
345
|
// others: a socket on the account's other token stays open.
|
|
351
346
|
test_if(capabilities.ws && rpc_path !== undefined, "account_token_revoke closes the revoked token's bearer socket only", async () => {
|
|
352
347
|
const fixture = await setup_test();
|
|
353
|
-
const target = await fixture.create_account({
|
|
348
|
+
const target = await fixture.create_account({
|
|
349
|
+
username: 'ws_token_revoke',
|
|
350
|
+
roles: [...ws_account_roles]
|
|
351
|
+
});
|
|
354
352
|
const minted = await mint_token(fixture, target, 'ws revoke probe');
|
|
355
353
|
await with_sockets([() => open_bearer(minted.token), () => open_bearer(target.api_token)], async (revoked, other) => {
|
|
356
354
|
const res = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, account_token_revoke_action_spec.method, { token_id: minted.id }, target.create_session_headers()), TokenRevokeOutput);
|
|
@@ -377,7 +375,8 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
377
375
|
const fixture = await setup_test();
|
|
378
376
|
const target = await fixture.create_account({
|
|
379
377
|
username: `ws_revoke_${slug}`,
|
|
380
|
-
password_value: DEFAULT_TEST_PASSWORD
|
|
378
|
+
password_value: DEFAULT_TEST_PASSWORD,
|
|
379
|
+
roles: [...ws_account_roles]
|
|
381
380
|
});
|
|
382
381
|
await with_sockets([() => open_bearer(target.api_token), () => open_session(target)], async (bearer, session) => {
|
|
383
382
|
await revoke(fixture, target);
|
|
@@ -391,7 +390,10 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
391
390
|
// before it, and its socket closes once the evicting mint commits.
|
|
392
391
|
test_if(capabilities.ws && rpc_path !== undefined && max_tokens !== null, 'a token the cap evicts has its bearer socket closed', async () => {
|
|
393
392
|
const fixture = await setup_test();
|
|
394
|
-
const target = await fixture.create_account({
|
|
393
|
+
const target = await fixture.create_account({
|
|
394
|
+
username: 'ws_token_cap',
|
|
395
|
+
roles: [...ws_account_roles]
|
|
396
|
+
});
|
|
395
397
|
const watched = await mint_token(fixture, target, 'ws cap victim');
|
|
396
398
|
const client = await open_bearer(watched.token);
|
|
397
399
|
try {
|
|
@@ -437,12 +439,7 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
437
439
|
// it returns, the row is visible on either spine.
|
|
438
440
|
test_if(capabilities.ws && capabilities.ws_account_actions && rpc_path !== undefined, "a WS mutation's audit row is visible after _testing_drain_effects", async () => {
|
|
439
441
|
const fixture = await setup_test();
|
|
440
|
-
const client = await
|
|
441
|
-
base_url,
|
|
442
|
-
ws_path,
|
|
443
|
-
cookies: fixture.transport.cookies(),
|
|
444
|
-
origin
|
|
445
|
-
});
|
|
442
|
+
const client = await open_session_ws(options, fixture, { admitted: false });
|
|
446
443
|
try {
|
|
447
444
|
await client.request(1, account_token_create_action_spec.method, {
|
|
448
445
|
name: 'ws drain probe',
|
|
@@ -453,14 +450,12 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
453
450
|
finally {
|
|
454
451
|
await client.close();
|
|
455
452
|
}
|
|
456
|
-
const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
|
|
457
|
-
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
458
453
|
// `_testing_reset` wiped audit_log at setup, so the mint is the only
|
|
459
454
|
// token_create event
|
|
460
|
-
const
|
|
461
|
-
assert.strictEqual(
|
|
462
|
-
assert.strictEqual(
|
|
463
|
-
assert.strictEqual(
|
|
455
|
+
const events = await list_audit_events(fixture, { event_type: 'token_create' }, rpc_path);
|
|
456
|
+
assert.strictEqual(events.length, 1, JSON.stringify(events));
|
|
457
|
+
assert.strictEqual(events[0].outcome, 'success');
|
|
458
|
+
assert.strictEqual(events[0].account_id, fixture.account.id);
|
|
464
459
|
});
|
|
465
460
|
});
|
|
466
461
|
};
|
|
@@ -43,9 +43,22 @@ export declare const create_test_role_grant_direct: (db: Db, input: CreateRoleGr
|
|
|
43
43
|
/**
|
|
44
44
|
* Add a second `actor` to an existing test account — the multi-actor edge.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* Its `created_at` is read from the wall clock (`clock_timestamp()`) rather
|
|
47
|
+
* than the transaction-fixed `NOW()` default the production
|
|
48
|
+
* `query_create_actor` relies on. Actors listed for an account sort by
|
|
49
|
+
* `created_at ASC, id ASC`, so an extra actor seeded in the transaction that
|
|
50
|
+
* created the account's first one would share its `created_at` and order by
|
|
51
|
+
* random id, and the multi-actor `actor_required` `available` list would come
|
|
52
|
+
* out in a random order. Seeded through here, the first actor sorts first and
|
|
53
|
+
* the extras in insert order. The bootstrap-cradle seeding of `extra_actors`
|
|
54
|
+
* uses it — the `_testing_reset` action and the in-process setup — as the Rust
|
|
55
|
+
* reset seeds them with `query_create_actor_with_clock_timestamp`.
|
|
56
|
+
*
|
|
57
|
+
* @param db - the database, or the transaction the account was seeded in
|
|
58
|
+
* @param account_id - the account the actor joins
|
|
59
|
+
* @param name - the actor's name
|
|
60
|
+
* @returns the created actor
|
|
61
|
+
* @mutates `actor` table - inserts the new row
|
|
49
62
|
*/
|
|
50
63
|
export declare const create_test_extra_actor: (db: Db, account_id: string, name: string) => Promise<Actor>;
|
|
51
64
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAA2C,CAAC;AAEhE
|
|
1
|
+
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAA2C,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GACnC,IAAI,EAAE,EACN,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAWf,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAU,IAAI,EAAE,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CACvC,CAAC"}
|
|
@@ -16,7 +16,7 @@ import './assert_dev_env.js';
|
|
|
16
16
|
*
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
|
-
import { query_create_account_with_actor,
|
|
19
|
+
import { query_create_account_with_actor, query_actor_by_id, query_actor_soft_delete } from '../auth/account_queries.js';
|
|
20
20
|
import { query_create_role_grant } from '../auth/role_grant_queries.js';
|
|
21
21
|
/**
|
|
22
22
|
* Create an `account` + `actor` row pair in the database for tests.
|
|
@@ -52,11 +52,34 @@ export const create_test_role_grant_direct = async (db, input) => query_create_r
|
|
|
52
52
|
/**
|
|
53
53
|
* Add a second `actor` to an existing test account — the multi-actor edge.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* Its `created_at` is read from the wall clock (`clock_timestamp()`) rather
|
|
56
|
+
* than the transaction-fixed `NOW()` default the production
|
|
57
|
+
* `query_create_actor` relies on. Actors listed for an account sort by
|
|
58
|
+
* `created_at ASC, id ASC`, so an extra actor seeded in the transaction that
|
|
59
|
+
* created the account's first one would share its `created_at` and order by
|
|
60
|
+
* random id, and the multi-actor `actor_required` `available` list would come
|
|
61
|
+
* out in a random order. Seeded through here, the first actor sorts first and
|
|
62
|
+
* the extras in insert order. The bootstrap-cradle seeding of `extra_actors`
|
|
63
|
+
* uses it — the `_testing_reset` action and the in-process setup — as the Rust
|
|
64
|
+
* reset seeds them with `query_create_actor_with_clock_timestamp`.
|
|
65
|
+
*
|
|
66
|
+
* @param db - the database, or the transaction the account was seeded in
|
|
67
|
+
* @param account_id - the account the actor joins
|
|
68
|
+
* @param name - the actor's name
|
|
69
|
+
* @returns the created actor
|
|
70
|
+
* @mutates `actor` table - inserts the new row
|
|
58
71
|
*/
|
|
59
|
-
export const create_test_extra_actor = async (db, account_id, name) =>
|
|
72
|
+
export const create_test_extra_actor = async (db, account_id, name) => {
|
|
73
|
+
// two steps: the actor projection's timestamp expression is private to
|
|
74
|
+
// `account_queries.ts`, so the row is read back through `query_actor_by_id`
|
|
75
|
+
const inserted = await db.query_one(`INSERT INTO actor (account_id, name, created_at) VALUES ($1, $2, clock_timestamp()) RETURNING id`, [account_id, name]);
|
|
76
|
+
if (!inserted)
|
|
77
|
+
throw new Error('create_test_extra_actor: INSERT returned no row');
|
|
78
|
+
const actor = await query_actor_by_id({ db }, inserted.id);
|
|
79
|
+
if (!actor)
|
|
80
|
+
throw new Error('create_test_extra_actor: inserted actor not found');
|
|
81
|
+
return actor;
|
|
82
|
+
};
|
|
60
83
|
/**
|
|
61
84
|
* Soft-delete (tombstone) one actor via the production `query_actor_soft_delete`
|
|
62
85
|
* — returns `true` when an active row flipped. The TS twin of the Rust
|