@fuzdev/fuz_app 0.147.0 → 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/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/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/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/testing/CLAUDE.md +720 -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/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/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.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +27 -17
- 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.js +1 -1
- 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 +3 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +8 -4
- 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 +6 -4
- 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
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
3
|
-
import {
|
|
3
|
+
import { create_test_extra_actor } from '../db_entities.js';
|
|
4
4
|
import { create_test_app, create_test_account_with_credentials, mint_test_session } from '../app_server.js';
|
|
5
5
|
import { create_test_app_surface_spec } from '../stubs.js';
|
|
6
6
|
import { http_transport } from '../rpc_helpers.js';
|
|
7
7
|
import { in_process_capabilities } from './capabilities.js';
|
|
8
|
-
import {
|
|
8
|
+
import { EXPIRED_SESSION_OFFSET_SECONDS, to_test_account } from './setup.js';
|
|
9
9
|
/**
|
|
10
10
|
* Wrap a Hono-style app into a `FetchTransport`-shaped object so the
|
|
11
|
-
* shared `
|
|
11
|
+
* shared `TestFixture.transport` type holds for both in-process and
|
|
12
12
|
* cross-process setups. In-process has no real cookie jar — the no-op
|
|
13
13
|
* `cookies()` returns `[]`; in-process tests build cookies via
|
|
14
14
|
* `fixture.create_session_headers()` instead.
|
|
@@ -60,15 +60,16 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
60
60
|
password_value: spec.password_value,
|
|
61
61
|
roles: [...spec.roles]
|
|
62
62
|
});
|
|
63
|
-
extra_accounts[spec.username] =
|
|
63
|
+
extra_accounts[spec.username] = to_test_account(seeded, cookie_name);
|
|
64
64
|
}
|
|
65
65
|
// Seed additional keeper actors directly against the same DB. Mirrors
|
|
66
66
|
// the cross-process `_testing_reset` `extra_actors` path; no production
|
|
67
67
|
// wire mints a second actor, so this bootstrap-cradle insert is the
|
|
68
|
-
// only way into a multi-actor keeper state.
|
|
68
|
+
// only way into a multi-actor keeper state. Seeded at the wall clock,
|
|
69
|
+
// like the reset, so they sort after the bootstrap actor.
|
|
69
70
|
const extra_actors = [];
|
|
70
71
|
for (const name of options.extra_actors ?? []) {
|
|
71
|
-
const seeded_actor = await
|
|
72
|
+
const seeded_actor = await create_test_extra_actor(test_app.backend.deps.db, test_app.backend.account.id, name);
|
|
72
73
|
extra_actors.push({ id: seeded_actor.id, name: seeded_actor.name });
|
|
73
74
|
}
|
|
74
75
|
return {
|
|
@@ -85,6 +86,7 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
85
86
|
create_account: test_app.create_account,
|
|
86
87
|
extra_accounts,
|
|
87
88
|
extra_actors,
|
|
89
|
+
reject_unknown_audit_metadata_keys: options.reject_unknown_audit_metadata_keys ?? false,
|
|
88
90
|
// Forge directly against the live backend's DB + keyring — no wire
|
|
89
91
|
// hop needed in-process.
|
|
90
92
|
mint_expired_session: async () => {
|
|
@@ -147,7 +149,8 @@ export const default_in_process_suite_options = (options) => ({
|
|
|
147
149
|
app_options: options.app_options,
|
|
148
150
|
roles: [ROLE_KEEPER, ...(options.extra_keeper_roles ?? [])],
|
|
149
151
|
extra_accounts: options.extra_accounts,
|
|
150
|
-
extra_actors: options.extra_actors
|
|
152
|
+
extra_actors: options.extra_actors,
|
|
153
|
+
reject_unknown_audit_metadata_keys: options.reject_unknown_audit_metadata_keys
|
|
151
154
|
}),
|
|
152
155
|
surface_source: options.surface_source ??
|
|
153
156
|
create_test_app_surface_spec({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login_security.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/login_security.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsF9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"login_security.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/login_security.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsF9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAO5C,mDAAmD;AACnD,MAAM,WAAW,6BAA6B;IAC7C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAmDD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IA4VF,CAAC"}
|
|
@@ -78,9 +78,8 @@ import { describe, test, assert } from 'vitest';
|
|
|
78
78
|
import { ERROR_RATE_LIMIT_EXCEEDED } from '../../http/error_schemas.js';
|
|
79
79
|
import { raw_http_exchange, raw_http_parse_response } from '../transports/raw_http.js';
|
|
80
80
|
import { default_login_account_rate_limit, default_login_ip_rate_limit } from '../../rate_limiter.js';
|
|
81
|
-
import {
|
|
82
|
-
import {
|
|
83
|
-
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
81
|
+
import { list_audit_events } from './audit_cross_helpers.js';
|
|
82
|
+
import { SPINE_ACCOUNT_LOGIN_PATH, SPINE_ACCOUNT_PASSWORD_PATH } from './spine_surface_constants.js';
|
|
84
83
|
/**
|
|
85
84
|
* RFC 5737 TEST-NET-3 (`203.0.113.0/24`) documentation addresses — guaranteed
|
|
86
85
|
* non-routable, so they never collide with a real client IP. One per bucket the
|
|
@@ -116,13 +115,13 @@ const XFF_V6_OTHER_64 = '2001:db8:e3:2::1';
|
|
|
116
115
|
const XFF_V6_PASSWORD_EXHAUST = '2001:db8:e3:5::1';
|
|
117
116
|
const XFF_V6_PASSWORD_SAME_64 = '2001:db8:e3:5:8000::2';
|
|
118
117
|
/** How long a raw exchange waits for the login's answer. */
|
|
119
|
-
const
|
|
118
|
+
const LOGIN_RAW_READ_TIMEOUT_MS = 5000;
|
|
120
119
|
/** A non-routable, non-existent identifier — login resolves to the not-found path (records the bucket, returns 401). */
|
|
121
120
|
const PROBE_PASSWORD = 'login_security_wrong_password';
|
|
122
121
|
export const describe_login_security_cross_tests = (options) => {
|
|
123
122
|
const { setup_test } = options;
|
|
124
|
-
const login_path = options.login_path ??
|
|
125
|
-
const password_path = options.password_path ??
|
|
123
|
+
const login_path = options.login_path ?? SPINE_ACCOUNT_LOGIN_PATH;
|
|
124
|
+
const password_path = options.password_path ?? SPINE_ACCOUNT_PASSWORD_PATH;
|
|
126
125
|
const concurrent_transactions = options.concurrent_transactions ?? false;
|
|
127
126
|
const base_url = options.base_url ?? null;
|
|
128
127
|
/** Per-IP cap shared by both impls (TS `default_login_ip_rate_limit`, Rust `DEFAULT_LOGIN_IP_RATE_LIMIT`). */
|
|
@@ -214,10 +213,8 @@ export const describe_login_security_cross_tests = (options) => {
|
|
|
214
213
|
// that stops keys on the (loopback) connection IP → 401.
|
|
215
214
|
const stopped = await attempt(fixture, `${XFF_MALFORMED_HOP}, ${XFF_PORT_SUFFIXED_HOP}`, 'probe_malformed_hop');
|
|
216
215
|
assert.strictEqual(stopped.status, 401, 'the walk must stop at the malformed hop, not reach the entry left of it');
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
const listed = expect_output(await cross_rpc_call(fixture.transport, SPINE_RPC_PATH, 'audit_log_list', { event_type: 'login', outcome: 'failure' }, fixture.create_session_headers()), AuditLogListOutput);
|
|
220
|
-
const ips = listed.events.map((e) => e.ip);
|
|
216
|
+
const events = await list_audit_events(fixture, { event_type: 'login', outcome: 'failure' });
|
|
217
|
+
const ips = events.map((e) => e.ip);
|
|
221
218
|
assert.ok(ips.some((ip) => ip === '127.0.0.1' || ip === '::1'), `the stopped walk must key on the loopback connection IP: ${JSON.stringify(ips)}`);
|
|
222
219
|
assert.ok(!ips.includes(XFF_PORT_SUFFIXED_HOP), `a malformed entry is never a client IP: ${JSON.stringify(ips)}`);
|
|
223
220
|
});
|
|
@@ -233,13 +230,11 @@ export const describe_login_security_cross_tests = (options) => {
|
|
|
233
230
|
// the next /64 has its own
|
|
234
231
|
const other = await attempt(fixture, XFF_V6_OTHER_64, 'probe_v6');
|
|
235
232
|
assert.strictEqual(other.status, 401, 'the next /64 must keep its own budget');
|
|
236
|
-
// The limiter keys the /64; the audit trail keeps the address.
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
const listed = expect_output(await cross_rpc_call(fixture.transport, SPINE_RPC_PATH, 'audit_log_list', { event_type: 'login', outcome: 'failure' }, fixture.create_session_headers()), AuditLogListOutput);
|
|
242
|
-
const ips = listed.events.map((e) => e.ip);
|
|
233
|
+
// The limiter keys the /64; the audit trail keeps the address. Read
|
|
234
|
+
// this test's login rows behind the drain barrier (`_testing_reset`
|
|
235
|
+
// wiped the log at setup).
|
|
236
|
+
const events = await list_audit_events(fixture, { event_type: 'login', outcome: 'failure' });
|
|
237
|
+
const ips = events.map((e) => e.ip);
|
|
243
238
|
assert.ok(ips.includes(XFF_V6_EXHAUST), `audit ips: ${JSON.stringify(ips)}`);
|
|
244
239
|
assert.ok(ips.includes(XFF_V6_OTHER_64), `audit ips: ${JSON.stringify(ips)}`);
|
|
245
240
|
assert.ok(ips.every((ip) => ip === null || !ip.includes('/')), `no audit row may carry a rate-limit key: ${JSON.stringify(ips)}`);
|
|
@@ -320,7 +315,7 @@ export const describe_login_security_cross_tests = (options) => {
|
|
|
320
315
|
`\r\n` +
|
|
321
316
|
body;
|
|
322
317
|
const { received } = await raw_http_exchange(base_url, request, {
|
|
323
|
-
read_timeout_ms:
|
|
318
|
+
read_timeout_ms: LOGIN_RAW_READ_TIMEOUT_MS,
|
|
324
319
|
until: 'response'
|
|
325
320
|
});
|
|
326
321
|
const response = raw_http_parse_response(received);
|
|
@@ -16,7 +16,7 @@ import '../assert_dev_env.js';
|
|
|
16
16
|
*
|
|
17
17
|
* Runs both legs via the shared `setup_test` protocol: the
|
|
18
18
|
* in-process leg (`auth/origin_parity.db.test.ts`, plain `gro test`) and the
|
|
19
|
-
* cross-process leg (`cross_backend/
|
|
19
|
+
* cross-process leg (`cross_backend/spine_suites.cross.test.ts`, the TS spine
|
|
20
20
|
* binaries + Rust `testing_spine_stub` over real HTTP). Origin middleware is
|
|
21
21
|
* on every spine, so the suite is ungated.
|
|
22
22
|
*
|
|
@@ -24,7 +24,7 @@ import { readFileSync } from 'node:fs';
|
|
|
24
24
|
import { assert, describe, test } from 'vitest';
|
|
25
25
|
import { z } from 'zod';
|
|
26
26
|
import { AllowedOriginsErrorKind } from '../../http/origin.js';
|
|
27
|
-
import { cross_rpc_call } from './
|
|
27
|
+
import { cross_rpc_call } from './cross_rpc.js';
|
|
28
28
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
29
29
|
import { TestingOriginCheckOutput, testing_origin_check_action_spec } from './testing_origin_check.js';
|
|
30
30
|
/** One expected refusal of a parse row. */
|
|
@@ -11,6 +11,12 @@ export interface OriginScopeCrossTestOptions extends CapabilityGatedCrossSuiteOp
|
|
|
11
11
|
* default) sends the whole body through the fixture's in-process transport.
|
|
12
12
|
*/
|
|
13
13
|
readonly base_url?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* The status the backend's fallback answers an unrouted `GET` with, once
|
|
16
|
+
* the origin check has passed it. Default `404`; a backend serving an SPA
|
|
17
|
+
* shell from its fallback passes `200`.
|
|
18
|
+
*/
|
|
19
|
+
readonly unrouted_status?: number;
|
|
14
20
|
}
|
|
15
21
|
export declare const describe_origin_scope_cross_tests: (options: OriginScopeCrossTestOptions) => void;
|
|
16
22
|
//# sourceMappingURL=origin_scope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin_scope.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"origin_scope.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin_scope.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,YAAY,CAAC;AASnE,iDAAiD;AACjD,MAAM,WAAW,2BAA4B,SAAQ,gCAAgC;IACpF,oFAAoF;IACpF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAwGxF,CAAC"}
|
|
@@ -20,8 +20,11 @@ import '../assert_dev_env.js';
|
|
|
20
20
|
*
|
|
21
21
|
* And on a path neither server routes, which reaches each spine's fallback:
|
|
22
22
|
*
|
|
23
|
-
* - **foreign `Origin` → 403 `forbidden_origin`**, **absent →
|
|
24
|
-
* fallback is behind the check, not attached after it.
|
|
23
|
+
* - **foreign `Origin` → 403 `forbidden_origin`**, **absent → the fallback's
|
|
24
|
+
* own answer** — the fallback is behind the check, not attached after it.
|
|
25
|
+
* The absent row pins that the check let the request through to the
|
|
26
|
+
* fallback: `404` by default, or `unrouted_status` for a backend whose
|
|
27
|
+
* fallback serves an SPA shell (`200`).
|
|
25
28
|
* - **an over-cap `POST` with a declared `Content-Length` and a foreign
|
|
26
29
|
* `Origin` → 413 `payload_too_large`** — the body cap comes ahead of the
|
|
27
30
|
* origin check on the fallback too (413-first holds for a declared length).
|
|
@@ -30,27 +33,25 @@ import '../assert_dev_env.js';
|
|
|
30
33
|
* server advertises; the unrouted rows run on every backend.
|
|
31
34
|
* Runs both legs via the shared `{setup_test, capabilities}` protocol: the
|
|
32
35
|
* in-process leg (`cross_backend/origin_scope_parity.db.test.ts`, plain `gro
|
|
33
|
-
* test`) and the cross-process leg (`cross_backend/
|
|
36
|
+
* test`) and the cross-process leg (`cross_backend/spine_suites.cross.test.ts`,
|
|
34
37
|
* the TS spine binaries + Rust `testing_spine_stub` over real HTTP).
|
|
35
38
|
*
|
|
36
39
|
* @module
|
|
37
40
|
*/
|
|
38
41
|
import { describe, assert, test } from 'vitest';
|
|
39
42
|
import { ERROR_FORBIDDEN_ORIGIN, ERROR_PAYLOAD_TOO_LARGE } from '../../http/error_schemas.js';
|
|
40
|
-
import { raw_http_exchange, raw_http_parse_response } from '../transports/raw_http.js';
|
|
43
|
+
import { raw_http_exchange, raw_http_parse_response, RAW_HTTP_READ_TIMEOUT_MS } from '../transports/raw_http.js';
|
|
41
44
|
import { test_if } from './capabilities.js';
|
|
45
|
+
import { SPINE_BODY_LIMIT_DEFAULT_BYTES } from './spine_surface_constants.js';
|
|
42
46
|
/** An origin no spine test server allows. */
|
|
43
47
|
const FOREIGN_ORIGIN = 'https://evil.example';
|
|
44
48
|
/** A path no spine test server routes — it reaches the fallback. */
|
|
45
49
|
const UNROUTED_PATH = '/origin-scope-unrouted';
|
|
46
|
-
/** The shared 1 MiB default cap both spines ship, as in `body_size.ts`. */
|
|
47
|
-
const BODY_LIMIT_DEFAULT_BYTES = 1024 * 1024;
|
|
48
|
-
/** How long a raw exchange waits for the refusal. */
|
|
49
|
-
const RAW_READ_TIMEOUT_MS = 2000;
|
|
50
50
|
export const describe_origin_scope_cross_tests = (options) => {
|
|
51
51
|
const { setup_test, capabilities } = options;
|
|
52
52
|
const probe_path = options.probe_path ?? '/ready';
|
|
53
53
|
const base_url = options.base_url ?? null;
|
|
54
|
+
const unrouted_status = options.unrouted_status ?? 404;
|
|
54
55
|
describe('origin check scope parity', () => {
|
|
55
56
|
test_if(capabilities.ready, `a foreign Origin on ${probe_path} → 403 forbidden_origin (the check covers every path)`, async () => {
|
|
56
57
|
const fixture = await setup_test();
|
|
@@ -86,15 +87,15 @@ export const describe_origin_scope_cross_tests = (options) => {
|
|
|
86
87
|
const body = (await res.json().catch(() => undefined));
|
|
87
88
|
assert.strictEqual(body?.error, ERROR_FORBIDDEN_ORIGIN);
|
|
88
89
|
});
|
|
89
|
-
test(
|
|
90
|
+
test(`an absent Origin on an unrouted path → passes to the fallback (${unrouted_status})`, async () => {
|
|
90
91
|
const fixture = await setup_test();
|
|
91
92
|
const res = await fixture.fresh_transport({ origin: null })(UNROUTED_PATH, {
|
|
92
93
|
method: 'GET'
|
|
93
94
|
});
|
|
94
|
-
assert.strictEqual(res.status,
|
|
95
|
+
assert.strictEqual(res.status, unrouted_status, 'a request with no Origin must pass the check and reach the fallback');
|
|
95
96
|
});
|
|
96
97
|
test('an over-cap POST to an unrouted path with a foreign Origin → 413 payload_too_large', async () => {
|
|
97
|
-
const over =
|
|
98
|
+
const over = SPINE_BODY_LIMIT_DEFAULT_BYTES + 1;
|
|
98
99
|
let status;
|
|
99
100
|
let text;
|
|
100
101
|
if (base_url === null) {
|
|
@@ -117,7 +118,7 @@ export const describe_origin_scope_cross_tests = (options) => {
|
|
|
117
118
|
`content-length: ${over}\r\n` +
|
|
118
119
|
`\r\n`;
|
|
119
120
|
const { received } = await raw_http_exchange(base_url, request, {
|
|
120
|
-
read_timeout_ms:
|
|
121
|
+
read_timeout_ms: RAW_HTTP_READ_TIMEOUT_MS,
|
|
121
122
|
until: 'response'
|
|
122
123
|
});
|
|
123
124
|
const response = raw_http_parse_response(received);
|
|
@@ -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_peer_ping_ws_tests`. */
|
|
5
|
-
export interface PeerPingWsTestOptions {
|
|
6
|
+
export interface PeerPingWsTestOptions 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 server-initiated `peer/ping` suite — a positive round-trip
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"peer_ping_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/peer_ping_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"peer_ping_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/peer_ping_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAoB1E,uDAAuD;AACvD,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC9D,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,qBAAqB,KAAG,IAmO5E,CAAC"}
|
|
@@ -36,10 +36,11 @@ import '../assert_dev_env.js';
|
|
|
36
36
|
*/
|
|
37
37
|
import { assert, describe } from 'vitest';
|
|
38
38
|
import { rpc_call } from '../rpc_helpers.js';
|
|
39
|
-
import { create_ws_transport } from '../transports/ws_transport.js';
|
|
40
39
|
import { is_response_for } from '../transports/ws_client.js';
|
|
41
40
|
import { create_jsonrpc_request, is_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
|
|
42
41
|
import { test_if } from './capabilities.js';
|
|
42
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
43
|
+
import { open_session_ws } from './ws_endpoint.js';
|
|
43
44
|
// The wire literals below are deliberate independent copies of the
|
|
44
45
|
// producers' constants (`actions/peer_ping.ts` on TS, `REASON_PEER_*` /
|
|
45
46
|
// `PEER_PING_METHOD` on Rust) — parity by test, not codegen. The harness
|
|
@@ -52,8 +53,6 @@ const PEER_PING_METHOD = 'peer/ping';
|
|
|
52
53
|
const REASON_PEER_TIMEOUT = 'peer_timeout';
|
|
53
54
|
const REASON_PEER_PING_INVALID_REPLY = 'peer_ping_invalid_reply';
|
|
54
55
|
const REASON_PEER_NO_TRANSPORT = 'peer_no_transport';
|
|
55
|
-
/** JSON-RPC endpoint path — matches the spine's `/api/rpc` (and the forge's). */
|
|
56
|
-
const RPC_PATH = '/api/rpc';
|
|
57
56
|
/** A reply payload that satisfies the Rust `PingResponse` shape. */
|
|
58
57
|
const valid_reply = (nonce) => ({ nonce, protocol_version: 1 });
|
|
59
58
|
/**
|
|
@@ -64,19 +63,10 @@ const valid_reply = (nonce) => ({ nonce, protocol_version: 1 });
|
|
|
64
63
|
* the HTTP no-transport path). Gated on `capabilities.peer_request`.
|
|
65
64
|
*/
|
|
66
65
|
export const describe_peer_ping_ws_tests = (options) => {
|
|
67
|
-
const { setup_test, capabilities
|
|
66
|
+
const { setup_test, capabilities } = options;
|
|
68
67
|
// -- shared helpers -------------------------------------------------------
|
|
69
|
-
/** Open a WS transport
|
|
70
|
-
const open_ws = (
|
|
71
|
-
assert.ok(cookie, 'expected a session cookie for the WS upgrade');
|
|
72
|
-
return create_ws_transport({
|
|
73
|
-
base_url,
|
|
74
|
-
ws_path,
|
|
75
|
-
cookies: [cookie],
|
|
76
|
-
origin: base_url,
|
|
77
|
-
on_request
|
|
78
|
-
});
|
|
79
|
-
};
|
|
68
|
+
/** Open a WS transport on the fixture's session, with an optional responder. */
|
|
69
|
+
const open_ws = (fixture, on_request) => open_session_ws(options, fixture, { admitted: false, on_request });
|
|
80
70
|
/** A responder that echoes a valid `PingResponse` for every `peer/ping`. */
|
|
81
71
|
const echo_responder = (req) => {
|
|
82
72
|
const nonce = req.params?.nonce ?? 0;
|
|
@@ -96,7 +86,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
96
86
|
describe('peer/ping server-initiated request (cross-process)', () => {
|
|
97
87
|
test_if(capabilities.peer_request, 'a client peer/ping round-trips: server pings back, validates the echo, returns it', async () => {
|
|
98
88
|
const fixture = await setup_test();
|
|
99
|
-
const ws = await open_ws(fixture
|
|
89
|
+
const ws = await open_ws(fixture, echo_responder);
|
|
100
90
|
try {
|
|
101
91
|
const result = await ws.request(1, PEER_PING_METHOD, { nonce: 42 }, 8000);
|
|
102
92
|
assert.strictEqual(result.nonce, 42, 'the server echoes the issued nonce back');
|
|
@@ -109,7 +99,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
109
99
|
test_if(capabilities.peer_request, 'a never-replying client yields a server-side Timeout (the liveness signal)', async () => {
|
|
110
100
|
const fixture = await setup_test();
|
|
111
101
|
// Responder swallows the ping (returns nothing) — the half-open peer.
|
|
112
|
-
const ws = await open_ws(fixture
|
|
102
|
+
const ws = await open_ws(fixture, () => undefined);
|
|
113
103
|
try {
|
|
114
104
|
// Short server-side deadline so the negative runs fast.
|
|
115
105
|
const frame = await ping_raw(ws, 2, { nonce: 1, timeout_ms: 300 });
|
|
@@ -124,7 +114,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
124
114
|
const fixture = await setup_test();
|
|
125
115
|
// Reply that fails `PingResponse` validation (nonce wrong type,
|
|
126
116
|
// protocol_version missing).
|
|
127
|
-
const ws = await open_ws(fixture
|
|
117
|
+
const ws = await open_ws(fixture, () => ({
|
|
128
118
|
result: { nonce: 'not-a-number' }
|
|
129
119
|
}));
|
|
130
120
|
try {
|
|
@@ -141,7 +131,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
141
131
|
// and return only the declared shape.
|
|
142
132
|
test_if(capabilities.peer_request, 'a reply with an undeclared key is accepted and the key is dropped', async () => {
|
|
143
133
|
const fixture = await setup_test();
|
|
144
|
-
const ws = await open_ws(fixture
|
|
134
|
+
const ws = await open_ws(fixture, (req) => {
|
|
145
135
|
const nonce = req.params?.nonce ?? 0;
|
|
146
136
|
return { result: { ...valid_reply(nonce), added_later: true } };
|
|
147
137
|
});
|
|
@@ -156,7 +146,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
156
146
|
});
|
|
157
147
|
test_if(capabilities.peer_request, 'a client JSON-RPC error reply surfaces as the action error (ClientError forwarded)', async () => {
|
|
158
148
|
const fixture = await setup_test();
|
|
159
|
-
const ws = await open_ws(fixture
|
|
149
|
+
const ws = await open_ws(fixture, () => ({
|
|
160
150
|
error: { code: -32603, message: 'client refused', data: { reason: 'client_says_no' } }
|
|
161
151
|
}));
|
|
162
152
|
try {
|
|
@@ -171,7 +161,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
171
161
|
});
|
|
172
162
|
test_if(capabilities.peer_request, 'an unsolicited response is dropped: it resolves nothing and the socket survives', async () => {
|
|
173
163
|
const fixture = await setup_test();
|
|
174
|
-
const ws = await open_ws(fixture
|
|
164
|
+
const ws = await open_ws(fixture, echo_responder);
|
|
175
165
|
try {
|
|
176
166
|
// Inject a reply for an id the server never issued on this socket.
|
|
177
167
|
await ws.send({ jsonrpc: '2.0', id: 's999999', result: valid_reply(7) });
|
|
@@ -189,8 +179,8 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
189
179
|
// A has NO responder, so the inbound server-initiated request is
|
|
190
180
|
// surfaced as a message A can observe (to learn its server id);
|
|
191
181
|
// A never answers it.
|
|
192
|
-
const ws_a = await open_ws(fixture
|
|
193
|
-
const ws_b = await open_ws(fixture
|
|
182
|
+
const ws_a = await open_ws(fixture);
|
|
183
|
+
const ws_b = await open_ws(fixture, echo_responder);
|
|
194
184
|
try {
|
|
195
185
|
// A invokes peer/ping with a short server-side deadline; don't
|
|
196
186
|
// await yet — observe the server→client request first.
|
|
@@ -217,7 +207,7 @@ export const describe_peer_ping_ws_tests = (options) => {
|
|
|
217
207
|
const fixture = await setup_test();
|
|
218
208
|
const res = await rpc_call({
|
|
219
209
|
app: fixture.transport,
|
|
220
|
-
path:
|
|
210
|
+
path: SPINE_RPC_PATH,
|
|
221
211
|
method: PEER_PING_METHOD,
|
|
222
212
|
params: { nonce: 1 },
|
|
223
213
|
headers: fixture.create_session_headers()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/** The URL protocols a Postgres connection string carries. */
|
|
3
|
+
export declare const POSTGRES_URL_PROTOCOLS: ReadonlySet<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Whether `url` parses as a `postgres:` / `postgresql:` URL. `URL.canParse`
|
|
6
|
+
* alone admits a scheme-less `localhost:5432/db`, which it reads as protocol
|
|
7
|
+
* `localhost:`.
|
|
8
|
+
*
|
|
9
|
+
* @param url - the candidate connection string
|
|
10
|
+
*/
|
|
11
|
+
export declare const is_postgres_url: (url: string) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* `database_url` with its database replaced by `database` — how a setup that
|
|
14
|
+
* spawns a second backend lands on the same server under a database nothing
|
|
15
|
+
* else uses.
|
|
16
|
+
*
|
|
17
|
+
* @param database_url - a `postgres://` URL on the target server
|
|
18
|
+
* @param database - the database name to put in it
|
|
19
|
+
* @returns the URL naming `database` on the same server
|
|
20
|
+
*/
|
|
21
|
+
export declare const with_database_name: (database_url: string, database: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* The URL `createdb` connects through to create a database on the server
|
|
24
|
+
* `server_url` names — the same URL with its database replaced by `postgres`.
|
|
25
|
+
*
|
|
26
|
+
* @param server_url - a `postgres://` URL on the target server
|
|
27
|
+
* @returns the URL of that server's `postgres` maintenance database
|
|
28
|
+
*/
|
|
29
|
+
export declare const to_maintenance_database_url: (server_url: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* A database URL fit to print: the password, when there is one, replaced —
|
|
32
|
+
* in the userinfo and in the `password` query parameter `pg` and libpq also
|
|
33
|
+
* take. The whole URL rather than its host, which is empty for a socket URL
|
|
34
|
+
* that names its server in the query string.
|
|
35
|
+
*
|
|
36
|
+
* @param database_url - a `postgres://` URL
|
|
37
|
+
* @returns the URL with any password replaced by `REDACTED`
|
|
38
|
+
*/
|
|
39
|
+
export declare const redact_database_url: (database_url: string) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether `createdb`'s stderr is its refusal of a database that already
|
|
42
|
+
* exists — Postgres's `database "<name>" already exists`, which `createdb`
|
|
43
|
+
* prints after `database creation failed: ERROR:`.
|
|
44
|
+
*
|
|
45
|
+
* @param stderr - what `createdb` wrote to stderr
|
|
46
|
+
* @param database - the database it was asked to create
|
|
47
|
+
*/
|
|
48
|
+
export declare const is_createdb_already_exists: (stderr: string, database: string) => boolean;
|
|
49
|
+
/** Dependencies of `ensure_database` — the warning a failed `createdb` is reported with. */
|
|
50
|
+
export interface EnsureDatabaseDeps {
|
|
51
|
+
readonly warn: (message: string) => void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a Postgres database when it is missing, with `createdb`.
|
|
55
|
+
*
|
|
56
|
+
* Which server it lands on depends on `server_url`. Given one, the database is
|
|
57
|
+
* created on the server that URL names: `createdb` connects through the URL
|
|
58
|
+
* with its database swapped for the `postgres` maintenance database, so host,
|
|
59
|
+
* port, role and query parameters all come from the URL, and a password goes
|
|
60
|
+
* to `createdb` in its environment, never its arguments. Without one,
|
|
61
|
+
* `createdb` reads the libpq environment (`PGHOST` / `PGPORT` / `PGUSER`).
|
|
62
|
+
*
|
|
63
|
+
* Idempotent and never throws: a database that already exists is the quiet
|
|
64
|
+
* success case (`is_createdb_already_exists`). Any other failure — no
|
|
65
|
+
* `createdb` on PATH, no server, a role that may not create databases — is
|
|
66
|
+
* reported through `deps.warn` with `createdb`'s own stderr, and whatever
|
|
67
|
+
* connects next reports a database that is really missing.
|
|
68
|
+
*
|
|
69
|
+
* @param database - the database name
|
|
70
|
+
* @param server_url - a `postgres://` URL on the server to create it on; its database name is ignored
|
|
71
|
+
* @param deps - where a failure is reported, `console.warn` by default
|
|
72
|
+
* @returns whether the database exists now (created, or already there)
|
|
73
|
+
*/
|
|
74
|
+
export declare const ensure_database: (database: string, server_url?: string, deps?: EnsureDatabaseDeps) => boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Create the database a Postgres URL names, on the server it names, when it
|
|
77
|
+
* is missing (`ensure_database`). A URL that is not `postgres:` — `memory://`,
|
|
78
|
+
* `file://` — or that names no database is left alone.
|
|
79
|
+
*
|
|
80
|
+
* @param database_url - the URL a backend connects to
|
|
81
|
+
* @param deps - where a failure is reported, `console.warn` by default
|
|
82
|
+
* @returns whether the URL named a Postgres database to ensure
|
|
83
|
+
*/
|
|
84
|
+
export declare const ensure_database_for_url: (database_url: string, deps?: EnsureDatabaseDeps) => boolean;
|
|
85
|
+
//# sourceMappingURL=postgres_database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_database.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/postgres_database.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAyC,CAAC;AAEjG;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,KAAG,OACyB,CAAC;AAExE;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,EAAE,UAAU,MAAM,KAAG,MAI3E,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,GAAI,YAAY,MAAM,KAAG,MACtB,CAAC;AAE5C;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,cAAc,MAAM,KAAG,MAK1D,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,OACrB,CAAC;AAE1D,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAMD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,eAAe,GAC3B,UAAU,MAAM,EAChB,aAAa,MAAM,EACnB,OAAM,kBAAiD,KACrD,OAyBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GACnC,cAAc,MAAM,EACpB,OAAO,kBAAkB,KACvB,OAMF,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Postgres database helpers for cross-backend global setups — creating the
|
|
4
|
+
* database a backend's URL names, and printing such a URL safely.
|
|
5
|
+
*
|
|
6
|
+
* The harness never creates a database by itself: a setup opts in
|
|
7
|
+
* (`create_cross_backend_global_setup`'s `ensure_databases`, or a call to
|
|
8
|
+
* `ensure_database_for_url`), so a test role without `CREATEDB` is never
|
|
9
|
+
* required by default.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
/** The URL protocols a Postgres connection string carries. */
|
|
15
|
+
export const POSTGRES_URL_PROTOCOLS = new Set(['postgres:', 'postgresql:']);
|
|
16
|
+
/**
|
|
17
|
+
* Whether `url` parses as a `postgres:` / `postgresql:` URL. `URL.canParse`
|
|
18
|
+
* alone admits a scheme-less `localhost:5432/db`, which it reads as protocol
|
|
19
|
+
* `localhost:`.
|
|
20
|
+
*
|
|
21
|
+
* @param url - the candidate connection string
|
|
22
|
+
*/
|
|
23
|
+
export const is_postgres_url = (url) => URL.canParse(url) && POSTGRES_URL_PROTOCOLS.has(new URL(url).protocol);
|
|
24
|
+
/**
|
|
25
|
+
* `database_url` with its database replaced by `database` — how a setup that
|
|
26
|
+
* spawns a second backend lands on the same server under a database nothing
|
|
27
|
+
* else uses.
|
|
28
|
+
*
|
|
29
|
+
* @param database_url - a `postgres://` URL on the target server
|
|
30
|
+
* @param database - the database name to put in it
|
|
31
|
+
* @returns the URL naming `database` on the same server
|
|
32
|
+
*/
|
|
33
|
+
export const with_database_name = (database_url, database) => {
|
|
34
|
+
const url = new URL(database_url);
|
|
35
|
+
url.pathname = `/${database}`;
|
|
36
|
+
return url.href;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The URL `createdb` connects through to create a database on the server
|
|
40
|
+
* `server_url` names — the same URL with its database replaced by `postgres`.
|
|
41
|
+
*
|
|
42
|
+
* @param server_url - a `postgres://` URL on the target server
|
|
43
|
+
* @returns the URL of that server's `postgres` maintenance database
|
|
44
|
+
*/
|
|
45
|
+
export const to_maintenance_database_url = (server_url) => with_database_name(server_url, 'postgres');
|
|
46
|
+
/**
|
|
47
|
+
* A database URL fit to print: the password, when there is one, replaced —
|
|
48
|
+
* in the userinfo and in the `password` query parameter `pg` and libpq also
|
|
49
|
+
* take. The whole URL rather than its host, which is empty for a socket URL
|
|
50
|
+
* that names its server in the query string.
|
|
51
|
+
*
|
|
52
|
+
* @param database_url - a `postgres://` URL
|
|
53
|
+
* @returns the URL with any password replaced by `REDACTED`
|
|
54
|
+
*/
|
|
55
|
+
export const redact_database_url = (database_url) => {
|
|
56
|
+
const url = new URL(database_url);
|
|
57
|
+
if (url.password)
|
|
58
|
+
url.password = 'REDACTED';
|
|
59
|
+
if (url.searchParams.has('password'))
|
|
60
|
+
url.searchParams.set('password', 'REDACTED');
|
|
61
|
+
return url.href;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Whether `createdb`'s stderr is its refusal of a database that already
|
|
65
|
+
* exists — Postgres's `database "<name>" already exists`, which `createdb`
|
|
66
|
+
* prints after `database creation failed: ERROR:`.
|
|
67
|
+
*
|
|
68
|
+
* @param stderr - what `createdb` wrote to stderr
|
|
69
|
+
* @param database - the database it was asked to create
|
|
70
|
+
*/
|
|
71
|
+
export const is_createdb_already_exists = (stderr, database) => stderr.includes(`database "${database}" already exists`);
|
|
72
|
+
const default_ensure_database_deps = {
|
|
73
|
+
warn: (message) => console.warn(message)
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Create a Postgres database when it is missing, with `createdb`.
|
|
77
|
+
*
|
|
78
|
+
* Which server it lands on depends on `server_url`. Given one, the database is
|
|
79
|
+
* created on the server that URL names: `createdb` connects through the URL
|
|
80
|
+
* with its database swapped for the `postgres` maintenance database, so host,
|
|
81
|
+
* port, role and query parameters all come from the URL, and a password goes
|
|
82
|
+
* to `createdb` in its environment, never its arguments. Without one,
|
|
83
|
+
* `createdb` reads the libpq environment (`PGHOST` / `PGPORT` / `PGUSER`).
|
|
84
|
+
*
|
|
85
|
+
* Idempotent and never throws: a database that already exists is the quiet
|
|
86
|
+
* success case (`is_createdb_already_exists`). Any other failure — no
|
|
87
|
+
* `createdb` on PATH, no server, a role that may not create databases — is
|
|
88
|
+
* reported through `deps.warn` with `createdb`'s own stderr, and whatever
|
|
89
|
+
* connects next reports a database that is really missing.
|
|
90
|
+
*
|
|
91
|
+
* @param database - the database name
|
|
92
|
+
* @param server_url - a `postgres://` URL on the server to create it on; its database name is ignored
|
|
93
|
+
* @param deps - where a failure is reported, `console.warn` by default
|
|
94
|
+
* @returns whether the database exists now (created, or already there)
|
|
95
|
+
*/
|
|
96
|
+
export const ensure_database = (database, server_url, deps = default_ensure_database_deps) => {
|
|
97
|
+
const args = [database];
|
|
98
|
+
const env = { ...process.env };
|
|
99
|
+
if (server_url !== undefined) {
|
|
100
|
+
const url = new URL(to_maintenance_database_url(server_url));
|
|
101
|
+
// any local user can read the process list
|
|
102
|
+
const password = url.searchParams.get('password') ?? decodeURIComponent(url.password);
|
|
103
|
+
if (password)
|
|
104
|
+
env.PGPASSWORD = password;
|
|
105
|
+
url.password = '';
|
|
106
|
+
url.searchParams.delete('password');
|
|
107
|
+
args.unshift(`--maintenance-db=${url.href}`);
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
execFileSync('createdb', args, { stdio: ['ignore', 'ignore', 'pipe'], env, encoding: 'utf-8' });
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const failure = error;
|
|
115
|
+
const stderr = (failure.stderr ?? '').trim();
|
|
116
|
+
if (is_createdb_already_exists(stderr, database))
|
|
117
|
+
return true;
|
|
118
|
+
deps.warn(`ensure_database: createdb ${database} failed — ` +
|
|
119
|
+
(failure.code === 'ENOENT' ? 'createdb is not on PATH' : stderr || failure.message));
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Create the database a Postgres URL names, on the server it names, when it
|
|
125
|
+
* is missing (`ensure_database`). A URL that is not `postgres:` — `memory://`,
|
|
126
|
+
* `file://` — or that names no database is left alone.
|
|
127
|
+
*
|
|
128
|
+
* @param database_url - the URL a backend connects to
|
|
129
|
+
* @param deps - where a failure is reported, `console.warn` by default
|
|
130
|
+
* @returns whether the URL named a Postgres database to ensure
|
|
131
|
+
*/
|
|
132
|
+
export const ensure_database_for_url = (database_url, deps) => {
|
|
133
|
+
if (!is_postgres_url(database_url))
|
|
134
|
+
return false;
|
|
135
|
+
const database = decodeURIComponent(new URL(database_url).pathname.slice(1));
|
|
136
|
+
if (!database)
|
|
137
|
+
return false;
|
|
138
|
+
ensure_database(database, database_url, deps);
|
|
139
|
+
return true;
|
|
140
|
+
};
|