@fuzdev/fuz_app 0.147.0 → 0.149.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 +8 -6
- 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/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +23 -3
- package/dist/auth/CLAUDE.md +3 -2
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +29 -8
- package/dist/auth/audit_emitter.d.ts +14 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +5 -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 +8 -4
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +90 -35
- 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 +80 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +71 -1
- package/dist/testing/CLAUDE.md +853 -146
- 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 +124 -0
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
- package/dist/testing/adversarial_input_cases.js +393 -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 +30 -3
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +55 -3
- 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 +57 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/audit_cross_helpers.js +89 -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 +78 -59
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +25 -24
- 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 +174 -3
- 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} +14 -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 +58 -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 +47 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_adversarial.js +302 -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 +33 -21
- 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 +58 -56
- 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 +311 -0
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_suites.js +897 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +44 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +53 -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 +29 -12
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +143 -51
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -14
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +58 -37
- 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 +23 -13
- 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 +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +27 -140
- package/dist/testing/rpc_envelope_cases.d.ts +114 -0
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
- package/dist/testing/rpc_envelope_cases.js +243 -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 +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +261 -73
- 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/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- 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/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -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,7 +1,9 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import { assert, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
import { get_audit_metadata_validation_failures, get_audit_unknown_event_type_failures, reset_audit_metadata_validation_failures, reset_audit_unknown_event_type_failures } from '../auth/audit_log_queries.js';
|
|
4
5
|
import { get_audit_write_failures, reset_audit_write_failures } from '../auth/audit_emitter.js';
|
|
6
|
+
import { audit_metadata_schemas } from '../auth/audit_log_schema.js';
|
|
5
7
|
/**
|
|
6
8
|
* Register per-test `beforeEach` + `afterEach` hooks that catch any audit
|
|
7
9
|
* emission with a metadata shape that fails its `audit_metadata_schemas`
|
|
@@ -62,8 +64,9 @@ export const assert_no_audit_drift = () => {
|
|
|
62
64
|
* `notify` is a no-op; `add_listener` records into a local array that
|
|
63
65
|
* `listener_count` reports, and returns a remover with the production
|
|
64
66
|
* semantics (per registration, idempotent) — registered listeners never fire,
|
|
65
|
-
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
|
|
66
|
-
* a recorded call writes nothing, so nothing is
|
|
67
|
+
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
|
|
68
|
+
* and `is_tracking_inflight` answers `false`: a recorded call writes nothing, so nothing is
|
|
69
|
+
* ever in flight.
|
|
67
70
|
*
|
|
68
71
|
* `emit` AND `emit_pool` both append to `calls`, so a test reads either
|
|
69
72
|
* entry point's writes off the same array.
|
|
@@ -116,7 +119,56 @@ export const create_recording_audit_emitter = (calls_ref) => {
|
|
|
116
119
|
};
|
|
117
120
|
},
|
|
118
121
|
listener_count: () => listeners.length,
|
|
119
|
-
drain_inflight: () => Promise.resolve()
|
|
122
|
+
drain_inflight: () => Promise.resolve(),
|
|
123
|
+
is_tracking_inflight: () => false
|
|
120
124
|
};
|
|
121
125
|
return { emitter, calls };
|
|
122
126
|
};
|
|
127
|
+
/**
|
|
128
|
+
* The closed-world twin of a builtin event's `audit_metadata_schemas` entry:
|
|
129
|
+
* the same fields, with unknown keys refused. The registry's schemas are
|
|
130
|
+
* `z.looseObject` so a consumer can add fields to a builtin event (a builtin's
|
|
131
|
+
* schema can't be overridden), so this is opt-in.
|
|
132
|
+
*/
|
|
133
|
+
const to_closed_metadata_schema = (schema) => {
|
|
134
|
+
if (schema instanceof z.ZodNullable) {
|
|
135
|
+
return to_closed_metadata_schema(schema.unwrap()).nullable();
|
|
136
|
+
}
|
|
137
|
+
if (schema instanceof z.ZodObject)
|
|
138
|
+
return z.strictObject(schema.shape);
|
|
139
|
+
return schema;
|
|
140
|
+
};
|
|
141
|
+
const declared_metadata_schemas = new Map(Object.entries(audit_metadata_schemas));
|
|
142
|
+
const closed_metadata_schemas = new Map(Object.entries(audit_metadata_schemas).map(([event_type, schema]) => [
|
|
143
|
+
event_type,
|
|
144
|
+
to_closed_metadata_schema(schema)
|
|
145
|
+
]));
|
|
146
|
+
/**
|
|
147
|
+
* Assert an audit row read off the wire carries the metadata its event type
|
|
148
|
+
* declares — parsed against `audit_metadata_schemas[event_type]` as declared,
|
|
149
|
+
* so a mistyped, `null`, or missing field fails, and with
|
|
150
|
+
* `reject_unknown_keys` so does a key the event doesn't declare. The wire
|
|
151
|
+
* schema (`AuditLogEventJson.metadata`) is a loose record, and only the TS
|
|
152
|
+
* spine validates metadata, at insert and fail-open, so this is the check that
|
|
153
|
+
* holds a spine's rows to their event's contract.
|
|
154
|
+
*
|
|
155
|
+
* Builtin event types only: a consumer-registered type has no entry here, and
|
|
156
|
+
* passes unexamined.
|
|
157
|
+
*
|
|
158
|
+
* @param event - an audit row from `audit_log_list` or an audit read like it
|
|
159
|
+
* @param options - `reject_unknown_keys` for the closed-world check
|
|
160
|
+
* @throws AssertionError naming the event type and the schema issues
|
|
161
|
+
*/
|
|
162
|
+
export const assert_audit_event_metadata = (event, options) => {
|
|
163
|
+
const schema = (options?.reject_unknown_keys ? closed_metadata_schemas : declared_metadata_schemas).get(event.event_type);
|
|
164
|
+
if (!schema)
|
|
165
|
+
return;
|
|
166
|
+
const parsed = schema.safeParse(event.metadata);
|
|
167
|
+
if (parsed.success)
|
|
168
|
+
return;
|
|
169
|
+
assert.fail(`audit row ${event.event_type} (outcome=${event.outcome}) carries metadata outside its ` +
|
|
170
|
+
`audit_metadata_schemas entry: ${JSON.stringify(event.metadata)} — ` +
|
|
171
|
+
parsed.error.issues
|
|
172
|
+
.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
173
|
+
.join('; '));
|
|
174
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,
|
|
1
|
+
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAwaF,CAAC"}
|
|
@@ -17,11 +17,12 @@ import '../assert_dev_env.js';
|
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
19
|
import { describe, assert } from 'vitest';
|
|
20
|
-
import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput,
|
|
20
|
+
import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAccountListOutput, ERROR_CANNOT_DELETE_KEEPER } from '../../auth/admin_action_specs.js';
|
|
21
21
|
import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED, ERROR_CREDENTIAL_TYPE_REQUIRED } from '../../http/error_schemas.js';
|
|
22
22
|
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
23
|
+
import { assert_audit_metadata_fields, list_audit_events } from './audit_cross_helpers.js';
|
|
23
24
|
import { test_if } from './capabilities.js';
|
|
24
|
-
import { cross_rpc_call, error_reason, expect_output } from './
|
|
25
|
+
import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
|
|
25
26
|
import { assert_iso8601_seconds } from './wire_shapes.js';
|
|
26
27
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
27
28
|
export const describe_account_lifecycle_cross_tests = (options) => {
|
|
@@ -38,6 +39,15 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
38
39
|
assert.strictEqual(deleted.deleted, true);
|
|
39
40
|
const undeleted = expect_output(await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers), AccountUndeleteOutput);
|
|
40
41
|
assert.strictEqual(undeleted.undeleted, true);
|
|
42
|
+
// each verb audits the victim's identity snapshot, attributed to
|
|
43
|
+
// the admin (`list_audit_events` parses the metadata)
|
|
44
|
+
for (const event_type of ['account_delete', 'account_undelete']) {
|
|
45
|
+
const events = await list_audit_events(fixture, { event_type }, rpc_path);
|
|
46
|
+
const row = events.find((e) => e.outcome === 'success' && e.target_account_id === victim.account.id);
|
|
47
|
+
assert.ok(row, `${event_type} writes a success row targeting the victim`);
|
|
48
|
+
assert.strictEqual(row.account_id, fixture.account.id, event_type);
|
|
49
|
+
assert.strictEqual(row.metadata?.username, victim.account.username, `${event_type} snapshots the username`);
|
|
50
|
+
}
|
|
41
51
|
});
|
|
42
52
|
test_if(capabilities.account_lifecycle, 'purge (keeper, confirmed)', async () => {
|
|
43
53
|
const fixture = await setup_test();
|
|
@@ -46,6 +56,11 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
46
56
|
// Purge is keeper-gated: daemon-token credential, not a session.
|
|
47
57
|
const purged = expect_output(await cross_rpc_call(t, rpc_path, 'account_purge', { account_id: victim.account.id, confirm: true }, fixture.create_daemon_token_headers()), AccountPurgeOutput);
|
|
48
58
|
assert.strictEqual(purged.purged, true);
|
|
59
|
+
// the purge row outlives the account it names, carrying its identity
|
|
60
|
+
const events = await list_audit_events(fixture, { event_type: 'account_purge' }, rpc_path);
|
|
61
|
+
const row = events.find((e) => e.outcome === 'success' && e.target_account_id === victim.account.id);
|
|
62
|
+
assert.ok(row, 'account_purge writes a success row targeting the victim');
|
|
63
|
+
assert.strictEqual(row.metadata?.username, victim.account.username, 'account_purge snapshots the username');
|
|
49
64
|
});
|
|
50
65
|
test_if(capabilities.account_lifecycle, 'keeper guard: delete + purge refuse the keeper account', async () => {
|
|
51
66
|
const fixture = await setup_test();
|
|
@@ -119,16 +134,11 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
119
134
|
// and emits a forensic `outcome: failure` audit row.
|
|
120
135
|
const del = await cross_rpc_call(t, rpc_path, 'account_delete', { account_id: fixture.account.id }, fixture.create_session_headers());
|
|
121
136
|
assert.strictEqual(error_reason(del), ERROR_CANNOT_DELETE_KEEPER);
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
// The failure row is now authoritative on both spines. `_testing_reset`
|
|
128
|
-
// wiped audit_log at setup, so the refused delete is the only
|
|
129
|
-
// account_delete event.
|
|
130
|
-
const listed = expect_output(await cross_rpc_call(t, rpc_path, 'audit_log_list', { event_type: 'account_delete' }, fixture.create_session_headers()), AuditLogListOutput);
|
|
131
|
-
const failure = listed.events.find((e) => e.outcome === 'failure' &&
|
|
137
|
+
// Read behind the drain barrier, so the failure row is authoritative
|
|
138
|
+
// on both spines. `_testing_reset` wiped audit_log at setup, so the
|
|
139
|
+
// refused delete is the only account_delete event.
|
|
140
|
+
const events = await list_audit_events(fixture, { event_type: 'account_delete' }, rpc_path);
|
|
141
|
+
const failure = events.find((e) => e.outcome === 'failure' &&
|
|
132
142
|
e.metadata?.reason === ERROR_CANNOT_DELETE_KEEPER);
|
|
133
143
|
assert.ok(failure, 'keeper-removal guard must emit an account_delete outcome=failure audit row with reason cannot_delete_keeper');
|
|
134
144
|
// Byte-shape gate on the audit row's timestamp — `z.string()` in
|
|
@@ -151,6 +161,13 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
151
161
|
const again = await cross_rpc_call(t, rpc_path, 'account_undelete', { account_id: victim.account.id }, admin_headers);
|
|
152
162
|
assert.strictEqual(again.ok, false, 'double-undelete must not silently succeed');
|
|
153
163
|
assert.strictEqual(error_reason(again), ERROR_ACCOUNT_NOT_FOUND);
|
|
164
|
+
// the refused undelete is audited as a failure naming the probed id
|
|
165
|
+
const events = await list_audit_events(fixture, { event_type: 'account_undelete', outcome: 'failure' }, rpc_path);
|
|
166
|
+
assert.strictEqual(events.length, 1, 'one account_undelete failure row');
|
|
167
|
+
assert_audit_metadata_fields(fixture, events[0].metadata, {
|
|
168
|
+
reason: ERROR_ACCOUNT_NOT_FOUND,
|
|
169
|
+
attempted_account_id: victim.account.id
|
|
170
|
+
});
|
|
154
171
|
});
|
|
155
172
|
// The last-admin guard (`ERROR_CANNOT_DELETE_LAST_ADMIN`) is **not**
|
|
156
173
|
// cross-process-testable against this fixture: the per-test keeper
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,
|
|
1
|
+
{"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8BxF,CAAC"}
|
|
@@ -6,22 +6,22 @@ import '../assert_dev_env.js';
|
|
|
6
6
|
* (`{ids} → {actors: [{id, username, display_name?}]}`), not folded into the
|
|
7
7
|
* standard bundle. It's live-mounted on the spine RPC path but kept off the
|
|
8
8
|
* declared surface (`create_spine_surface_spec`) — like cells / ws / sse — so
|
|
9
|
-
* the standard cross suite's generic round-trip never drives it
|
|
10
|
-
*
|
|
9
|
+
* the standard cross suite's generic round-trip never drives it.
|
|
10
|
+
*
|
|
11
|
+
* Its single-request refusals are conformance-table rows on a table given the
|
|
12
|
+
* live mount (`live_rpc_endpoints`): the anonymous 401 a generated auth row,
|
|
13
|
+
* the empty-`ids` 400 a hand-written one. This suite holds what a row can't
|
|
14
|
+
* say, because it names the fixture's own actor:
|
|
11
15
|
*
|
|
12
|
-
* - **anonymous → 401** — the account-grain auth gate refuses an
|
|
13
|
-
* unauthenticated caller before the handler runs.
|
|
14
16
|
* - **keeper resolves own actor → 200** — the populated round trip: the
|
|
15
17
|
* returned row carries the keeper's `id` + `username`, and **no**
|
|
16
18
|
* `account_id` / `email` / timestamp / role field (the wire shape's
|
|
17
19
|
* deliberate info-leak posture). This is the assertion that exercises the
|
|
18
20
|
* Rust row→JSON mapping against the TS canonical shape.
|
|
19
|
-
* - **empty `ids` → 400** — the `min(1)` input bound is enforced on both
|
|
20
|
-
* spines (TS Zod, Rust `parse_ids`).
|
|
21
21
|
*
|
|
22
22
|
* Runs both legs via the shared `{setup_test}` protocol: the in-process leg
|
|
23
23
|
* (`auth/actor_lookup_parity.db.test.ts`, plain `gro test`) and the
|
|
24
|
-
* cross-process leg (`cross_backend/
|
|
24
|
+
* cross-process leg (`cross_backend/spine_suites.cross.test.ts`, the TS spine
|
|
25
25
|
* binaries + Rust `testing_spine_stub` over real HTTP). `actor_lookup` is
|
|
26
26
|
* mounted on every spine, so the suite is ungated.
|
|
27
27
|
*
|
|
@@ -38,15 +38,6 @@ export const describe_actor_lookup_cross_tests = (options) => {
|
|
|
38
38
|
const { setup_test } = options;
|
|
39
39
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
40
40
|
describe('actor_lookup parity', () => {
|
|
41
|
-
test('anonymous → 401 (account-grain auth gate)', async () => {
|
|
42
|
-
const fixture = await setup_test();
|
|
43
|
-
const res = await fixture.fresh_transport()(rpc_path, {
|
|
44
|
-
method: 'POST',
|
|
45
|
-
headers: { 'content-type': 'application/json' },
|
|
46
|
-
body: lookup_envelope([fixture.actor.id], 'anon-lookup')
|
|
47
|
-
});
|
|
48
|
-
assert.strictEqual(res.status, 401, 'unauthenticated actor_lookup must be refused');
|
|
49
|
-
});
|
|
50
41
|
test('keeper resolves own actor → 200 with id + username, no control fields', async () => {
|
|
51
42
|
const fixture = await setup_test();
|
|
52
43
|
const res = await fixture.transport(rpc_path, {
|
|
@@ -67,14 +58,5 @@ export const describe_actor_lookup_cross_tests = (options) => {
|
|
|
67
58
|
assert(!(key in row), `actor_lookup row must not leak '${key}'`);
|
|
68
59
|
}
|
|
69
60
|
});
|
|
70
|
-
test('empty ids → 400 (min(1) input bound)', async () => {
|
|
71
|
-
const fixture = await setup_test();
|
|
72
|
-
const res = await fixture.transport(rpc_path, {
|
|
73
|
-
method: 'POST',
|
|
74
|
-
headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
|
|
75
|
-
body: lookup_envelope([], 'empty-lookup')
|
|
76
|
-
});
|
|
77
|
-
assert.strictEqual(res.status, 400, 'empty ids must fail input validation');
|
|
78
|
-
});
|
|
79
61
|
});
|
|
80
62
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"app_settings.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/app_settings.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,qFAAqF;AACrF,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AAcnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8DxF,CAAC"}
|
|
@@ -22,7 +22,7 @@ import '../assert_dev_env.js';
|
|
|
22
22
|
*
|
|
23
23
|
* Cites `security.md` §Signup. Runs both legs via the shared `{setup_test}`
|
|
24
24
|
* protocol: in-process (`auth/app_settings_parity.db.test.ts`) +
|
|
25
|
-
* cross-process (`cross_backend/
|
|
25
|
+
* cross-process (`cross_backend/spine_suites.cross.test.ts`, TS spine
|
|
26
26
|
* binaries + Rust `testing_spine_stub`). Mounted on every spine, so the
|
|
27
27
|
* suite is ungated.
|
|
28
28
|
*
|
|
@@ -31,9 +31,7 @@ import '../assert_dev_env.js';
|
|
|
31
31
|
import { describe, test, assert } from 'vitest';
|
|
32
32
|
import { app_settings_update_action_spec } from '../../auth/admin_action_specs.js';
|
|
33
33
|
import { ERROR_NO_MATCHING_INVITE } from '../../http/error_schemas.js';
|
|
34
|
-
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
35
|
-
/** REST signup path on the spine surface (`/api/account` prefix + `/signup`). */
|
|
36
|
-
const SIGNUP_PATH = '/api/account/signup';
|
|
34
|
+
import { SPINE_ACCOUNT_SIGNUP_PATH, SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
37
35
|
/** A password that satisfies the creation-strength schema (min 12). */
|
|
38
36
|
const SIGNUP_PASSWORD = 'securepassword123';
|
|
39
37
|
/** Build the JSON-RPC envelope for an `app_settings_update` call. */
|
|
@@ -55,7 +53,7 @@ export const describe_app_settings_cross_tests = (options) => {
|
|
|
55
53
|
body: update_envelope(true, 'enable-open-signup')
|
|
56
54
|
});
|
|
57
55
|
assert.strictEqual(enable.status, 200, 'admin app_settings_update must succeed');
|
|
58
|
-
const res = await fixture.fresh_transport()(
|
|
56
|
+
const res = await fixture.fresh_transport()(SPINE_ACCOUNT_SIGNUP_PATH, {
|
|
59
57
|
method: 'POST',
|
|
60
58
|
headers: { 'content-type': 'application/json' },
|
|
61
59
|
body: JSON.stringify({ username: 'open_signup_user', password: SIGNUP_PASSWORD })
|
|
@@ -80,7 +78,7 @@ export const describe_app_settings_cross_tests = (options) => {
|
|
|
80
78
|
body: update_envelope(false, 'disable-open-signup')
|
|
81
79
|
});
|
|
82
80
|
assert.strictEqual(disable.status, 200, 'admin app_settings_update (disable) must succeed');
|
|
83
|
-
const res = await fixture.fresh_transport()(
|
|
81
|
+
const res = await fixture.fresh_transport()(SPINE_ACCOUNT_SIGNUP_PATH, {
|
|
84
82
|
method: 'POST',
|
|
85
83
|
headers: { 'content-type': 'application/json' },
|
|
86
84
|
body: JSON.stringify({ username: 'closed_signup_user', password: SIGNUP_PASSWORD })
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { AuditLogEventWithUsernamesJson } from '../../auth/audit_log_schema.js';
|
|
3
|
+
import type { FetchTransport } from '../transports/fetch_transport.js';
|
|
4
|
+
/** The fixture members an audit read uses — every `TestFixture` has them. */
|
|
5
|
+
export interface AuditReadFixture {
|
|
6
|
+
readonly fresh_transport: (options?: {
|
|
7
|
+
readonly origin?: string | null;
|
|
8
|
+
}) => FetchTransport;
|
|
9
|
+
/** The keeper's session headers — the keeper holds the admin role the audit read needs. */
|
|
10
|
+
readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
11
|
+
readonly create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
12
|
+
/** Refuse undeclared metadata keys — see `TestFixture.reject_unknown_audit_metadata_keys`. */
|
|
13
|
+
readonly reject_unknown_audit_metadata_keys?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Await the backend's in-flight audit writes via `_testing_drain_effects`, so
|
|
17
|
+
* a following `audit_log_list` sees every row emitted before the call. The
|
|
18
|
+
* backend must mount the action — every spine test binary does; in-process
|
|
19
|
+
* suites mount `create_testing_drain_effects_action`.
|
|
20
|
+
*
|
|
21
|
+
* Refuses an answer without `tracked: true`. A binary whose audit emitter was
|
|
22
|
+
* built without in-flight tracking answers the drain at once, waiting on
|
|
23
|
+
* nothing, and its audit reads race the writes — an absent row then reads as
|
|
24
|
+
* a pass. Every audit read runs through here, so each one checks the barrier
|
|
25
|
+
* is live.
|
|
26
|
+
*
|
|
27
|
+
* @param fixture - the per-test fixture, whose daemon token authorizes the drain
|
|
28
|
+
* @param rpc_path - the RPC endpoint the drain action is mounted on
|
|
29
|
+
*/
|
|
30
|
+
export declare const cross_drain_effects: (fixture: AuditReadFixture, rpc_path?: string) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Assert an audit row's `metadata` carries `expected` — each field compared
|
|
33
|
+
* on its own, so a consumer's added key passes — and none of `absent`. When
|
|
34
|
+
* the fixture's setup closed the world (`reject_unknown_audit_metadata_keys`),
|
|
35
|
+
* the whole object must equal `expected`, so an undeclared key fails too.
|
|
36
|
+
*
|
|
37
|
+
* @param fixture - the per-test fixture, whose flag picks the comparison
|
|
38
|
+
* @param metadata - the row's `metadata`
|
|
39
|
+
* @param expected - the declared fields and their values
|
|
40
|
+
* @param absent - keys the row must not carry, e.g. an optional field left unset
|
|
41
|
+
*/
|
|
42
|
+
export declare const assert_audit_metadata_fields: (fixture: Pick<AuditReadFixture, "reject_unknown_audit_metadata_keys">, metadata: Record<string, unknown> | null, expected: Record<string, unknown>, absent?: ReadonlyArray<string>) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Drain the backend's audit writes (`cross_drain_effects`), then list audit rows as
|
|
45
|
+
* the keeper, parse them against `AuditLogListOutput`, and parse each row's
|
|
46
|
+
* `metadata` against its event type's schema (`assert_audit_event_metadata`),
|
|
47
|
+
* with unknown keys refused when `fixture.reject_unknown_audit_metadata_keys`
|
|
48
|
+
* is set. The wire schema leaves `metadata` a loose record, so without this a
|
|
49
|
+
* spine's metadata drift reaches no assertion.
|
|
50
|
+
*
|
|
51
|
+
* @param fixture - the per-test fixture; the list runs on the keeper's session
|
|
52
|
+
* @param params - `audit_log_list` params, e.g. `{event_type: 'login', outcome: 'failure'}`
|
|
53
|
+
* @param rpc_path - the RPC endpoint both actions are mounted on
|
|
54
|
+
* @returns the listed events
|
|
55
|
+
*/
|
|
56
|
+
export declare const list_audit_events: (fixture: AuditReadFixture, params: Record<string, unknown>, rpc_path?: string) => Promise<Array<AuditLogEventWithUsernamesJson>>;
|
|
57
|
+
//# sourceMappingURL=audit_cross_helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/audit_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsB9B,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAErF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAIvE,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,cAAc,CAAC;IAC5F,2FAA2F;IAC3F,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5F,QAAQ,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjG,8FAA8F;IAC9F,QAAQ,CAAC,kCAAkC,CAAC,EAAE,OAAO,CAAC;CACtD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,SAAS,gBAAgB,EACzB,WAAU,MAAuB,KAC/B,OAAO,CAAC,IAAI,CAmBd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,IAAI,CAAC,gBAAgB,EAAE,oCAAoC,CAAC,EACrE,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACxC,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAQ,aAAa,CAAC,MAAM,CAAM,KAChC,IASF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAC7B,SAAS,gBAAgB,EACzB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAU,MAAuB,KAC/B,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAe/C,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Audit-row reads for the cross-backend suites, behind the
|
|
4
|
+
* `_testing_drain_effects` barrier.
|
|
5
|
+
*
|
|
6
|
+
* Both spines write audit rows off the request path — a WS mutation is
|
|
7
|
+
* answered before its effects flush, and the Rust spine's emits are
|
|
8
|
+
* fire-and-forget — so a suite that reads `audit_log_list` right after the
|
|
9
|
+
* call it audits can miss the row, and an assertion that a row is absent then
|
|
10
|
+
* passes vacuously. `list_audit_events` drains before it lists, so no reader
|
|
11
|
+
* can skip the barrier, and holds every row it lists to its event type's
|
|
12
|
+
* metadata schema (`assert_audit_event_metadata`), so every reader checks the
|
|
13
|
+
* metadata of the rows it reads, on every spine — closed to undeclared keys
|
|
14
|
+
* when the fixture's setup asked for it.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { assert } from 'vitest';
|
|
19
|
+
import { AuditLogListOutput } from '../../auth/admin_action_specs.js';
|
|
20
|
+
import { assert_audit_event_metadata } from '../audit_drift_guard.js';
|
|
21
|
+
import { cross_rpc_call, expect_output } from './cross_rpc.js';
|
|
22
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
23
|
+
/**
|
|
24
|
+
* Await the backend's in-flight audit writes via `_testing_drain_effects`, so
|
|
25
|
+
* a following `audit_log_list` sees every row emitted before the call. The
|
|
26
|
+
* backend must mount the action — every spine test binary does; in-process
|
|
27
|
+
* suites mount `create_testing_drain_effects_action`.
|
|
28
|
+
*
|
|
29
|
+
* Refuses an answer without `tracked: true`. A binary whose audit emitter was
|
|
30
|
+
* built without in-flight tracking answers the drain at once, waiting on
|
|
31
|
+
* nothing, and its audit reads race the writes — an absent row then reads as
|
|
32
|
+
* a pass. Every audit read runs through here, so each one checks the barrier
|
|
33
|
+
* is live.
|
|
34
|
+
*
|
|
35
|
+
* @param fixture - the per-test fixture, whose daemon token authorizes the drain
|
|
36
|
+
* @param rpc_path - the RPC endpoint the drain action is mounted on
|
|
37
|
+
*/
|
|
38
|
+
export const cross_drain_effects = async (fixture, rpc_path = SPINE_RPC_PATH) => {
|
|
39
|
+
const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
|
|
40
|
+
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
41
|
+
const tracked = typeof drained.result === 'object' && drained.result !== null
|
|
42
|
+
? drained.result.tracked
|
|
43
|
+
: undefined;
|
|
44
|
+
assert.strictEqual(tracked, true, "_testing_drain_effects must answer tracked: true — the backend's audit emitter does not " +
|
|
45
|
+
`track in-flight writes, so the drain waits on nothing (got ${JSON.stringify(drained.result)})`);
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Assert an audit row's `metadata` carries `expected` — each field compared
|
|
49
|
+
* on its own, so a consumer's added key passes — and none of `absent`. When
|
|
50
|
+
* the fixture's setup closed the world (`reject_unknown_audit_metadata_keys`),
|
|
51
|
+
* the whole object must equal `expected`, so an undeclared key fails too.
|
|
52
|
+
*
|
|
53
|
+
* @param fixture - the per-test fixture, whose flag picks the comparison
|
|
54
|
+
* @param metadata - the row's `metadata`
|
|
55
|
+
* @param expected - the declared fields and their values
|
|
56
|
+
* @param absent - keys the row must not carry, e.g. an optional field left unset
|
|
57
|
+
*/
|
|
58
|
+
export const assert_audit_metadata_fields = (fixture, metadata, expected, absent = []) => {
|
|
59
|
+
assert.ok(metadata, 'the audit row carries metadata');
|
|
60
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
61
|
+
assert.deepStrictEqual(metadata[key], value, `metadata.${key}`);
|
|
62
|
+
}
|
|
63
|
+
for (const key of absent)
|
|
64
|
+
assert.notProperty(metadata, key, `metadata.${key} is absent`);
|
|
65
|
+
if (fixture.reject_unknown_audit_metadata_keys) {
|
|
66
|
+
assert.deepStrictEqual(metadata, expected, 'metadata carries only the declared fields');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Drain the backend's audit writes (`cross_drain_effects`), then list audit rows as
|
|
71
|
+
* the keeper, parse them against `AuditLogListOutput`, and parse each row's
|
|
72
|
+
* `metadata` against its event type's schema (`assert_audit_event_metadata`),
|
|
73
|
+
* with unknown keys refused when `fixture.reject_unknown_audit_metadata_keys`
|
|
74
|
+
* is set. The wire schema leaves `metadata` a loose record, so without this a
|
|
75
|
+
* spine's metadata drift reaches no assertion.
|
|
76
|
+
*
|
|
77
|
+
* @param fixture - the per-test fixture; the list runs on the keeper's session
|
|
78
|
+
* @param params - `audit_log_list` params, e.g. `{event_type: 'login', outcome: 'failure'}`
|
|
79
|
+
* @param rpc_path - the RPC endpoint both actions are mounted on
|
|
80
|
+
* @returns the listed events
|
|
81
|
+
*/
|
|
82
|
+
export const list_audit_events = async (fixture, params, rpc_path = SPINE_RPC_PATH) => {
|
|
83
|
+
await cross_drain_effects(fixture, rpc_path);
|
|
84
|
+
const { events } = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, 'audit_log_list', params, fixture.create_session_headers()), AuditLogListOutput);
|
|
85
|
+
const reject_unknown_keys = fixture.reject_unknown_audit_metadata_keys ?? false;
|
|
86
|
+
for (const event of events)
|
|
87
|
+
assert_audit_event_metadata(event, { reject_unknown_keys });
|
|
88
|
+
return events;
|
|
89
|
+
};
|
|
@@ -44,7 +44,8 @@ export interface BackendBootstrapConfig {
|
|
|
44
44
|
* (env: `*_DAEMON_TOKEN_PATH`). `spawn_backend` reads this file once
|
|
45
45
|
* after the health probe succeeds and threads the token onto
|
|
46
46
|
* `BackendHandle.daemon_token` for `_testing_reset` calls plus any
|
|
47
|
-
* other admin/keeper-gated cross-process tests.
|
|
47
|
+
* other admin/keeper-gated cross-process tests. `spawn_backend` deletes
|
|
48
|
+
* the file before each spawn, so a token a previous run left is never read.
|
|
48
49
|
*/
|
|
49
50
|
readonly daemon_token_path: string;
|
|
50
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B
|
|
1
|
+
{"version":3,"file":"backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C"}
|
|
@@ -20,6 +20,12 @@ export interface BodySizeCrossTestOptions extends RpcPathCrossSuiteOptions {
|
|
|
20
20
|
* time.
|
|
21
21
|
*/
|
|
22
22
|
readonly base_url: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* The backend's request-body cap on `rpc_path`, in bytes — the boundary the
|
|
25
|
+
* cases probe. Default `SPINE_BODY_LIMIT_DEFAULT_BYTES`; pass the backend's
|
|
26
|
+
* value when it caps its RPC path elsewhere.
|
|
27
|
+
*/
|
|
28
|
+
readonly rpc_max_body_bytes?: number;
|
|
23
29
|
}
|
|
24
30
|
export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
|
|
25
31
|
//# sourceMappingURL=body_size.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiG9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACzE;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACrC;AA8FD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAkIlF,CAAC"}
|