@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,
|
|
1
|
+
{"version":3,"file":"admin_integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/admin_integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAgC7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW3E,OAAO,EAMN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA4B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAavE;;GAEG;AACH,MAAM,WAAW,mCAAmC;IACnD;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,4GAA4G;IAC5G,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAiBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,mCAAmC,KAC1C,IAw+BF,CAAC"}
|
|
@@ -32,10 +32,11 @@ import { DEFAULT_TEST_PASSWORD } from './test_credentials.js';
|
|
|
32
32
|
import { role_grant_offer_and_accept } from './role_grant_helpers.js';
|
|
33
33
|
import { find_auth_route } from './integration_helpers.js';
|
|
34
34
|
import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERROR_COVERAGE } from './error_coverage.js';
|
|
35
|
-
import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup } from './rpc_helpers.js';
|
|
35
|
+
import { find_rpc_action, rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup } from './rpc_helpers.js';
|
|
36
36
|
import { role_grant_offer_create_action_spec, role_grant_revoke_action_spec } from '../auth/role_grant_offer_action_specs.js';
|
|
37
|
-
import { admin_account_list_action_spec, ADMIN_ACCOUNT_LIST_LIMIT_MAX, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec } from '../auth/admin_action_specs.js';
|
|
38
|
-
import { account_token_create_action_spec, account_verify_action_spec } from '../auth/account_action_specs.js';
|
|
37
|
+
import { admin_account_list_action_spec, ADMIN_ACCOUNT_LIST_LIMIT_MAX, app_settings_get_action_spec, app_settings_update_action_spec, AUDIT_LOG_LIST_LIMIT_MAX, invite_create_action_spec, invite_delete_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec } from '../auth/admin_action_specs.js';
|
|
38
|
+
import { account_session_list_action_spec, account_session_revoke_action_spec, account_token_create_action_spec, account_token_revoke_action_spec, account_verify_action_spec } from '../auth/account_action_specs.js';
|
|
39
|
+
import { assert_audit_event_metadata } from './audit_drift_guard.js';
|
|
39
40
|
import { to_route_content_type } from './schema_generators.js';
|
|
40
41
|
/**
|
|
41
42
|
* How long a failure-audit read waits for the pool-routed row to land.
|
|
@@ -151,6 +152,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
151
152
|
headers: fixture.create_session_headers()
|
|
152
153
|
});
|
|
153
154
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
155
|
+
for (const event of audit_res.result.events) {
|
|
156
|
+
assert_audit_event_metadata(event, {
|
|
157
|
+
reject_unknown_keys: fixture.reject_unknown_audit_metadata_keys ?? false
|
|
158
|
+
});
|
|
159
|
+
}
|
|
154
160
|
const failure = audit_res.result.events.find((e) => e.outcome === 'failure');
|
|
155
161
|
if (failure || Date.now() >= deadline)
|
|
156
162
|
return failure;
|
|
@@ -632,6 +638,58 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
632
638
|
headers: fixture.create_session_headers()
|
|
633
639
|
});
|
|
634
640
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
641
|
+
/** Call `spec` on the keeper's session, asserting success. */
|
|
642
|
+
const call_ok = async (spec, params, headers = fixture.create_session_headers()) => {
|
|
643
|
+
const res = await rpc_call_for_spec({
|
|
644
|
+
app: { request: fixture.transport },
|
|
645
|
+
path: rpc_path,
|
|
646
|
+
spec,
|
|
647
|
+
params,
|
|
648
|
+
headers
|
|
649
|
+
});
|
|
650
|
+
if (!res.ok)
|
|
651
|
+
assert.fail(`${spec.method} failed: ${JSON.stringify(res.error)}`);
|
|
652
|
+
return res.result;
|
|
653
|
+
};
|
|
654
|
+
// Steps 5a–5d each drive actions a consumer surface may not mount;
|
|
655
|
+
// a step whose actions are missing is skipped, and its event isn't
|
|
656
|
+
// expected.
|
|
657
|
+
const mounted = (...specs) => specs.every((spec) => find_rpc_action(rpc_endpoints_for_setup, spec.method));
|
|
658
|
+
const driven_types = [];
|
|
659
|
+
// 5a. revoke that token (RPC)
|
|
660
|
+
if (mounted(account_token_revoke_action_spec)) {
|
|
661
|
+
const token_revoked = await call_ok(account_token_revoke_action_spec, {
|
|
662
|
+
token_id: token_res.result.id
|
|
663
|
+
});
|
|
664
|
+
assert.strictEqual(token_revoked.revoked, true);
|
|
665
|
+
driven_types.push('token_revoke');
|
|
666
|
+
}
|
|
667
|
+
// 5b. user_two revokes its own session — the last use of that session
|
|
668
|
+
if (mounted(account_session_list_action_spec, account_session_revoke_action_spec)) {
|
|
669
|
+
const user_two_headers = user_two.create_session_headers();
|
|
670
|
+
const { sessions } = await call_ok(account_session_list_action_spec, undefined, user_two_headers);
|
|
671
|
+
assert.ok(sessions[0], 'user_two has a session to revoke');
|
|
672
|
+
const session_revoked = await call_ok(account_session_revoke_action_spec, { session_id: sessions[0].id }, user_two_headers);
|
|
673
|
+
assert.strictEqual(session_revoked.revoked, true);
|
|
674
|
+
driven_types.push('session_revoke');
|
|
675
|
+
}
|
|
676
|
+
// 5c. an invite created and deleted unclaimed
|
|
677
|
+
if (mounted(invite_create_action_spec, invite_delete_action_spec)) {
|
|
678
|
+
const { invite } = await call_ok(invite_create_action_spec, {
|
|
679
|
+
email: 'audit_invitee@example.com',
|
|
680
|
+
username: 'audit_invitee'
|
|
681
|
+
});
|
|
682
|
+
await call_ok(invite_delete_action_spec, { invite_id: invite.id });
|
|
683
|
+
driven_types.push('invite_create', 'invite_delete');
|
|
684
|
+
}
|
|
685
|
+
// 5d. open signup flipped and restored
|
|
686
|
+
if (mounted(app_settings_get_action_spec, app_settings_update_action_spec)) {
|
|
687
|
+
const { settings } = await call_ok(app_settings_get_action_spec, {});
|
|
688
|
+
for (const open_signup of [!settings.open_signup, settings.open_signup]) {
|
|
689
|
+
await call_ok(app_settings_update_action_spec, { open_signup });
|
|
690
|
+
}
|
|
691
|
+
driven_types.push('app_settings_update');
|
|
692
|
+
}
|
|
635
693
|
// 6. password change
|
|
636
694
|
await fixture.transport(password_route.path, {
|
|
637
695
|
method: 'POST',
|
|
@@ -670,14 +728,23 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
670
728
|
app: { request: fixture.transport },
|
|
671
729
|
path: rpc_path,
|
|
672
730
|
spec: audit_log_list_action_spec,
|
|
673
|
-
params: {},
|
|
731
|
+
params: { limit: AUDIT_LOG_LIST_LIMIT_MAX },
|
|
674
732
|
headers: relogin_headers
|
|
675
733
|
});
|
|
676
734
|
assert.ok(audit_res.ok, `audit_log_list failed: ${audit_res.ok ? '' : JSON.stringify(audit_res.error)}`);
|
|
677
735
|
const events = audit_res.result.events;
|
|
736
|
+
// every row's metadata holds to its event type's schema — the
|
|
737
|
+
// wire leaves it a loose record
|
|
738
|
+
for (const event of events) {
|
|
739
|
+
assert_audit_event_metadata(event, {
|
|
740
|
+
reject_unknown_keys: fixture.reject_unknown_audit_metadata_keys ?? false
|
|
741
|
+
});
|
|
742
|
+
}
|
|
678
743
|
// check that each operation produced at least one event.
|
|
679
744
|
// `role_grant_offer_create` fires on the admin RPC; `role_grant_create`
|
|
680
745
|
// fires when the recipient accepts (driven by offer_and_accept).
|
|
746
|
+
// No `signup` is expected: in-process, `create_account` inserts
|
|
747
|
+
// user_two directly, writing no audit row.
|
|
681
748
|
const expected_types = [
|
|
682
749
|
'login',
|
|
683
750
|
'logout',
|
|
@@ -686,7 +753,8 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
686
753
|
'role_grant_create',
|
|
687
754
|
'role_grant_revoke',
|
|
688
755
|
'token_create',
|
|
689
|
-
'password_change'
|
|
756
|
+
'password_change',
|
|
757
|
+
...driven_types
|
|
690
758
|
];
|
|
691
759
|
for (const event_type of expected_types) {
|
|
692
760
|
const found = events.filter((e) => e.event_type === event_type);
|
|
@@ -1,51 +1,5 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
|
|
4
2
|
import type { AdversarialTestOptions } from './attack_surface.js';
|
|
5
|
-
interface InputTestCase {
|
|
6
|
-
label: string;
|
|
7
|
-
body: unknown;
|
|
8
|
-
expected_error: typeof ERROR_INVALID_REQUEST_BODY | typeof ERROR_INVALID_JSON_BODY;
|
|
9
|
-
}
|
|
10
|
-
interface ParamsTestCase {
|
|
11
|
-
label: string;
|
|
12
|
-
params: Record<string, string>;
|
|
13
|
-
}
|
|
14
|
-
interface QueryTestCase {
|
|
15
|
-
label: string;
|
|
16
|
-
query: Record<string, string>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Generate adversarial test cases for a route's input schema.
|
|
20
|
-
*
|
|
21
|
-
* Produces focused, non-redundant cases:
|
|
22
|
-
* - Whole-body: send array instead of object, extra unknown key
|
|
23
|
-
* - Missing required fields (without defaults)
|
|
24
|
-
* - One wrong-type value per field
|
|
25
|
-
* - Null for required non-nullable fields
|
|
26
|
-
* - One format violation per constrained field
|
|
27
|
-
*
|
|
28
|
-
* @throws Error if the seed body built by `generate_valid_value` fails
|
|
29
|
-
* `input_schema.safeParse` — surfaces broken generation logic with the
|
|
30
|
-
* Zod issues path rather than producing nonsense adversarial cases.
|
|
31
|
-
*/
|
|
32
|
-
export declare const generate_input_test_cases: (input_schema: z.ZodType) => Array<InputTestCase>;
|
|
33
|
-
/**
|
|
34
|
-
* Generate adversarial test cases for a route's params schema.
|
|
35
|
-
*
|
|
36
|
-
* Params are always strings from URL segments. Only generates cases for
|
|
37
|
-
* format-constrained fields (uuid, pattern) since unconstrained string
|
|
38
|
-
* params accept any string value.
|
|
39
|
-
*/
|
|
40
|
-
export declare const generate_params_test_cases: (params_schema: z.ZodObject) => Array<ParamsTestCase>;
|
|
41
|
-
/**
|
|
42
|
-
* Generate adversarial test cases for a route's query schema.
|
|
43
|
-
*
|
|
44
|
-
* Query params are always strings from the URL. Generates cases for:
|
|
45
|
-
* - Missing required fields
|
|
46
|
-
* - Format violations on constrained fields (uuid, pattern)
|
|
47
|
-
*/
|
|
48
|
-
export declare const generate_query_test_cases: (query_schema: z.ZodObject) => Array<QueryTestCase>;
|
|
49
3
|
/**
|
|
50
4
|
* Generate adversarial input validation test suites.
|
|
51
5
|
*
|
|
@@ -54,5 +8,4 @@ export declare const generate_query_test_cases: (query_schema: z.ZodObject) => A
|
|
|
54
8
|
* middleware is actually exercised.
|
|
55
9
|
*/
|
|
56
10
|
export declare const describe_adversarial_input: (options: AdversarialTestOptions) => void;
|
|
57
|
-
export {};
|
|
58
11
|
//# sourceMappingURL=adversarial_input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adversarial_input.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_input.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"adversarial_input.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_input.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AA2BlE;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS,sBAAsB,KAAG,IAyN5E,CAAC"}
|
|
@@ -2,353 +2,17 @@ import './assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Adversarial input validation testing for route specs.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* are reached.
|
|
8
|
-
*
|
|
9
|
-
* Tests are focused: one representative wrong-type value per field, one format
|
|
10
|
-
* violation per constrained field, one null and one missing test per required
|
|
11
|
-
* field, plus whole-body structural attacks (non-object body, extra unknown keys).
|
|
5
|
+
* Fires the payloads `adversarial_input_cases.ts` generates against a test
|
|
6
|
+
* app and asserts 400 responses before handlers are reached.
|
|
12
7
|
*
|
|
13
8
|
* @module
|
|
14
9
|
*/
|
|
15
10
|
import { test, assert, describe } from 'vitest';
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import { zod_unwrap_to_object, zod_extract_fields } from '../zod.js';
|
|
18
|
-
import { is_null_schema } from '../http/schema_helpers.js';
|
|
19
11
|
import { filter_routes_with_input, filter_routes_with_params, filter_routes_with_query } from '../http/surface_query.js';
|
|
20
12
|
import { ValidationError, ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY, ERROR_INVALID_ROUTE_PARAMS, ERROR_INVALID_QUERY_PARAMS } from '../http/error_schemas.js';
|
|
21
13
|
import { create_auth_test_apps, create_route_skip_filter, select_auth_app } from './auth_apps.js';
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
/** One wrong-type value per base type — one representative is sufficient. */
|
|
25
|
-
const wrong_type_for = (base_type) => {
|
|
26
|
-
switch (base_type) {
|
|
27
|
-
case 'string':
|
|
28
|
-
case 'uuid':
|
|
29
|
-
case 'email':
|
|
30
|
-
return { label: 'number instead of string', value: 42 };
|
|
31
|
-
case 'number':
|
|
32
|
-
case 'int':
|
|
33
|
-
return { label: 'string instead of number', value: 'not_a_number' };
|
|
34
|
-
case 'boolean':
|
|
35
|
-
return { label: 'number instead of boolean', value: 42 };
|
|
36
|
-
case 'array':
|
|
37
|
-
return { label: 'string instead of array', value: 'not_an_array' };
|
|
38
|
-
case 'object':
|
|
39
|
-
return { label: 'string instead of object', value: 'not_an_object' };
|
|
40
|
-
case 'enum':
|
|
41
|
-
return { label: 'number instead of enum', value: 42 };
|
|
42
|
-
default:
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
/** Format violation payloads — one per constraint type. */
|
|
47
|
-
const format_violation = (format) => {
|
|
48
|
-
switch (format) {
|
|
49
|
-
case 'uuid':
|
|
50
|
-
return { label: 'malformed uuid', value: 'not-a-uuid' };
|
|
51
|
-
case 'email':
|
|
52
|
-
return { label: 'malformed email', value: 'not-an-email' };
|
|
53
|
-
case 'date-time':
|
|
54
|
-
return { label: 'malformed datetime', value: 'not-a-date' };
|
|
55
|
-
case 'pattern':
|
|
56
|
-
return { label: 'pattern violation', value: "'; DROP TABLE --" };
|
|
57
|
-
default:
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
// --- Input test case generation ---
|
|
62
|
-
/**
|
|
63
|
-
* Generate adversarial test cases for a route's input schema.
|
|
64
|
-
*
|
|
65
|
-
* Produces focused, non-redundant cases:
|
|
66
|
-
* - Whole-body: send array instead of object, extra unknown key
|
|
67
|
-
* - Missing required fields (without defaults)
|
|
68
|
-
* - One wrong-type value per field
|
|
69
|
-
* - Null for required non-nullable fields
|
|
70
|
-
* - One format violation per constrained field
|
|
71
|
-
*
|
|
72
|
-
* @throws Error if the seed body built by `generate_valid_value` fails
|
|
73
|
-
* `input_schema.safeParse` — surfaces broken generation logic with the
|
|
74
|
-
* Zod issues path rather than producing nonsense adversarial cases.
|
|
75
|
-
*/
|
|
76
|
-
export const generate_input_test_cases = (input_schema) => {
|
|
77
|
-
if (is_null_schema(input_schema))
|
|
78
|
-
return [];
|
|
79
|
-
const object_schema = zod_unwrap_to_object(input_schema);
|
|
80
|
-
if (!object_schema)
|
|
81
|
-
return [];
|
|
82
|
-
const fields = zod_extract_fields(object_schema);
|
|
83
|
-
const cases = [];
|
|
84
|
-
// build a base object with valid-ish values — must pass validation itself
|
|
85
|
-
// skip optional fields without defaults to avoid generating invalid nested values
|
|
86
|
-
const base = {};
|
|
87
|
-
for (const field of fields) {
|
|
88
|
-
if (!field.required && !field.has_default)
|
|
89
|
-
continue;
|
|
90
|
-
base[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
|
|
91
|
-
}
|
|
92
|
-
let base_result = input_schema.safeParse(base);
|
|
93
|
-
if (!base_result.success) {
|
|
94
|
-
// Fallback for schemas with a top-level `.refine()` that requires at
|
|
95
|
-
// least one of N optional fields. Fill optional fields until the base
|
|
96
|
-
// satisfies validation; surfaces a clear error if no combination works.
|
|
97
|
-
for (const field of fields) {
|
|
98
|
-
if (field.required || field.has_default || field.name in base)
|
|
99
|
-
continue;
|
|
100
|
-
base[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
|
|
101
|
-
base_result = input_schema.safeParse(base);
|
|
102
|
-
if (base_result.success)
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (!base_result.success) {
|
|
107
|
-
throw new Error(`adversarial_input: generated base object fails validation for schema — ` +
|
|
108
|
-
`fix generate_valid_value for: ${base_result.error.issues
|
|
109
|
-
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
110
|
-
.join(', ')}`);
|
|
111
|
-
}
|
|
112
|
-
// whole-body structural: send array instead of object
|
|
113
|
-
cases.push({
|
|
114
|
-
label: 'non-object body (array)',
|
|
115
|
-
body: [1, 2, 3],
|
|
116
|
-
expected_error: ERROR_INVALID_JSON_BODY
|
|
117
|
-
});
|
|
118
|
-
// whole-body structural: extra unknown key (enforces strictObject)
|
|
119
|
-
// only emit if the schema rejects unknown keys (i.e. uses z.strictObject)
|
|
120
|
-
const extra_key_result = input_schema.safeParse({ ...base, __adversarial_extra: 'rejected' });
|
|
121
|
-
if (!extra_key_result.success) {
|
|
122
|
-
cases.push({
|
|
123
|
-
label: 'extra unknown key',
|
|
124
|
-
body: { ...base, __adversarial_extra: 'should_be_rejected' },
|
|
125
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
for (const field of fields) {
|
|
129
|
-
const field_schema = object_schema.shape[field.name];
|
|
130
|
-
// missing required field (skip fields with defaults — Zod fills them)
|
|
131
|
-
if (field.required && !field.has_default) {
|
|
132
|
-
const without = {};
|
|
133
|
-
for (const [k, v] of Object.entries(base)) {
|
|
134
|
-
if (k !== field.name)
|
|
135
|
-
without[k] = v;
|
|
136
|
-
}
|
|
137
|
-
cases.push({
|
|
138
|
-
label: `missing: ${field.name}`,
|
|
139
|
-
body: without,
|
|
140
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
// one wrong-type value
|
|
144
|
-
const wrong = wrong_type_for(field.base_type);
|
|
145
|
-
if (wrong) {
|
|
146
|
-
cases.push({
|
|
147
|
-
label: `wrong type: ${field.name} (${wrong.label})`,
|
|
148
|
-
body: { ...base, [field.name]: wrong.value },
|
|
149
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
// null for required non-nullable fields
|
|
153
|
-
if (field.required && !field.nullable && field.base_type !== 'null') {
|
|
154
|
-
cases.push({
|
|
155
|
-
label: `null: ${field.name}`,
|
|
156
|
-
body: { ...base, [field.name]: null },
|
|
157
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
// format violation for constrained string fields
|
|
161
|
-
const format = detect_format(field_schema);
|
|
162
|
-
if (format) {
|
|
163
|
-
const violation = format_violation(format);
|
|
164
|
-
if (violation) {
|
|
165
|
-
cases.push({
|
|
166
|
-
label: `format: ${field.name} (${violation.label})`,
|
|
167
|
-
body: { ...base, [field.name]: violation.value },
|
|
168
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// --- Boundary cases via JSON Schema introspection ---
|
|
173
|
-
try {
|
|
174
|
-
const json = z.toJSONSchema(field_schema);
|
|
175
|
-
// string length boundaries
|
|
176
|
-
if (field.base_type === 'string' ||
|
|
177
|
-
field.base_type === 'uuid' ||
|
|
178
|
-
field.base_type === 'email') {
|
|
179
|
-
if (typeof json.minLength === 'number' && json.minLength > 0) {
|
|
180
|
-
cases.push({
|
|
181
|
-
label: `empty string: ${field.name}`,
|
|
182
|
-
body: { ...base, [field.name]: '' },
|
|
183
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
if (typeof json.maxLength === 'number') {
|
|
187
|
-
cases.push({
|
|
188
|
-
label: `over maxLength: ${field.name} (${json.maxLength + 1} chars)`,
|
|
189
|
-
body: { ...base, [field.name]: 'x'.repeat(json.maxLength + 1) },
|
|
190
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// numeric boundaries
|
|
195
|
-
if (field.base_type === 'number' || field.base_type === 'int') {
|
|
196
|
-
if (typeof json.minimum === 'number') {
|
|
197
|
-
cases.push({
|
|
198
|
-
label: `below minimum: ${field.name} (${json.minimum - 1})`,
|
|
199
|
-
body: { ...base, [field.name]: json.minimum - 1 },
|
|
200
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (typeof json.maximum === 'number') {
|
|
204
|
-
cases.push({
|
|
205
|
-
label: `above maximum: ${field.name} (${json.maximum + 1})`,
|
|
206
|
-
body: { ...base, [field.name]: json.maximum + 1 },
|
|
207
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
if (typeof json.exclusiveMinimum === 'number') {
|
|
211
|
-
cases.push({
|
|
212
|
-
label: `at exclusive minimum: ${field.name} (${json.exclusiveMinimum})`,
|
|
213
|
-
body: { ...base, [field.name]: json.exclusiveMinimum },
|
|
214
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
if (typeof json.exclusiveMaximum === 'number') {
|
|
218
|
-
cases.push({
|
|
219
|
-
label: `at exclusive maximum: ${field.name} (${json.exclusiveMaximum})`,
|
|
220
|
-
body: { ...base, [field.name]: json.exclusiveMaximum },
|
|
221
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
// 0 and negative for positive-only fields
|
|
225
|
-
if (typeof json.minimum === 'number' && json.minimum > 0) {
|
|
226
|
-
cases.push({
|
|
227
|
-
label: `zero for positive-only: ${field.name}`,
|
|
228
|
-
body: { ...base, [field.name]: 0 },
|
|
229
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
230
|
-
});
|
|
231
|
-
cases.push({
|
|
232
|
-
label: `negative for positive-only: ${field.name}`,
|
|
233
|
-
body: { ...base, [field.name]: -1 },
|
|
234
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
// array length boundaries
|
|
239
|
-
if (field.base_type === 'array') {
|
|
240
|
-
if (typeof json.minItems === 'number' && json.minItems > 0) {
|
|
241
|
-
cases.push({
|
|
242
|
-
label: `empty array for minItems > 0: ${field.name}`,
|
|
243
|
-
body: { ...base, [field.name]: [] },
|
|
244
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
if (typeof json.maxItems === 'number') {
|
|
248
|
-
// generate an array one item over the max (items are null — schema-agnostic)
|
|
249
|
-
cases.push({
|
|
250
|
-
label: `over maxItems: ${field.name} (${json.maxItems + 1} items)`,
|
|
251
|
-
body: { ...base, [field.name]: Array.from({ length: json.maxItems + 1 }, () => null) },
|
|
252
|
-
expected_error: ERROR_INVALID_REQUEST_BODY
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
// schema can't be converted to JSON Schema, skip boundary cases
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return cases;
|
|
262
|
-
};
|
|
263
|
-
// --- Params test case generation ---
|
|
264
|
-
/**
|
|
265
|
-
* Generate adversarial test cases for a route's params schema.
|
|
266
|
-
*
|
|
267
|
-
* Params are always strings from URL segments. Only generates cases for
|
|
268
|
-
* format-constrained fields (uuid, pattern) since unconstrained string
|
|
269
|
-
* params accept any string value.
|
|
270
|
-
*/
|
|
271
|
-
export const generate_params_test_cases = (params_schema) => {
|
|
272
|
-
const fields = zod_extract_fields(params_schema);
|
|
273
|
-
const cases = [];
|
|
274
|
-
// build base params with valid-ish values
|
|
275
|
-
const base_params = {};
|
|
276
|
-
for (const field of fields) {
|
|
277
|
-
const field_schema = params_schema.shape[field.name];
|
|
278
|
-
const format = detect_format(field_schema);
|
|
279
|
-
if (format === 'uuid' || field.base_type === 'uuid') {
|
|
280
|
-
base_params[field.name] = '00000000-0000-0000-0000-000000000000';
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
base_params[field.name] = 'test_value';
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
for (const field of fields) {
|
|
287
|
-
const field_schema = params_schema.shape[field.name];
|
|
288
|
-
const format = detect_format(field_schema);
|
|
289
|
-
if (!format)
|
|
290
|
-
continue; // unconstrained string — any value passes
|
|
291
|
-
const violation = format_violation(format);
|
|
292
|
-
if (violation) {
|
|
293
|
-
cases.push({
|
|
294
|
-
label: `${violation.label}: param ${field.name}`,
|
|
295
|
-
params: { ...base_params, [field.name]: violation.value }
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return cases;
|
|
300
|
-
};
|
|
301
|
-
// --- Query test case generation ---
|
|
302
|
-
/**
|
|
303
|
-
* Generate adversarial test cases for a route's query schema.
|
|
304
|
-
*
|
|
305
|
-
* Query params are always strings from the URL. Generates cases for:
|
|
306
|
-
* - Missing required fields
|
|
307
|
-
* - Format violations on constrained fields (uuid, pattern)
|
|
308
|
-
*/
|
|
309
|
-
export const generate_query_test_cases = (query_schema) => {
|
|
310
|
-
const fields = zod_extract_fields(query_schema);
|
|
311
|
-
const cases = [];
|
|
312
|
-
// build base query with valid-ish values
|
|
313
|
-
const base_query = {};
|
|
314
|
-
for (const field of fields) {
|
|
315
|
-
const field_schema = query_schema.shape[field.name];
|
|
316
|
-
const format = detect_format(field_schema);
|
|
317
|
-
if (format === 'uuid' || field.base_type === 'uuid') {
|
|
318
|
-
base_query[field.name] = '00000000-0000-0000-0000-000000000000';
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
base_query[field.name] = 'test_value';
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
for (const field of fields) {
|
|
325
|
-
// missing required field
|
|
326
|
-
if (field.required && !field.has_default) {
|
|
327
|
-
const without = {};
|
|
328
|
-
for (const [k, v] of Object.entries(base_query)) {
|
|
329
|
-
if (k !== field.name)
|
|
330
|
-
without[k] = v;
|
|
331
|
-
}
|
|
332
|
-
cases.push({
|
|
333
|
-
label: `missing query: ${field.name}`,
|
|
334
|
-
query: without
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
// format violation for constrained string fields
|
|
338
|
-
const field_schema = query_schema.shape[field.name];
|
|
339
|
-
const format = detect_format(field_schema);
|
|
340
|
-
if (!format)
|
|
341
|
-
continue;
|
|
342
|
-
const violation = format_violation(format);
|
|
343
|
-
if (violation) {
|
|
344
|
-
cases.push({
|
|
345
|
-
label: `${violation.label}: query ${field.name}`,
|
|
346
|
-
query: { ...base_query, [field.name]: violation.value }
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return cases;
|
|
351
|
-
};
|
|
14
|
+
import { resolve_valid_path, to_route_content_type } from './schema_generators.js';
|
|
15
|
+
import { generate_input_test_cases, generate_params_test_cases, generate_query_test_cases } from './adversarial_input_cases.js';
|
|
352
16
|
// --- URL helpers ---
|
|
353
17
|
/** Build a URL path with adversarial param values. */
|
|
354
18
|
const build_fuzz_url = (route_path, params) => {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Schema-walk generation of adversarial inputs — payloads that must fail
|
|
4
|
+
* validation.
|
|
5
|
+
*
|
|
6
|
+
* Focused cases: one representative wrong-type value per field, one format
|
|
7
|
+
* violation per constrained field, one null and one missing case per required
|
|
8
|
+
* field, plus whole-body structural attacks (non-object body, extra unknown
|
|
9
|
+
* keys). Hono-free, so the cross-process suites reuse the generators the
|
|
10
|
+
* in-process `describe_adversarial_input` and `describe_rpc_attack_surface_tests`
|
|
11
|
+
* run.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
|
|
17
|
+
/** One adversarial body for an input schema, and the error code it owes. */
|
|
18
|
+
export interface InputTestCase {
|
|
19
|
+
label: string;
|
|
20
|
+
body: unknown;
|
|
21
|
+
/**
|
|
22
|
+
* The one top-level field the case breaks; absent on a whole-body case (a
|
|
23
|
+
* non-object body, an extra unknown key). An RPC refusal of the case owes
|
|
24
|
+
* the field's declared `invalid_reason` (`expected_invalid_reason`).
|
|
25
|
+
*/
|
|
26
|
+
field?: string;
|
|
27
|
+
expected_error: typeof ERROR_INVALID_REQUEST_BODY | typeof ERROR_INVALID_JSON_BODY;
|
|
28
|
+
}
|
|
29
|
+
/** One adversarial params set for a route's params schema. */
|
|
30
|
+
export interface ParamsTestCase {
|
|
31
|
+
label: string;
|
|
32
|
+
params: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
/** One adversarial query for a route's query schema. */
|
|
35
|
+
export interface QueryTestCase {
|
|
36
|
+
label: string;
|
|
37
|
+
query: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
/** Options for `generate_input_test_cases`. */
|
|
40
|
+
export interface InputTestCaseOptions {
|
|
41
|
+
/**
|
|
42
|
+
* A valid body to build the cases from, in place of the generated one —
|
|
43
|
+
* for an input whose valid shape the generator can't reach, or one whose
|
|
44
|
+
* generated body the handler can't run. Each case is this body with one
|
|
45
|
+
* field broken, so it must pass the input schema itself: one that doesn't
|
|
46
|
+
* throws.
|
|
47
|
+
*/
|
|
48
|
+
base?: Record<string, unknown>;
|
|
49
|
+
/** Names the input in error messages — the RPC method or route it belongs to. */
|
|
50
|
+
label?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate adversarial test cases for a route's input schema.
|
|
54
|
+
*
|
|
55
|
+
* Produces focused, non-redundant cases:
|
|
56
|
+
* - Whole-body: send array instead of object, extra unknown key
|
|
57
|
+
* - Missing required fields (without defaults)
|
|
58
|
+
* - One wrong-type value per field
|
|
59
|
+
* - Null for required non-nullable fields
|
|
60
|
+
* - One format violation per constrained field
|
|
61
|
+
* - String length (empty, one under `minLength`, one over `maxLength`), numeric
|
|
62
|
+
* (out of range, non-integer, off a `multipleOf` grid) and array-length
|
|
63
|
+
* (empty, one under `minItems`, one over `maxItems`) boundaries
|
|
64
|
+
*
|
|
65
|
+
* Format and boundaries come from the field's input-side JSON Schema
|
|
66
|
+
* (`to_field_json_schema`), so a field carrying a `.transform()` or pipe and a
|
|
67
|
+
* `.nullable()` / `.nullish()` field get them too. A bound only a `.refine()`
|
|
68
|
+
* enforces generates nothing — JSON Schema can't express it.
|
|
69
|
+
*
|
|
70
|
+
* Each per-field case is single-fault: valid except for the field it breaks.
|
|
71
|
+
* It changes one field of a valid base body — `options.base` when given, else
|
|
72
|
+
* one built by `generate_valid_object` — or, for a field that base omits, of
|
|
73
|
+
* the valid body `generate_valid_object_with_field` builds around it, so a case
|
|
74
|
+
* can't also break an object-level rule (adding `scope_kind` without its
|
|
75
|
+
* paired `scope_id`, or `slug` beside an exactly-one-of `id`). A field no valid
|
|
76
|
+
* body carries keeps its wrong-type and null cases on the base and gets no
|
|
77
|
+
* format or boundary cases, which would test no bound. A per-field case is
|
|
78
|
+
* emitted only when the field refuses its value, so no case is accidentally
|
|
79
|
+
* valid.
|
|
80
|
+
*
|
|
81
|
+
* @param input_schema - the input schema to attack
|
|
82
|
+
* @param options - a base body to seed the cases, and a label for errors
|
|
83
|
+
* @throws Error if the base body fails `input_schema.safeParse` — a seeded
|
|
84
|
+
* base the schema refuses, or a generated one that surfaces broken
|
|
85
|
+
* generation logic — naming the issues rather than producing nonsense
|
|
86
|
+
* adversarial cases.
|
|
87
|
+
*/
|
|
88
|
+
export declare const generate_input_test_cases: (input_schema: z.ZodType, options?: InputTestCaseOptions) => Array<InputTestCase>;
|
|
89
|
+
/**
|
|
90
|
+
* The `error.data.reason` an RPC refusal of `test_case` owes: the declared
|
|
91
|
+
* `invalid_reason` of the field it breaks, resolved as the dispatcher resolves
|
|
92
|
+
* a failure on that field (`resolve_invalid_reason`), or `undefined` — no
|
|
93
|
+
* reason — for an undeclared field or a whole-body case.
|
|
94
|
+
*
|
|
95
|
+
* @param input_schema - the input schema the case was generated from
|
|
96
|
+
* @param test_case - a case `generate_input_test_cases` produced from it
|
|
97
|
+
* @returns the owed reason, or `undefined` when the refusal carries none
|
|
98
|
+
*/
|
|
99
|
+
export declare const expected_invalid_reason: (input_schema: z.ZodType, test_case: InputTestCase) => string | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Generate adversarial test cases for a route's params schema.
|
|
102
|
+
*
|
|
103
|
+
* Params are always strings from URL segments. Only generates cases for
|
|
104
|
+
* format-constrained fields (uuid, pattern) since unconstrained string
|
|
105
|
+
* params accept any string value, and only a violation the field refuses.
|
|
106
|
+
* Each case breaks one field of a valid params record.
|
|
107
|
+
*
|
|
108
|
+
* @throws Error if no valid params record can be generated
|
|
109
|
+
*/
|
|
110
|
+
export declare const generate_params_test_cases: (params_schema: z.ZodObject) => Array<ParamsTestCase>;
|
|
111
|
+
/**
|
|
112
|
+
* Generate adversarial test cases for a route's query schema.
|
|
113
|
+
*
|
|
114
|
+
* Query params are always strings from the URL. Generates cases for:
|
|
115
|
+
* - Missing required fields the field refuses to go without (a preprocessed
|
|
116
|
+
* field with a default accepts its absence)
|
|
117
|
+
* - Format violations on constrained fields (uuid, pattern) the field refuses
|
|
118
|
+
*
|
|
119
|
+
* Each case breaks one field of a valid query record.
|
|
120
|
+
*
|
|
121
|
+
* @throws Error if no valid query record can be generated
|
|
122
|
+
*/
|
|
123
|
+
export declare const generate_query_test_cases: (query_schema: z.ZodObject) => Array<QueryTestCase>;
|
|
124
|
+
//# sourceMappingURL=adversarial_input_cases.d.ts.map
|