@fuzdev/fuz_app 0.146.1 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +6 -4
- package/dist/actions/action_codegen.d.ts +15 -1
- package/dist/actions/action_codegen.d.ts.map +1 -1
- package/dist/actions/action_codegen.js +23 -2
- package/dist/actions/perform_action.js +1 -1
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/bootstrap_account.d.ts +2 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +17 -6
- package/dist/auth/cell_action_specs.d.ts +3 -1
- package/dist/auth/cell_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_action_specs.js +5 -5
- package/dist/auth/cell_actions.d.ts.map +1 -1
- package/dist/auth/cell_actions.js +2 -3
- package/dist/auth/cell_data_schema.d.ts +1 -1
- package/dist/auth/cell_data_schema.js +1 -1
- package/dist/auth/cell_field_action_specs.d.ts +4 -3
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_field_action_specs.js +5 -6
- package/dist/auth/cell_field_actions.d.ts.map +1 -1
- package/dist/auth/cell_field_actions.js +8 -1
- package/dist/auth/cell_item_action_specs.d.ts +4 -2
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
- package/dist/auth/cell_item_action_specs.js +5 -5
- package/dist/auth/cell_item_actions.d.ts.map +1 -1
- package/dist/auth/cell_item_actions.js +8 -1
- package/dist/auth/keyring.d.ts +35 -38
- package/dist/auth/keyring.d.ts.map +1 -1
- package/dist/auth/keyring.js +43 -50
- package/dist/auth/password_argon2.d.ts.map +1 -1
- package/dist/auth/password_argon2.js +11 -8
- package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +3 -1
- package/dist/auth/session_cookie.d.ts.map +1 -1
- package/dist/auth/session_cookie.js +5 -2
- package/dist/db/CLAUDE.md +18 -16
- package/dist/db/cell_queries.d.ts +3 -2
- package/dist/db/cell_queries.d.ts.map +1 -1
- package/dist/db/cell_queries.js +3 -2
- package/dist/db/fact_disk_storage.d.ts +15 -15
- package/dist/db/fact_disk_storage.d.ts.map +1 -1
- package/dist/db/fact_disk_storage.js +22 -28
- package/dist/db/fact_queries.d.ts +2 -3
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +2 -3
- package/dist/db/fact_store.d.ts +20 -18
- package/dist/db/fact_store.d.ts.map +1 -1
- package/dist/db/fact_store.js +30 -44
- package/dist/db/file_fact_url.d.ts +19 -4
- package/dist/db/file_fact_url.d.ts.map +1 -1
- package/dist/db/file_fact_url.js +19 -4
- package/dist/primitive_schemas.d.ts +1 -1
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +2 -1
- package/dist/schema_meta.d.ts +20 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +7 -1
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +2 -2
- package/dist/server/serve_fact_route.d.ts +9 -5
- package/dist/server/serve_fact_route.d.ts.map +1 -1
- package/dist/server/serve_fact_route.js +15 -7
- package/dist/testing/CLAUDE.md +737 -114
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +73 -5
- package/dist/testing/adversarial_input.d.ts +0 -47
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +4 -340
- package/dist/testing/adversarial_input_cases.d.ts +64 -0
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
- package/dist/testing/adversarial_input_cases.js +348 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +2 -2
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +24 -20
- package/dist/testing/audit_drift_guard.d.ts +27 -1
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +50 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +29 -12
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_lookup.js +7 -25
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
- package/dist/testing/cross_backend/app_settings.js +4 -6
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
- package/dist/testing/cross_backend/backend_config.d.ts +2 -1
- package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +6 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.js +26 -44
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
- package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
- package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
- package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +55 -59
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +20 -23
- package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
- package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
- package/dist/testing/cross_backend/cargo_binary.js +148 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +57 -1
- package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
- package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_gated_create.js +9 -487
- package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +22 -12
- package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
- package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_moderate.js +511 -0
- package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +40 -19
- package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_case.js +29 -1
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
- package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +297 -65
- package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
- package/dist/testing/cross_backend/cookie_attributes.js +3 -2
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
- package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
- package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_deadline.js +32 -31
- package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
- package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
- package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
- package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +97 -91
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +2 -2
- package/dist/testing/cross_backend/differential.d.ts +126 -0
- package/dist/testing/cross_backend/differential.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential.js +201 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
- package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
- package/dist/testing/cross_backend/differential_normalize.js +153 -0
- package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- package/dist/testing/cross_backend/fact_serving.js +41 -7
- package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
- package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
- package/dist/testing/cross_backend/full_spine_mount.js +55 -1
- package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
- package/dist/testing/cross_backend/identity_parity.js +6 -5
- package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +10 -7
- package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
- package/dist/testing/cross_backend/login_security.js +13 -18
- package/dist/testing/cross_backend/origin.js +1 -1
- package/dist/testing/cross_backend/origin_conformance.js +1 -1
- package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
- package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
- package/dist/testing/cross_backend/origin_scope.js +13 -12
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
- package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
- package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
- package/dist/testing/cross_backend/postgres_database.js +140 -0
- package/dist/testing/cross_backend/provided_context.d.ts +28 -0
- package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
- package/dist/testing/cross_backend/provided_context.js +35 -0
- package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
- package/dist/testing/cross_backend/query_shape.js +10 -12
- package/dist/testing/cross_backend/ready.js +2 -2
- package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
- package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
- package/dist/testing/cross_backend/revocation_drivers.js +1 -1
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
- package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
- package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
- package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
- package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_participation.js +32 -21
- package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
- package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
- package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
- package/dist/testing/cross_backend/session_cap.d.ts +9 -8
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +9 -4
- package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
- package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
- package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
- package/dist/testing/cross_backend/setup.d.ts +56 -54
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +29 -29
- package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
- package/dist/testing/cross_backend/simple_request.js +4 -4
- package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
- package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
- package/dist/testing/cross_backend/spawn_backend.js +219 -60
- package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_suites.js +626 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +3 -4
- package/dist/testing/cross_backend/standard.d.ts +18 -1
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +2 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
- package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +7 -10
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +10 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +21 -12
- package/dist/testing/cross_backend/testing_server_bun.js +1 -1
- package/dist/testing/cross_backend/token_lifetime.js +1 -1
- package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/token_scope_surface.js +9 -13
- package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
- package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
- package/dist/testing/cross_backend/transport_composition.js +81 -51
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
- package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
- package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
- package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_endpoint.js +54 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
- package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
- package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
- package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_role_gated.js +12 -21
- package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +29 -34
- package/dist/testing/db_entities.d.ts +16 -3
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +28 -5
- package/dist/testing/integration_helpers.d.ts +61 -2
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +75 -18
- package/dist/testing/round_trip.d.ts +17 -0
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +67 -24
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +13 -138
- package/dist/testing/rpc_envelope_cases.d.ts +111 -0
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
- package/dist/testing/rpc_envelope_cases.js +240 -0
- package/dist/testing/rpc_helpers.d.ts +31 -0
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +35 -14
- package/dist/testing/rpc_round_trip.d.ts +62 -6
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +151 -39
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +22 -2
- package/dist/testing/standard.d.ts +26 -0
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +4 -1
- package/dist/testing/transports/raw_http.d.ts +21 -0
- package/dist/testing/transports/raw_http.d.ts.map +1 -1
- package/dist/testing/transports/raw_http.js +30 -0
- package/package.json +10 -8
- package/dist/testing/cross_backend/actor_search.d.ts +0 -6
- package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
- package/dist/testing/cross_backend/actor_search.js +0 -90
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
|
@@ -32,7 +32,8 @@ import '../assert_dev_env.js';
|
|
|
32
32
|
import { assert, describe, test } from 'vitest';
|
|
33
33
|
import { ERROR_ROLE_NOT_WEB_GRANTABLE } from '../../http/error_schemas.js';
|
|
34
34
|
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
35
|
-
import {
|
|
35
|
+
import { list_audit_events } from './audit_cross_helpers.js';
|
|
36
|
+
import { cross_rpc_call, error_reason } from './cross_rpc.js';
|
|
36
37
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
37
38
|
const NIL_UUID = '00000000-0000-0000-0000-000000000000';
|
|
38
39
|
/**
|
|
@@ -63,7 +64,7 @@ const GRANT_EVENT_TYPES = ['role_grant_create', 'role_grant_offer_create'];
|
|
|
63
64
|
export const describe_role_grant_role_grammar_cross_tests = (options) => {
|
|
64
65
|
const { setup_test } = options;
|
|
65
66
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
66
|
-
describe('grant verbs refuse an off-grammar role at validation',
|
|
67
|
+
describe('grant verbs refuse an off-grammar role at validation', () => {
|
|
67
68
|
for (const method of GRANT_METHODS) {
|
|
68
69
|
test(`${method}: an off-grammar role is invalid_params with no reason`, async () => {
|
|
69
70
|
const fixture = await setup_test();
|
|
@@ -94,14 +95,10 @@ export const describe_role_grant_role_grammar_cross_tests = (options) => {
|
|
|
94
95
|
// and the length cap is inclusive: a role exactly at it is on the grammar
|
|
95
96
|
const at_length = await cross_rpc_call(t, rpc_path, 'role_grant_assign', { to_account_id: NIL_UUID, role: AT_LENGTH_ROLE }, session);
|
|
96
97
|
assert.strictEqual(error_reason(at_length), ERROR_ROLE_NOT_WEB_GRANTABLE, 'a role at the length cap passes validation');
|
|
97
|
-
//
|
|
98
|
-
// the
|
|
99
|
-
const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
|
|
100
|
-
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
98
|
+
// both spines write audit rows off the request path, so each read is
|
|
99
|
+
// behind the drain barrier
|
|
101
100
|
for (const event_type of GRANT_EVENT_TYPES) {
|
|
102
|
-
const
|
|
103
|
-
assert.ok(listed.ok, `audit_log_list failed: ${JSON.stringify(listed.error)}`);
|
|
104
|
-
const events = listed.result.events;
|
|
101
|
+
const events = await list_audit_events(fixture, { event_type }, rpc_path);
|
|
105
102
|
const roles = events.map((event) => event.metadata?.role);
|
|
106
103
|
for (const role of roles) {
|
|
107
104
|
assert.ok(typeof role !== 'string' || !OFF_GRAMMAR_ROLES.includes(role), `${event_type}: an audit row records the off-grammar role ${JSON.stringify(role)}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { type AppSurfaceSpec, type RpcEndpointSpec } from '../../http/surface.js';
|
|
3
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
4
|
+
/** Options for `describe_rpc_adversarial_cross_tests`. */
|
|
5
|
+
export interface RpcAdversarialCrossTestOptions extends RpcPathCrossSuiteOptions {
|
|
6
|
+
/** The declared surface; the endpoint at `rpc_path` supplies the methods and their input schemas. */
|
|
7
|
+
readonly surface_source: AppSurfaceSpec;
|
|
8
|
+
/**
|
|
9
|
+
* The endpoints the backend live-mounts (resolved —
|
|
10
|
+
* `resolve_rpc_endpoints_for_setup` turns the factory form into this). The
|
|
11
|
+
* methods the one at `rpc_path` carries beyond the declared surface run
|
|
12
|
+
* through both groups too. Omitted, only declared methods run.
|
|
13
|
+
*/
|
|
14
|
+
readonly live_rpc_endpoints?: ReadonlyArray<RpcEndpointSpec>;
|
|
15
|
+
/** Methods to leave out, beyond the protocol actions always skipped. */
|
|
16
|
+
readonly skip_methods?: ReadonlyArray<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Generated params cases a backend is known to answer otherwise, each run
|
|
19
|
+
* as its own `xfail_until` test; the method's other cases keep their test.
|
|
20
|
+
* Pass it only for the backend that diverges.
|
|
21
|
+
*/
|
|
22
|
+
readonly xfail_params?: ReadonlyArray<RpcParamsXfail>;
|
|
23
|
+
}
|
|
24
|
+
/** Generated params cases of one method marked `xfail_until`, by case label. */
|
|
25
|
+
export interface RpcParamsXfail {
|
|
26
|
+
readonly method: string;
|
|
27
|
+
/** `InputTestCase.label`s of `generate_rpc_params_test_cases(method's input)`. */
|
|
28
|
+
readonly labels: ReadonlyArray<string>;
|
|
29
|
+
readonly tracking_id: string;
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run the cross-process malformed-envelope and invalid-params suite.
|
|
34
|
+
*
|
|
35
|
+
* @param options - the fixture, the declared surface and live mount, the RPC path, and methods to skip
|
|
36
|
+
*/
|
|
37
|
+
export declare const describe_rpc_adversarial_cross_tests: (options: RpcAdversarialCrossTestOptions) => void;
|
|
38
|
+
//# sourceMappingURL=rpc_adversarial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc_adversarial.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_adversarial.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyE9B,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAoB/B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAKxE,0DAA0D;AAC1D,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC/E,qGAAqG;IACrG,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACtD;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAmGD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,8BAA8B,KACrC,IA8JF,CAAC"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-process malformed-envelope and invalid-params suite for a JSON-RPC
|
|
4
|
+
* endpoint.
|
|
5
|
+
*
|
|
6
|
+
* `describe_rpc_attack_surface_tests` runs its envelope and params groups
|
|
7
|
+
* in-process against stub apps, so they never reach a spawned backend — and
|
|
8
|
+
* the Rust spine parses envelopes and params by hand, while the
|
|
9
|
+
* action-manifest parity gate compares declarations and is blind to param
|
|
10
|
+
* schemas. This suite sends the same requests over `fixture.transport` to
|
|
11
|
+
* each backend.
|
|
12
|
+
*
|
|
13
|
+
* Two groups:
|
|
14
|
+
*
|
|
15
|
+
* - **Envelopes** — `create_rpc_envelope_cases`, the in-process group's set:
|
|
16
|
+
* non-JSON body, wrong or missing `jsonrpc`, missing `method` or `id`, a
|
|
17
|
+
* batch array, non-object POST params, an unknown method, `GET` missing
|
|
18
|
+
* `method` or `id`, `GET` invalid-JSON params, `GET` non-object params on
|
|
19
|
+
* every read, and a `GET` of a mutating method.
|
|
20
|
+
* Each is sent by the principal its method's auth admits, so the envelope
|
|
21
|
+
* check is what refuses, and each pins status, JSON-RPC code, the echoed
|
|
22
|
+
* `id`, and `data.reason` (exact where the TS dispatcher sets one, absent
|
|
23
|
+
* otherwise). Three ordering cases go without a credential: an anonymous
|
|
24
|
+
* `GET` of a mutating method is refused `invalid_request` rather than 401
|
|
25
|
+
* (the read restriction runs after the method lookup and before
|
|
26
|
+
* authorization), an anonymous batch array is refused `invalid_request`
|
|
27
|
+
* with `id: null` (the envelope parse runs before auth, and batches are
|
|
28
|
+
* unsupported), and an anonymous `GET` with non-object params is the 401
|
|
29
|
+
* (params validation runs after authorization).
|
|
30
|
+
* - **Params** — for each declared method (and each live-mounted one, below),
|
|
31
|
+
* `generate_rpc_params_test_cases`
|
|
32
|
+
* (the in-process generator minus its structural cases), each sent as the
|
|
33
|
+
* method's authorized principal and owing 400 `invalid_params` with no
|
|
34
|
+
* `data.reason`. One test per method, reporting every case that answers
|
|
35
|
+
* otherwise; `xfail_params` runs named cases of a method as their own
|
|
36
|
+
* `xfail_until` tests, for a known divergence on one backend.
|
|
37
|
+
*
|
|
38
|
+
* The authorized principal is the keeper on the first channel the method's
|
|
39
|
+
* auth admits: its session, else its daemon token or API token over a
|
|
40
|
+
* no-`Origin` transport (`resolve_conformance_principal`), mirroring the
|
|
41
|
+
* in-process `select_auth_app`. **Preconditions:** the keeper holds every
|
|
42
|
+
* role a declared method gates on (`default_cross_process_setup` seeds
|
|
43
|
+
* `keeper` and `admin`; pass `extra_keeper_roles` for others), and it has a
|
|
44
|
+
* single actor. No generated body carries a valid `acting` — the generator's
|
|
45
|
+
* base fills only required and defaulted fields — and a malformed `acting` is
|
|
46
|
+
* read as omitted ahead of validation on both spines, so a single-actor
|
|
47
|
+
* keeper resolves its actor and validation is what refuses. A keeper with a
|
|
48
|
+
* second actor answers `actor_required` first; such a suite lists the
|
|
49
|
+
* actor-required methods in `skip_methods`.
|
|
50
|
+
*
|
|
51
|
+
* **Off the declared surface.** `live_rpc_endpoints` adds the methods the
|
|
52
|
+
* backend live-mounts at `rpc_path` beyond the declared surface — cell verbs,
|
|
53
|
+
* opt-in resolvers, the `_testing_*` backdoors. Both groups run over them too:
|
|
54
|
+
* the envelope cases built from those methods, in a group of their own, and a
|
|
55
|
+
* params test per method, `_testing_*` included (a daemon-only backdoor is
|
|
56
|
+
* sent with the keeper's daemon token). A spine that keeps a method family off
|
|
57
|
+
* its declared surface still owes the twin's answer to bad params there.
|
|
58
|
+
*
|
|
59
|
+
* Skipped: `_testing_*` methods on the declared surface (a declared surface
|
|
60
|
+
* lists them only by accident; the live mount is where they are walked), and
|
|
61
|
+
* the protocol actions (`PROTOCOL_ACTION_METHODS`, fixed shared specs covered
|
|
62
|
+
* by the WS suites, which `generate_rpc_auth_conformance_cases` skips too).
|
|
63
|
+
*
|
|
64
|
+
* Cross-process only: in-process, `describe_rpc_attack_surface_tests` runs
|
|
65
|
+
* the same envelope cases and params generator.
|
|
66
|
+
*
|
|
67
|
+
* @module
|
|
68
|
+
*/
|
|
69
|
+
import { assert, describe, test } from 'vitest';
|
|
70
|
+
import { generate_app_surface } from '../../http/surface.js';
|
|
71
|
+
import { is_public_auth } from '../../http/auth_shape.js';
|
|
72
|
+
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
73
|
+
import { BUILTIN_CREDENTIAL_TYPES, CREDENTIAL_TYPE_API_TOKEN, CREDENTIAL_TYPE_DAEMON_TOKEN, CREDENTIAL_TYPE_SESSION } from '../../auth/credential_type_schema.js';
|
|
74
|
+
import { PROTOCOL_ACTION_METHODS } from '../../actions/action_codegen.js';
|
|
75
|
+
import { TESTING_METHOD_PREFIX } from '../surface_invariants.js';
|
|
76
|
+
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
77
|
+
import { assert_rpc_envelope_refusal, create_rpc_envelope_cases, generate_rpc_params_test_cases, to_rpc_envelope_request } from '../rpc_envelope_cases.js';
|
|
78
|
+
import { resolve_conformance_principal } from './conformance_table.js';
|
|
79
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
80
|
+
import { xfail_until } from './xfail.js';
|
|
81
|
+
const PROTOCOL_METHODS = new Set(PROTOCOL_ACTION_METHODS);
|
|
82
|
+
/** The keeper principal on each builtin credential channel. */
|
|
83
|
+
const CHANNEL_PRINCIPALS = {
|
|
84
|
+
[CREDENTIAL_TYPE_SESSION]: 'keeper',
|
|
85
|
+
[CREDENTIAL_TYPE_DAEMON_TOKEN]: 'daemon',
|
|
86
|
+
[CREDENTIAL_TYPE_API_TOKEN]: 'token'
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The principal a method's auth admits: the keeper's session where the
|
|
90
|
+
* method takes one, else the first builtin channel its credential gate
|
|
91
|
+
* names — the choice the in-process `select_auth_app` makes, minus its throw
|
|
92
|
+
* on a non-session channel paired with a role other than keeper (the keeper
|
|
93
|
+
* holds every builtin role, so sending its token there still passes the gate).
|
|
94
|
+
*
|
|
95
|
+
* @param auth - the method's declared auth
|
|
96
|
+
* @returns the principal to send as
|
|
97
|
+
*/
|
|
98
|
+
const to_authorized_principal = (auth) => {
|
|
99
|
+
if (is_public_auth(auth))
|
|
100
|
+
return 'anonymous';
|
|
101
|
+
const credential_types = auth.credential_types;
|
|
102
|
+
if (!credential_types?.length || credential_types.includes(CREDENTIAL_TYPE_SESSION)) {
|
|
103
|
+
return 'keeper';
|
|
104
|
+
}
|
|
105
|
+
const channel = BUILTIN_CREDENTIAL_TYPES.find((t) => credential_types.includes(t));
|
|
106
|
+
const principal = channel === undefined ? undefined : CHANNEL_PRINCIPALS[channel];
|
|
107
|
+
if (!principal) {
|
|
108
|
+
throw new Error(`rpc_adversarial: no builtin channel admits credential types '${credential_types.join("', '")}'`);
|
|
109
|
+
}
|
|
110
|
+
return principal;
|
|
111
|
+
};
|
|
112
|
+
const send_as = async (fixture, auth, as, url, init) => {
|
|
113
|
+
const principal = as === 'anonymous' ? 'anonymous' : to_authorized_principal(auth);
|
|
114
|
+
const { transport, headers } = await resolve_conformance_principal(fixture, principal);
|
|
115
|
+
const res = await transport(url, {
|
|
116
|
+
...init,
|
|
117
|
+
headers: { ...init.headers, ...headers }
|
|
118
|
+
});
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
let body;
|
|
121
|
+
try {
|
|
122
|
+
body = JSON.parse(text);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
body = text;
|
|
126
|
+
}
|
|
127
|
+
return { status: res.status, body };
|
|
128
|
+
};
|
|
129
|
+
const reason_of = (body) => {
|
|
130
|
+
const data = body?.error?.data;
|
|
131
|
+
return typeof data === 'object' && data !== null
|
|
132
|
+
? data.reason
|
|
133
|
+
: undefined;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The live-mounted actions at `rpc_path` the declared endpoint lacks, each
|
|
137
|
+
* method once, in mount order, with their surface form.
|
|
138
|
+
*/
|
|
139
|
+
const to_off_surface = (rpc_path, declared, live_rpc_endpoints) => {
|
|
140
|
+
const seen = new Set(declared);
|
|
141
|
+
const actions = [];
|
|
142
|
+
for (const live of live_rpc_endpoints ?? []) {
|
|
143
|
+
if (live.path !== rpc_path)
|
|
144
|
+
continue;
|
|
145
|
+
for (const action of live.actions) {
|
|
146
|
+
if (seen.has(action.spec.method))
|
|
147
|
+
continue;
|
|
148
|
+
seen.add(action.spec.method);
|
|
149
|
+
actions.push(action);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (actions.length === 0)
|
|
153
|
+
return null;
|
|
154
|
+
const surface = generate_app_surface({
|
|
155
|
+
route_specs: [],
|
|
156
|
+
middleware_specs: [],
|
|
157
|
+
rpc_endpoints: [{ path: rpc_path, actions }]
|
|
158
|
+
});
|
|
159
|
+
const endpoint = surface.rpc_endpoints[0];
|
|
160
|
+
if (!endpoint)
|
|
161
|
+
throw new Error(`rpc_adversarial: no surface for the live mount at '${rpc_path}'`);
|
|
162
|
+
return { actions, endpoint };
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Run the cross-process malformed-envelope and invalid-params suite.
|
|
166
|
+
*
|
|
167
|
+
* @param options - the fixture, the declared surface and live mount, the RPC path, and methods to skip
|
|
168
|
+
*/
|
|
169
|
+
export const describe_rpc_adversarial_cross_tests = (options) => {
|
|
170
|
+
const { setup_test, surface_source } = options;
|
|
171
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
172
|
+
const skip = new Set(options.skip_methods);
|
|
173
|
+
const endpoint = surface_source.surface.rpc_endpoints.find((e) => e.path === rpc_path);
|
|
174
|
+
const endpoint_spec = surface_source.rpc_endpoints.find((e) => e.path === rpc_path);
|
|
175
|
+
const off_surface = endpoint
|
|
176
|
+
? to_off_surface(rpc_path,
|
|
177
|
+
// a `_testing_*` method listed on the declared surface is skipped there,
|
|
178
|
+
// so it must stay eligible here, where the live mount walks it
|
|
179
|
+
new Set(endpoint.methods
|
|
180
|
+
.map((m) => m.name)
|
|
181
|
+
.filter((name) => !name.startsWith(TESTING_METHOD_PREFIX))), options.live_rpc_endpoints)
|
|
182
|
+
: null;
|
|
183
|
+
describe('RPC adversarial envelopes (cross-process)', () => {
|
|
184
|
+
if (!endpoint || !endpoint_spec) {
|
|
185
|
+
test('the RPC endpoint is on the declared surface', () => {
|
|
186
|
+
assert.fail(`no RPC endpoint at '${rpc_path}' on the declared surface`);
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const cases = create_rpc_envelope_cases(endpoint, { skip_methods: options.skip_methods });
|
|
191
|
+
test('the envelope cases cover the endpoint', () => {
|
|
192
|
+
// both halves need a read and a mutation to choose; a surface
|
|
193
|
+
// lacking either loses cases silently otherwise
|
|
194
|
+
assert.ok(cases.some((c) => !c.method.side_effects), 'no read method to carry the POST and GET envelope cases');
|
|
195
|
+
assert.ok(cases.some((c) => c.method.side_effects), 'no authenticated mutation to carry the GET read-restriction cases');
|
|
196
|
+
});
|
|
197
|
+
for (const c of cases) {
|
|
198
|
+
test(c.name, async () => {
|
|
199
|
+
const fixture = await setup_test();
|
|
200
|
+
const { url, init } = to_rpc_envelope_request(rpc_path, c);
|
|
201
|
+
const { status, body } = await send_as(fixture, c.method.auth, c.as, url, init);
|
|
202
|
+
assert_rpc_envelope_refusal(status, body, c);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (off_surface) {
|
|
207
|
+
describe('RPC adversarial envelopes, live-mounted methods off the declared surface (cross-process)', () => {
|
|
208
|
+
for (const c of create_rpc_envelope_cases(off_surface.endpoint, {
|
|
209
|
+
skip_methods: options.skip_methods
|
|
210
|
+
})) {
|
|
211
|
+
test(c.name, async () => {
|
|
212
|
+
const fixture = await setup_test();
|
|
213
|
+
const { url, init } = to_rpc_envelope_request(rpc_path, c);
|
|
214
|
+
const { status, body } = await send_as(fixture, c.method.auth, c.as, url, init);
|
|
215
|
+
assert_rpc_envelope_refusal(status, body, c);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
describe('RPC adversarial params (cross-process)', () => {
|
|
221
|
+
if (!endpoint || !endpoint_spec)
|
|
222
|
+
return;
|
|
223
|
+
let total = 0;
|
|
224
|
+
const xfails = new Map();
|
|
225
|
+
for (const x of options.xfail_params ?? []) {
|
|
226
|
+
for (const label of x.labels)
|
|
227
|
+
xfails.set(`${x.method}\0${label}`, x);
|
|
228
|
+
}
|
|
229
|
+
const matched_xfails = new Set();
|
|
230
|
+
/** Send one case; a refusal message when it is not the canonical 400. */
|
|
231
|
+
const check_case = async (fixture, action, tc) => {
|
|
232
|
+
const { status, body } = await send_as(fixture, action.spec.auth, 'authorized', rpc_path, create_rpc_post_init(action.spec.method, tc.body));
|
|
233
|
+
const code = body?.error?.code;
|
|
234
|
+
if (status === 400 &&
|
|
235
|
+
code === JSONRPC_ERROR_CODES.invalid_params &&
|
|
236
|
+
reason_of(body) === undefined) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
return `[${tc.label}] params ${JSON.stringify(tc.body)} → ${status} ${JSON.stringify(body)}`;
|
|
240
|
+
};
|
|
241
|
+
// the declared methods, then the live-mounted ones the declared surface lacks
|
|
242
|
+
const declared_actions = endpoint_spec.actions.filter((action) => !action.spec.method.startsWith(TESTING_METHOD_PREFIX) &&
|
|
243
|
+
endpoint.methods.some((m) => m.name === action.spec.method));
|
|
244
|
+
for (const action of [...declared_actions, ...(off_surface?.actions ?? [])]) {
|
|
245
|
+
const { method, input } = action.spec;
|
|
246
|
+
if (PROTOCOL_METHODS.has(method))
|
|
247
|
+
continue;
|
|
248
|
+
if (skip.has(method))
|
|
249
|
+
continue;
|
|
250
|
+
const all_cases = generate_rpc_params_test_cases(input);
|
|
251
|
+
if (all_cases.length === 0)
|
|
252
|
+
continue;
|
|
253
|
+
total += all_cases.length;
|
|
254
|
+
const param_cases = [];
|
|
255
|
+
for (const tc of all_cases) {
|
|
256
|
+
const key = `${method}\0${tc.label}`;
|
|
257
|
+
const xfail = xfails.get(key);
|
|
258
|
+
if (!xfail) {
|
|
259
|
+
param_cases.push(tc);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
matched_xfails.add(key);
|
|
263
|
+
xfail_until(xfail.tracking_id, xfail.reason, `${method}: [${tc.label}] → 400 invalid_params`, async () => {
|
|
264
|
+
const failure = await check_case(await setup_test(), action, tc);
|
|
265
|
+
assert.strictEqual(failure, null);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (param_cases.length === 0)
|
|
269
|
+
continue;
|
|
270
|
+
test(`${method}: every generated invalid params case → 400 invalid_params`, async () => {
|
|
271
|
+
const fixture = await setup_test();
|
|
272
|
+
const failures = [];
|
|
273
|
+
for (const tc of param_cases) {
|
|
274
|
+
const failure = await check_case(fixture, action, tc);
|
|
275
|
+
if (failure)
|
|
276
|
+
failures.push(failure);
|
|
277
|
+
}
|
|
278
|
+
assert.deepStrictEqual(failures, [], `${method}: ${failures.length} of ${param_cases.length} cases not refused invalid_params`);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
test('every xfail_params entry names a generated case', () => {
|
|
282
|
+
// a stale entry would otherwise xfail nothing, silently
|
|
283
|
+
const unmatched = [...xfails.keys()]
|
|
284
|
+
.filter((key) => !matched_xfails.has(key))
|
|
285
|
+
.map((key) => key.replace('\0', ': '));
|
|
286
|
+
assert.deepStrictEqual(unmatched, []);
|
|
287
|
+
});
|
|
288
|
+
test('generated params cases exist', () => {
|
|
289
|
+
assert.ok(total > 0, 'no params cases generated — schema walking may be broken');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RpcSuccessFixtures } from '../rpc_round_trip.js';
|
|
3
|
+
/** Options for `create_spine_rpc_success_fixtures`. */
|
|
4
|
+
export interface SpineRpcSuccessFixturesOptions {
|
|
5
|
+
/** The JSON-RPC endpoint path the standard actions are mounted on. Default `SPINE_RPC_PATH`. */
|
|
6
|
+
readonly rpc_path?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build the success-fixture map for the standard spine methods that need a
|
|
10
|
+
* real row id: `account_token_revoke`, `account_session_revoke`,
|
|
11
|
+
* `invite_delete`, `role_grant_revoke`, `role_grant_offer_accept`,
|
|
12
|
+
* `role_grant_offer_decline`, and `role_grant_offer_retract`.
|
|
13
|
+
*
|
|
14
|
+
* Each factory mints fresh accounts — a fresh `ROLE_ADMIN` holder as grantor
|
|
15
|
+
* where the setup step is admin-gated — and builds its row as a real client
|
|
16
|
+
* would, then returns the headers of the account that calls: the owner for
|
|
17
|
+
* token and session revoke, accept, decline, and retract; the fresh admin
|
|
18
|
+
* for invite delete and role grant revoke.
|
|
19
|
+
*
|
|
20
|
+
* @param options - the RPC endpoint path
|
|
21
|
+
* @returns the method → fixture-factory map
|
|
22
|
+
*/
|
|
23
|
+
export declare const create_spine_rpc_success_fixtures: (options?: SpineRpcSuccessFixturesOptions) => RpcSuccessFixtures;
|
|
24
|
+
//# sourceMappingURL=rpc_success_fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc_success_fixtures.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_success_fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAqB,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAOlF,uDAAuD;AACvD,MAAM,WAAW,8BAA8B;IAC9C,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,GAC7C,UAAS,8BAAmC,KAC1C,kBAwHF,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Success fixtures for the standard spine RPC methods that act on an existing
|
|
4
|
+
* row — revoke a token or a session, delete an invite, revoke a role grant,
|
|
5
|
+
* accept, decline, or retract an offer. The generic round-trip sends nil ids,
|
|
6
|
+
* so on its own it only ever sees these methods refuse with `not_found`; each
|
|
7
|
+
* fixture here builds the real row through the backend's own RPC surface and
|
|
8
|
+
* returns the params (and, where the row must belong to the caller, the
|
|
9
|
+
* caller's headers) that drive a **success** response, which
|
|
10
|
+
* `describe_rpc_round_trip_tests` then validates against the method's output
|
|
11
|
+
* schema on every backend.
|
|
12
|
+
*
|
|
13
|
+
* Spine-generic: the fixtures use only standard RPC actions and the builtin
|
|
14
|
+
* `ROLE_ADMIN`, so any consumer mounting `create_standard_rpc_actions` can
|
|
15
|
+
* pass the map to `describe_standard_cross_process_tests` (as
|
|
16
|
+
* `rpc_success_fixtures`), `describe_standard_tests`, or
|
|
17
|
+
* `describe_rpc_round_trip_tests` (as `success_fixtures`). Each fixture mints
|
|
18
|
+
* its own accounts and calls as one of them, so none touches or calls as the
|
|
19
|
+
* suite's shared identities.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { assert } from 'vitest';
|
|
24
|
+
import { account_session_list_action_spec, account_token_create_action_spec } from '../../auth/account_action_specs.js';
|
|
25
|
+
import { invite_create_action_spec } from '../../auth/admin_action_specs.js';
|
|
26
|
+
import { role_grant_assign_action_spec, role_grant_offer_create_action_spec } from '../../auth/role_grant_offer_action_specs.js';
|
|
27
|
+
import { ROLE_ADMIN } from '../../auth/role_schema.js';
|
|
28
|
+
import { rpc_call_typed } from '../rpc_helpers.js';
|
|
29
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
30
|
+
/**
|
|
31
|
+
* Build the success-fixture map for the standard spine methods that need a
|
|
32
|
+
* real row id: `account_token_revoke`, `account_session_revoke`,
|
|
33
|
+
* `invite_delete`, `role_grant_revoke`, `role_grant_offer_accept`,
|
|
34
|
+
* `role_grant_offer_decline`, and `role_grant_offer_retract`.
|
|
35
|
+
*
|
|
36
|
+
* Each factory mints fresh accounts — a fresh `ROLE_ADMIN` holder as grantor
|
|
37
|
+
* where the setup step is admin-gated — and builds its row as a real client
|
|
38
|
+
* would, then returns the headers of the account that calls: the owner for
|
|
39
|
+
* token and session revoke, accept, decline, and retract; the fresh admin
|
|
40
|
+
* for invite delete and role grant revoke.
|
|
41
|
+
*
|
|
42
|
+
* @param options - the RPC endpoint path
|
|
43
|
+
* @returns the method → fixture-factory map
|
|
44
|
+
*/
|
|
45
|
+
export const create_spine_rpc_success_fixtures = (options = {}) => {
|
|
46
|
+
const { rpc_path = SPINE_RPC_PATH } = options;
|
|
47
|
+
const call = (fixture, spec, params, headers) => rpc_call_typed({ app: fixture.transport, path: rpc_path, method: spec.method, params, headers }, spec.output);
|
|
48
|
+
const create_grantor = (fixture) => fixture.create_account({ roles: [ROLE_ADMIN] });
|
|
49
|
+
const create_pending_offer = async (fixture) => {
|
|
50
|
+
const grantor = await create_grantor(fixture);
|
|
51
|
+
const recipient = await fixture.create_account();
|
|
52
|
+
const { offer } = await call(fixture, role_grant_offer_create_action_spec, { to_account_id: recipient.account.id, role: ROLE_ADMIN }, grantor.create_session_headers());
|
|
53
|
+
return { grantor, recipient, offer_id: offer.id };
|
|
54
|
+
};
|
|
55
|
+
const fixtures = new Map([
|
|
56
|
+
[
|
|
57
|
+
'account_token_revoke',
|
|
58
|
+
async (fixture) => {
|
|
59
|
+
const owner = await fixture.create_account();
|
|
60
|
+
const headers = owner.create_session_headers();
|
|
61
|
+
const token = await call(fixture, account_token_create_action_spec, {
|
|
62
|
+
name: 'rpc success fixture',
|
|
63
|
+
scope: { kind: 'full' },
|
|
64
|
+
lifetime: { kind: 'eternal' }
|
|
65
|
+
}, headers);
|
|
66
|
+
return { params: { token_id: token.id }, headers };
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
'account_session_revoke',
|
|
71
|
+
async (fixture) => {
|
|
72
|
+
// a throwaway account revokes its own session, the one it calls with
|
|
73
|
+
const owner = await fixture.create_account();
|
|
74
|
+
const headers = owner.create_session_headers();
|
|
75
|
+
const { sessions } = await call(fixture, account_session_list_action_spec, undefined, headers);
|
|
76
|
+
const session = sessions[0];
|
|
77
|
+
assert.ok(session, 'account_session_revoke fixture: the fresh account lists no session');
|
|
78
|
+
return { params: { session_id: session.id }, headers };
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
'invite_delete',
|
|
83
|
+
async (fixture) => {
|
|
84
|
+
const grantor = await create_grantor(fixture);
|
|
85
|
+
const { invite } = await call(fixture, invite_create_action_spec, { username: `inv${crypto.randomUUID().replaceAll('-', '').slice(0, 24)}` }, grantor.create_session_headers());
|
|
86
|
+
return { params: { invite_id: invite.id }, headers: grantor.create_session_headers() };
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
'role_grant_revoke',
|
|
91
|
+
async (fixture) => {
|
|
92
|
+
const grantor = await create_grantor(fixture);
|
|
93
|
+
const grantee = await fixture.create_account();
|
|
94
|
+
const { role_grant_id } = await call(fixture, role_grant_assign_action_spec, { to_account_id: grantee.account.id, role: ROLE_ADMIN }, grantor.create_session_headers());
|
|
95
|
+
return {
|
|
96
|
+
params: { actor_id: grantee.actor.id, role_grant_id },
|
|
97
|
+
headers: grantor.create_session_headers()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
'role_grant_offer_accept',
|
|
103
|
+
async (fixture) => {
|
|
104
|
+
const { recipient, offer_id } = await create_pending_offer(fixture);
|
|
105
|
+
return { params: { offer_id }, headers: recipient.create_session_headers() };
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
'role_grant_offer_decline',
|
|
110
|
+
async (fixture) => {
|
|
111
|
+
const { recipient, offer_id } = await create_pending_offer(fixture);
|
|
112
|
+
return { params: { offer_id }, headers: recipient.create_session_headers() };
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
'role_grant_offer_retract',
|
|
117
|
+
async (fixture) => {
|
|
118
|
+
const { grantor, offer_id } = await create_pending_offer(fixture);
|
|
119
|
+
return { params: { offer_id }, headers: grantor.create_session_headers() };
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
]);
|
|
123
|
+
return fixtures;
|
|
124
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* The declared surface of a consumer served by the Rust spine, for its cross
|
|
4
|
+
* suites — `create_rust_spine_consumer_surface_spec`.
|
|
5
|
+
*
|
|
6
|
+
* The usual Rust spine consumer server mounts the same spine routes whatever
|
|
7
|
+
* its domain: `/health`, `fuz_auth::account_router` under `/api/account`
|
|
8
|
+
* (`ACCOUNT_ROUTER_PATHS`), the bootstrap and signup routers beside it, and
|
|
9
|
+
* one RPC endpoint carrying the standard admin / role-grant-offer / account
|
|
10
|
+
* actions plus the consumer's own. This module builds that surface from the
|
|
11
|
+
* hono-free route shapes and the consumer's action specs, so a consumer
|
|
12
|
+
* supplies only what is its own. A server that mounts no signup router (a
|
|
13
|
+
* single-keeper daemon such as mageguild's) is not this shape.
|
|
14
|
+
*
|
|
15
|
+
* The route shapes come from `create_account_route_shapes` and friends, never
|
|
16
|
+
* the live route factories, which statically pull `hono/cookie` and
|
|
17
|
+
* `hono/streaming`. Handlers are never invoked — the suites read `method`,
|
|
18
|
+
* `path`, `auth` and the schemas, and live dispatch happens in the spawned
|
|
19
|
+
* binary — so routes and actions carry no-op handlers.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import type { RpcAction } from '../../actions/action_rpc.js';
|
|
24
|
+
import type { ActionSpecUnion } from '../../actions/action_spec.js';
|
|
25
|
+
import type { RoleSchemaResult } from '../../auth/role_schema.js';
|
|
26
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
27
|
+
import type { AppSurfaceSpec, RpcEndpointSpec } from '../../http/surface.js';
|
|
28
|
+
import type { AppServerContext } from '../../server/app_server_context.js';
|
|
29
|
+
/**
|
|
30
|
+
* The routes `fuz_auth::account_router` mounts, relative to `/api/account`. An
|
|
31
|
+
* allowlist rather than every account route shape: `/verify` is a TS-only
|
|
32
|
+
* route the Rust router doesn't serve, so declaring it would have the suites
|
|
33
|
+
* probe a 404. A shape fuz_app renames or drops fails the surface build; one
|
|
34
|
+
* it adds stays off the surface until the Rust router mounts it and this list
|
|
35
|
+
* names it.
|
|
36
|
+
*/
|
|
37
|
+
export declare const ACCOUNT_ROUTER_PATHS: readonly ["/status", "/login", "/logout", "/password"];
|
|
38
|
+
/** Options for `create_rust_spine_consumer_rpc_endpoints`. */
|
|
39
|
+
export interface RustSpineConsumerRpcEndpointsOptions {
|
|
40
|
+
/**
|
|
41
|
+
* The consumer's own action specs. The request-response specs that aren't
|
|
42
|
+
* protocol actions (`heartbeat`, `peer/ping`, …) go on the RPC endpoint, so a
|
|
43
|
+
* full registry like `all_action_specs` can be passed as it is.
|
|
44
|
+
*/
|
|
45
|
+
readonly domain_action_specs: ReadonlyArray<ActionSpecUnion>;
|
|
46
|
+
/**
|
|
47
|
+
* Actions besides the standard set and the domain specs — opt-in families
|
|
48
|
+
* the consumer mounts (`create_actor_lookup_actions`,
|
|
49
|
+
* `create_self_service_role_actions`), built against the stub context.
|
|
50
|
+
*/
|
|
51
|
+
readonly extra_rpc_actions?: (ctx: AppServerContext) => Array<RpcAction>;
|
|
52
|
+
/** The consumer's role schema, for the standard admin and role-grant-offer actions. */
|
|
53
|
+
readonly roles?: RoleSchemaResult;
|
|
54
|
+
/**
|
|
55
|
+
* Domain methods kept off the declared surface — ones the spec-derived
|
|
56
|
+
* suites can't drive (an input whose `.refine()` the generic body generator
|
|
57
|
+
* can't satisfy). They stay mounted on the backend; this only leaves them
|
|
58
|
+
* out of what the suites enumerate.
|
|
59
|
+
*/
|
|
60
|
+
readonly exclude_methods?: ReadonlyArray<string>;
|
|
61
|
+
/** The RPC endpoint's path. Default `SPINE_RPC_PATH`. */
|
|
62
|
+
readonly rpc_path?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Options for `create_rust_spine_consumer_surface_spec`. */
|
|
65
|
+
export interface RustSpineConsumerSurfaceSpecOptions extends RustSpineConsumerRpcEndpointsOptions {
|
|
66
|
+
/** The backend's session config — its cookie name above all. */
|
|
67
|
+
readonly session_options: SessionOptions<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the server turns on the spine's opt-in host gate
|
|
70
|
+
* (`fuz_http::StackOptions::host_gate`), declared as a `host_validation`
|
|
71
|
+
* layer at the head of the middleware list.
|
|
72
|
+
*
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
readonly host_gate?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The RPC endpoints a Rust spine consumer mounts: the standard admin /
|
|
79
|
+
* role-grant-offer / account actions, the extra families, then the domain
|
|
80
|
+
* specs — what `describe_spine_cross_suites` takes as `rpc_endpoints`.
|
|
81
|
+
*
|
|
82
|
+
* @param options - the consumer's action specs, extras and roles
|
|
83
|
+
* @returns the endpoints, as a factory of the stub context
|
|
84
|
+
* @throws Error when an `exclude_methods` entry names no domain spec
|
|
85
|
+
*/
|
|
86
|
+
export declare const create_rust_spine_consumer_rpc_endpoints: (options: RustSpineConsumerRpcEndpointsOptions) => ((ctx: AppServerContext) => Array<RpcEndpointSpec>);
|
|
87
|
+
/**
|
|
88
|
+
* Build the declared surface of a Rust spine consumer for its cross suites,
|
|
89
|
+
* on `create_test_app_surface_spec`: the spine routes (health, the account
|
|
90
|
+
* router's paths, signup, and bootstrap as a `surface_only` shape), the RPC
|
|
91
|
+
* endpoint from `create_rust_spine_consumer_rpc_endpoints`, the daemon-token
|
|
92
|
+
* layer every consumer test binary wires, and — with `host_gate` — the
|
|
93
|
+
* `host_validation` layer at the head of the middleware list.
|
|
94
|
+
*
|
|
95
|
+
* @param options - the consumer's session config, action specs, extras and roles
|
|
96
|
+
* @returns the surface spec, for `describe_spine_cross_suites`'s `surface_source`
|
|
97
|
+
* @throws Error when an account route shape the Rust router mounts is missing,
|
|
98
|
+
* or an `exclude_methods` entry names no domain spec
|
|
99
|
+
*/
|
|
100
|
+
export declare const create_rust_spine_consumer_surface_spec: (options: RustSpineConsumerSurfaceSpecOptions) => AppSurfaceSpec;
|
|
101
|
+
//# sourceMappingURL=rust_spine_consumer_surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rust_spine_consumer_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_consumer_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAA6B,MAAM,8BAA8B,CAAC;AAM/F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAMnE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAI3E;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,wDAAyD,CAAC;AAU3F,8DAA8D;AAC9D,MAAM,WAAW,oCAAoC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACzE,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6DAA6D;AAC7D,MAAM,WAAW,mCAAoC,SAAQ,oCAAoC;IAChG,gEAAgE;IAChE,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,wCAAwC,GACpD,SAAS,oCAAoC,KAC3C,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,eAAe,CAAC,CA+BpD,CAAC;AAiCF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uCAAuC,GACnD,SAAS,mCAAmC,KAC1C,cAeA,CAAC"}
|