@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
|
@@ -1,7 +1,71 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Family-shared `BackendConfig` builders for cross-process test backends.
|
|
4
|
+
*
|
|
5
|
+
* Two consumer-facing factories — `make_default_ts_backend_config`
|
|
6
|
+
* and `make_default_rust_backend_config` — own the common shape
|
|
7
|
+
* for the JS-runtime (Deno/Node on V8) and Rust families respectively.
|
|
8
|
+
* Per-backend factories in consumer projects compose a small
|
|
9
|
+
* declaration against one of these and add consumer-specific env vars
|
|
10
|
+
* via `extra_env`.
|
|
11
|
+
*
|
|
12
|
+
* Defaults baked in by family:
|
|
13
|
+
*
|
|
14
|
+
* - **TS** — `'memory://'` (in-memory pglet), 30s startup window.
|
|
15
|
+
* - **Rust** — caller-supplied real Postgres URL (an in-process WASM database isn't reachable
|
|
16
|
+
* from `tokio-postgres`), 120s startup window (cargo first-build cost),
|
|
17
|
+
* and the `FUZ_TESTING_RESET_DB_ON_STARTUP=true` self-wipe gate.
|
|
18
|
+
*
|
|
19
|
+
* Both builders default `port_env_var` to `'PORT'`. Consumers whose
|
|
20
|
+
* binary reads a different name (e.g. zzz's `ZZZ_PORT`) override. Both bind
|
|
21
|
+
* `HOST` to `DEFAULT_TEST_BACKEND_HOST` (`127.0.0.1`), and, given the name of
|
|
22
|
+
* the binary's daemon-token directory env var (`daemon_token_dir_env`), set it
|
|
23
|
+
* to the `{dir}` whose `{dir}/run/daemon_token` is the bootstrap's
|
|
24
|
+
* `daemon_token_path` (`to_daemon_token_dir`).
|
|
25
|
+
*
|
|
26
|
+
* Common across both families: `/api/rpc`, `/api/ws`, `/health`,
|
|
27
|
+
* `/api/account/bootstrap`, `cookie_name: 'fuz_session'`, the standard
|
|
28
|
+
* bootstrap block keyed off `default_test_*` constants. Builders call
|
|
29
|
+
* `build_test_backend_paths(name)` internally when the optional `paths`
|
|
30
|
+
* is omitted.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
import { basename, dirname } from 'node:path';
|
|
2
35
|
import { build_test_backend_paths } from './build_test_backend_paths.js';
|
|
3
36
|
import { default_test_bootstrap_token, default_test_cookie_keys, default_test_keeper_password, default_test_keeper_username } from './default_secrets.js';
|
|
4
|
-
import { ACTION_RATE_LIMIT_ENABLED_ENV, ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV } from './spine_surface_constants.js';
|
|
37
|
+
import { ACTION_RATE_LIMIT_ENABLED_ENV, ACTION_RATE_LIMIT_MAX_ATTEMPTS_ENV, SPINE_ACCOUNT_BOOTSTRAP_PATH, SPINE_RPC_PATH, SPINE_WS_PATH } from './spine_surface_constants.js';
|
|
38
|
+
/**
|
|
39
|
+
* The `HOST` both builders bind a backend to — loopback IPv4, since
|
|
40
|
+
* `localhost` can resolve to `::1` and a backend bound there misses a client
|
|
41
|
+
* that connects to `127.0.0.1` (the raw-socket suites, a git hook calling
|
|
42
|
+
* back). `base_url` stays `http://localhost:<port>`, which reaches it.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_TEST_BACKEND_HOST = '127.0.0.1';
|
|
45
|
+
/**
|
|
46
|
+
* The directory a binary writing its daemon token to `{dir}/run/daemon_token`
|
|
47
|
+
* is given, so the token lands at `daemon_token_path`.
|
|
48
|
+
*
|
|
49
|
+
* @param daemon_token_path - where the harness reads the token
|
|
50
|
+
* @returns the `{dir}` of that layout
|
|
51
|
+
* @throws Error when the path is not `.../run/daemon_token`, which no such
|
|
52
|
+
* directory can produce
|
|
53
|
+
*/
|
|
54
|
+
export const to_daemon_token_dir = (daemon_token_path) => {
|
|
55
|
+
const run_dir = dirname(daemon_token_path);
|
|
56
|
+
if (basename(daemon_token_path) !== 'daemon_token' || basename(run_dir) !== 'run') {
|
|
57
|
+
throw new Error(`daemon_token_dir_env needs a daemon_token_path of the form {dir}/run/daemon_token, ` +
|
|
58
|
+
`got ${daemon_token_path}`);
|
|
59
|
+
}
|
|
60
|
+
return dirname(run_dir);
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The env entry `daemon_token_dir_env` names, pointing the binary at the
|
|
64
|
+
* directory whose layout puts the token at the bootstrap's `daemon_token_path`.
|
|
65
|
+
*/
|
|
66
|
+
const daemon_token_dir_entry = (daemon_token_dir_env, bootstrap) => daemon_token_dir_env === undefined
|
|
67
|
+
? {}
|
|
68
|
+
: { [daemon_token_dir_env]: to_daemon_token_dir(bootstrap.daemon_token_path) };
|
|
5
69
|
/**
|
|
6
70
|
* The `extra_env` entries carrying the action rate-limit toggles, shared by the
|
|
7
71
|
* TS and Rust backend configs so one option pair drives both impls.
|
|
@@ -15,109 +79,47 @@ export const action_rate_limit_env = (options) => ({
|
|
|
15
79
|
: {})
|
|
16
80
|
});
|
|
17
81
|
/**
|
|
18
|
-
* Capabilities shared by TS-family backends —
|
|
19
|
-
*
|
|
20
|
-
*
|
|
82
|
+
* Capabilities shared by TS-family backends — only what the family
|
|
83
|
+
* guarantees: the declared spine surface's mounts and the behavior of its
|
|
84
|
+
* reference runtime, Node. Carries no `MountFlag`; each backend config states
|
|
85
|
+
* those itself (`FamilyDefaultCapabilities`).
|
|
21
86
|
*/
|
|
22
87
|
export const ts_default_capabilities = Object.freeze({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
cell_relations: true,
|
|
88
|
+
// The account-lifecycle verbs are on the declared surface
|
|
89
|
+
// (`create_admin_actions`), which every TS backend running the standard
|
|
90
|
+
// suites mounts.
|
|
27
91
|
account_lifecycle: true,
|
|
28
|
-
fact_serving: true,
|
|
29
|
-
// Off by default like `sse` — a generic TS consumer backend may not mount
|
|
30
|
-
// `/ready`. fuz_app's own spine configs (`ts_spine_*`) opt in.
|
|
31
|
-
ready: false,
|
|
32
92
|
// `GET /api/account/status` is bundled into `create_account_route_specs`, so
|
|
33
93
|
// every TS backend mounting account routes serves it.
|
|
34
94
|
account_status: true,
|
|
35
|
-
// Off by default like `sse`/`ready`: the WS backend transport *can* initiate
|
|
36
|
-
// server→client requests (`BackendWebsocketTransport.request_connection`),
|
|
37
|
-
// but driving the full `peer/ping` suite also needs `peer/ping` mounted on
|
|
38
|
-
// the HTTP RPC endpoint (the no-transport case). The TS spine config opts in;
|
|
39
|
-
// a generic consumer enables it once it wires that mount.
|
|
40
|
-
peer_request: false,
|
|
41
95
|
// Node (`ws`) and Deno hand frames pipelined with the upgrade request to the
|
|
42
|
-
// socket. A Bun-served
|
|
96
|
+
// socket. A Bun-served backend overrides to `false` (see the bun spine
|
|
43
97
|
// config) — `Bun.serve` answers such a request `400` when the upgrade
|
|
44
98
|
// follows an asynchronous check.
|
|
45
99
|
ws_handshake_pipelining: true,
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// actions on `/api/ws`); a consumer does once its endpoint carries them.
|
|
49
|
-
ws_account_actions: false,
|
|
50
|
-
// Off by default — the `cell_gated_create` test policy is a fuz_app test
|
|
51
|
-
// fixture, mounted only on the spine binaries (`ts_spine_*`), not generic
|
|
52
|
-
// consumers.
|
|
53
|
-
cell_gated_create: false,
|
|
54
|
-
// Off by default — the second, role-gated WS endpoint is a spine-binary
|
|
55
|
-
// mount (`SPINE_ROLE_GATED_WS_PATH`); fuz_app's spine configs opt in.
|
|
56
|
-
ws_role_gated: false,
|
|
57
|
-
// Off by default: `@hono/node-ws` drops a refused upgrade's body, so a
|
|
58
|
-
// Node-served consumer sends none. A Deno- or Bun-served one may opt in.
|
|
100
|
+
// `@hono/node-ws` drops a refused upgrade's body, so a Node-served backend
|
|
101
|
+
// sends none. A Deno- or Bun-served one overrides to `true`.
|
|
59
102
|
ws_refusal_body: false
|
|
60
103
|
});
|
|
61
104
|
/**
|
|
62
|
-
* Capabilities for the Rust family
|
|
63
|
-
* `
|
|
64
|
-
*
|
|
105
|
+
* Capabilities for the Rust family — only what the family guarantees, as with
|
|
106
|
+
* `ts_default_capabilities`: the declared spine surface's mounts and the
|
|
107
|
+
* behavior of hyper. Carries no `MountFlag`.
|
|
65
108
|
*/
|
|
66
109
|
export const rust_default_capabilities = Object.freeze({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
cell_relations: true,
|
|
110
|
+
// The account-lifecycle verbs are in the spine's admin action set, on the
|
|
111
|
+
// declared surface every Rust spine backend running the standard suites
|
|
112
|
+
// mounts.
|
|
71
113
|
account_lifecycle: true,
|
|
72
|
-
fact_serving: true,
|
|
73
|
-
// Off by default like `sse`; the spine-stub preset opts in (it mounts
|
|
74
|
-
// `/ready` over the env-supplied fixture path).
|
|
75
|
-
ready: false,
|
|
76
114
|
// The Rust `account_router` bundles `/status` into the account routes, so
|
|
77
115
|
// every Rust spine serving the account surface serves it.
|
|
78
116
|
account_status: true,
|
|
79
|
-
// Server-initiated requests landed Rust-first canonical — the Rust spine
|
|
80
|
-
// drives the `peer/ping` round-trip, so the family opts into the suite.
|
|
81
|
-
peer_request: true,
|
|
82
117
|
// hyper hands bytes pipelined after the upgrade request to the upgraded
|
|
83
118
|
// stream, where they wait for the read loop that starts at admission.
|
|
84
119
|
ws_handshake_pipelining: true,
|
|
85
|
-
// Off by default, as on the TS family: a consumer opts in once its WS
|
|
86
|
-
// endpoint mounts the account actions. The `testing_spine_stub` preset does.
|
|
87
|
-
ws_account_actions: false,
|
|
88
|
-
// Off by default — the `testing_spine_stub` preset opts in (it mounts the
|
|
89
|
-
// `TestCellGatedCreateAuthorize` policy); a generic Rust consumer doesn't.
|
|
90
|
-
cell_gated_create: false,
|
|
91
|
-
// Off by default — the `testing_spine_stub` preset opts in (it mounts the
|
|
92
|
-
// role-gated WS endpoint at `SPINE_ROLE_GATED_WS_PATH`).
|
|
93
|
-
ws_role_gated: false,
|
|
94
120
|
// axum sends a refused upgrade's response as built, body included.
|
|
95
121
|
ws_refusal_body: true
|
|
96
122
|
});
|
|
97
|
-
/**
|
|
98
|
-
* Shape notes for TS-family backends — wiring facts, not gating flags.
|
|
99
|
-
* `trusted_proxy: false` (the test binary doesn't enable proxy parsing) and
|
|
100
|
-
* `login_rate_limit: false` (the TS canonical path leaves the limiter null in
|
|
101
|
-
* test mode); bearer auth is always wired. Documentation only — see
|
|
102
|
-
* `BackendShapeNotes`.
|
|
103
|
-
*/
|
|
104
|
-
export const ts_default_shape_notes = Object.freeze({
|
|
105
|
-
bearer_auth: true,
|
|
106
|
-
trusted_proxy: false,
|
|
107
|
-
login_rate_limit: false
|
|
108
|
-
});
|
|
109
|
-
/**
|
|
110
|
-
* Shape notes for Rust-family backends — wiring facts, not gating flags. Adds
|
|
111
|
-
* `trusted_proxy: true` (the Rust spine's client-IP middleware is always wired;
|
|
112
|
-
* the env-gate only chooses XFF vs the TCP peer IP) and `login_rate_limit: true`
|
|
113
|
-
* (env-gated bucket on `/login` + `/password`). Documentation only — see
|
|
114
|
-
* `BackendShapeNotes`.
|
|
115
|
-
*/
|
|
116
|
-
export const rust_default_shape_notes = Object.freeze({
|
|
117
|
-
bearer_auth: true,
|
|
118
|
-
trusted_proxy: true,
|
|
119
|
-
login_rate_limit: true
|
|
120
|
-
});
|
|
121
123
|
/** Bootstrap block built from the default secrets + supplied paths. */
|
|
122
124
|
const build_default_bootstrap = (paths, overrides) => ({
|
|
123
125
|
token_path: paths.bootstrap_token_path,
|
|
@@ -134,28 +136,30 @@ const build_default_bootstrap = (paths, overrides) => ({
|
|
|
134
136
|
* genuinely differs.
|
|
135
137
|
*/
|
|
136
138
|
export const make_default_ts_backend_config = (opts) => {
|
|
137
|
-
const { name, port, start_command, database_url = 'memory://', extra_env, capabilities
|
|
139
|
+
const { name, port, start_command, database_url = 'memory://', extra_env, capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', cookie_name = 'fuz_session', daemon_token_dir_env } = opts;
|
|
140
|
+
const bootstrap = build_default_bootstrap(paths, bootstrap_overrides);
|
|
138
141
|
return {
|
|
139
142
|
name,
|
|
140
143
|
start_command,
|
|
141
144
|
base_url: `http://localhost:${port}`,
|
|
142
|
-
rpc_path:
|
|
143
|
-
ws_path:
|
|
145
|
+
rpc_path: SPINE_RPC_PATH,
|
|
146
|
+
ws_path: SPINE_WS_PATH,
|
|
144
147
|
health_path: '/health',
|
|
145
|
-
bootstrap_path:
|
|
148
|
+
bootstrap_path: SPINE_ACCOUNT_BOOTSTRAP_PATH,
|
|
146
149
|
cookie_name,
|
|
147
150
|
startup_timeout_ms: 30_000,
|
|
148
151
|
env: {
|
|
149
152
|
NODE_ENV: 'development',
|
|
150
|
-
HOST:
|
|
153
|
+
HOST: DEFAULT_TEST_BACKEND_HOST,
|
|
151
154
|
[port_env_var]: String(port),
|
|
152
155
|
DATABASE_URL: database_url,
|
|
153
156
|
SECRET_FUZ_COOKIE_KEYS: default_test_cookie_keys,
|
|
154
157
|
FUZ_ALLOWED_ORIGINS: 'http://localhost:*',
|
|
155
158
|
FUZ_BOOTSTRAP_TOKEN_PATH: paths.bootstrap_token_path,
|
|
159
|
+
...daemon_token_dir_entry(daemon_token_dir_env, bootstrap),
|
|
156
160
|
...extra_env
|
|
157
161
|
},
|
|
158
|
-
bootstrap
|
|
162
|
+
bootstrap,
|
|
159
163
|
capabilities
|
|
160
164
|
};
|
|
161
165
|
};
|
|
@@ -166,20 +170,21 @@ export const make_default_ts_backend_config = (opts) => {
|
|
|
166
170
|
* the 120s startup window for cargo's first-run build cost.
|
|
167
171
|
*/
|
|
168
172
|
export const make_default_rust_backend_config = (opts) => {
|
|
169
|
-
const { name, port, start_command, database_url, extra_env, capabilities
|
|
173
|
+
const { name, port, start_command, database_url, extra_env, capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', rust_log = 'info', cookie_name = 'fuz_session', daemon_token_dir_env } = opts;
|
|
174
|
+
const bootstrap = build_default_bootstrap(paths, bootstrap_overrides);
|
|
170
175
|
return {
|
|
171
176
|
name,
|
|
172
177
|
start_command,
|
|
173
178
|
base_url: `http://localhost:${port}`,
|
|
174
|
-
rpc_path:
|
|
175
|
-
ws_path:
|
|
179
|
+
rpc_path: SPINE_RPC_PATH,
|
|
180
|
+
ws_path: SPINE_WS_PATH,
|
|
176
181
|
health_path: '/health',
|
|
177
|
-
bootstrap_path:
|
|
182
|
+
bootstrap_path: SPINE_ACCOUNT_BOOTSTRAP_PATH,
|
|
178
183
|
cookie_name,
|
|
179
184
|
startup_timeout_ms: 120_000,
|
|
180
185
|
env: {
|
|
181
186
|
RUST_LOG: rust_log,
|
|
182
|
-
HOST:
|
|
187
|
+
HOST: DEFAULT_TEST_BACKEND_HOST,
|
|
183
188
|
[port_env_var]: String(port),
|
|
184
189
|
DATABASE_URL: database_url,
|
|
185
190
|
SECRET_FUZ_COOKIE_KEYS: default_test_cookie_keys,
|
|
@@ -192,9 +197,10 @@ export const make_default_rust_backend_config = (opts) => {
|
|
|
192
197
|
// `fuz_db::run_migrations`. The `_testing_reset` RPC action
|
|
193
198
|
// (per-test reset) is orthogonal.
|
|
194
199
|
FUZ_TESTING_RESET_DB_ON_STARTUP: 'true',
|
|
200
|
+
...daemon_token_dir_entry(daemon_token_dir_env, bootstrap),
|
|
195
201
|
...extra_env
|
|
196
202
|
},
|
|
197
|
-
bootstrap
|
|
203
|
+
bootstrap,
|
|
198
204
|
capabilities
|
|
199
205
|
};
|
|
200
206
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAItD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AACvF,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAI1F,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAgB3E;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,gBAGxB,CAAC;AAEH,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAgC/E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC+B,CAAC;AAE7F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAS1C,CAAC"}
|
|
@@ -14,7 +14,7 @@ import { create_test_app_surface_spec } from '../stubs.js';
|
|
|
14
14
|
// without dragging this module's eager in-process route handlers
|
|
15
15
|
// (→ `session_middleware` → `hono/cookie`). Import only the constants this
|
|
16
16
|
// module uses internally; callers that need a bare constant reach for the leaf.
|
|
17
|
-
import { SPINE_CELL_EDITOR_ROLE, SPINE_EXPECTED_SCHEMA_URL, SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
17
|
+
import { SPINE_ACCOUNT_PATH_PREFIX, SPINE_CELL_EDITOR_ROLE, SPINE_EXPECTED_SCHEMA_URL, SPINE_PARTICIPANT_ROLE, SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
18
18
|
/**
|
|
19
19
|
* Session config — cookie name matches the binary's issued session cookie
|
|
20
20
|
* (`fuz_session`) so cookie-attribute assertions + jar extraction line up.
|
|
@@ -68,7 +68,7 @@ export const spine_rpc_endpoints = (ctx, options) => [
|
|
|
68
68
|
* the stream at `SPINE_SSE_PATH`.
|
|
69
69
|
*/
|
|
70
70
|
export const create_spine_route_specs = (ctx) => [
|
|
71
|
-
...prefix_route_specs(
|
|
71
|
+
...prefix_route_specs(SPINE_ACCOUNT_PATH_PREFIX, [
|
|
72
72
|
...create_account_route_specs(ctx.deps, {
|
|
73
73
|
session_options: spine_session_options,
|
|
74
74
|
// Honor the limiters the server was assembled with (on `AppServerContext`
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { AppSurfaceSpec } from '../../http/surface.js';
|
|
3
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
4
|
+
import type { RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
5
|
+
import type { ConformanceCase } from './conformance_case.js';
|
|
6
|
+
import { type ConformancePrincipalConfig } from './conformance_table.js';
|
|
7
|
+
import type { SetupTest } from './setup.js';
|
|
8
|
+
import type { WsEndpointTarget } from './ws_endpoint.js';
|
|
9
|
+
/** One side of the differential. */
|
|
10
|
+
export interface DifferentialBackend {
|
|
11
|
+
/** Short label used in failure output (`ts`, `rust`). */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/** Per-test fixture producer for this backend. */
|
|
14
|
+
readonly setup_test: SetupTest;
|
|
15
|
+
/**
|
|
16
|
+
* The backend's WebSocket endpoint. Set on both backends to send the cases
|
|
17
|
+
* over a socket instead of HTTP — only the cases `is_ws_conformance_case`
|
|
18
|
+
* admits, and the ledger is checked against those. Set on one alone, the
|
|
19
|
+
* suite throws at registration.
|
|
20
|
+
*/
|
|
21
|
+
readonly ws?: WsEndpointTarget;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* What a ledger entry matches: cases by name, or every case on a method —
|
|
25
|
+
* one, or a list.
|
|
26
|
+
*/
|
|
27
|
+
export type KnownDivergenceMatch = {
|
|
28
|
+
readonly case_name: string | ReadonlyArray<string>;
|
|
29
|
+
readonly method?: never;
|
|
30
|
+
} | {
|
|
31
|
+
readonly method: string | ReadonlyArray<string>;
|
|
32
|
+
readonly case_name?: never;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* One path a known divergence differs at, with what each side must show
|
|
36
|
+
* there.
|
|
37
|
+
*
|
|
38
|
+
* `path` covers itself and everything under it (`differential_path_covers`).
|
|
39
|
+
* `a` / `b`, when given, are the value that side's diff must carry at the
|
|
40
|
+
* differing path — `undefined` meaning the side has nothing there — so a
|
|
41
|
+
* divergence that changes shape (the side that lacked a field starts sending
|
|
42
|
+
* a different one, or the other side's value moves) is no longer covered and
|
|
43
|
+
* fails. Omit a side to leave its value open. A bare string is a path with
|
|
44
|
+
* both sides open.
|
|
45
|
+
*
|
|
46
|
+
* A pin is compared with the normalized leaf value at the differing path: a
|
|
47
|
+
* placeholdered value never matches the raw one, an object pin must list its
|
|
48
|
+
* keys sorted, and a pin on a parent path never matches, since diffs are
|
|
49
|
+
* reported at their leaves.
|
|
50
|
+
*/
|
|
51
|
+
export interface KnownDivergencePath {
|
|
52
|
+
readonly path: string;
|
|
53
|
+
readonly a?: unknown;
|
|
54
|
+
readonly b?: unknown;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A tracked, accepted difference between the two backends.
|
|
58
|
+
*
|
|
59
|
+
* `what_differs` lists the normalized-response paths that differ
|
|
60
|
+
* (`DifferentialDiff.path` — `status`, `headers.<name>`,
|
|
61
|
+
* `body.error.data.reason`), each optionally with the value each side shows
|
|
62
|
+
* (`KnownDivergencePath`). A matched case may differ there and nowhere else.
|
|
63
|
+
* An entry naming cases must still differ on each of them; one naming
|
|
64
|
+
* methods, on at least one of its cases.
|
|
65
|
+
*
|
|
66
|
+
* A pin is compared with the normalized leaf value at the differing path: a
|
|
67
|
+
* placeholdered value never matches the raw one, an object pin must list its
|
|
68
|
+
* keys sorted, and a pin on a parent path never matches, since diffs are
|
|
69
|
+
* reported at their leaves.
|
|
70
|
+
*/
|
|
71
|
+
export type KnownDivergence = KnownDivergenceMatch & {
|
|
72
|
+
/** Tracking id of the divergence (a slug, as `xfail_until` takes). */
|
|
73
|
+
readonly tracking_id: string;
|
|
74
|
+
/** Why the divergence is accepted for now. */
|
|
75
|
+
readonly reason: string;
|
|
76
|
+
/** The normalized-response paths that differ, with any per-side values. */
|
|
77
|
+
readonly what_differs: ReadonlyArray<string | KnownDivergencePath>;
|
|
78
|
+
};
|
|
79
|
+
/** Options for `describe_differential_conformance_tests`. */
|
|
80
|
+
export interface DifferentialConformanceOptions {
|
|
81
|
+
/** The cases to send to both backends, in order. */
|
|
82
|
+
readonly cases: ReadonlyArray<ConformanceCase>;
|
|
83
|
+
/** The two backends; `a` and `b` in diffs are these, in order. */
|
|
84
|
+
readonly backends: readonly [DifferentialBackend, DifferentialBackend];
|
|
85
|
+
/** Surface spec — supplies the `RouteSpec`s for the REST branch. */
|
|
86
|
+
readonly surface_source: AppSurfaceSpec;
|
|
87
|
+
/** RPC endpoints — resolved to find each method's action spec. */
|
|
88
|
+
readonly rpc_endpoints: RpcEndpointsSuiteOption;
|
|
89
|
+
/**
|
|
90
|
+
* The RPC endpoints both backends live-mount, for methods off the declared
|
|
91
|
+
* `rpc_endpoints` — resolved the way the table runner resolves them
|
|
92
|
+
* (`resolve_conformance_rpc_endpoints`).
|
|
93
|
+
*/
|
|
94
|
+
readonly live_rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
95
|
+
/** Session options — needed to resolve the `rpc_endpoints` factory form. */
|
|
96
|
+
readonly session_options: SessionOptions<string>;
|
|
97
|
+
/** Maps the `role_holder` / `wrong_role` principals to seeded usernames. */
|
|
98
|
+
readonly principals?: ConformancePrincipalConfig;
|
|
99
|
+
/** The tracked divergences these cases may show. */
|
|
100
|
+
readonly known_divergences?: ReadonlyArray<KnownDivergence>;
|
|
101
|
+
/** `describe` block label. Defaults to `'differential conformance'`. */
|
|
102
|
+
readonly suite_name?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The ledger's static problems against a case list: an entry that matches no
|
|
106
|
+
* case, and every `case_name` it lists that no case carries, or every `method`
|
|
107
|
+
* no case sends — a dead name would otherwise ride along behind a live one in
|
|
108
|
+
* the same entry.
|
|
109
|
+
*
|
|
110
|
+
* Needs no backend; the differential's closing test reports these, and a
|
|
111
|
+
* consumer can check its ledger with it directly.
|
|
112
|
+
*
|
|
113
|
+
* @param cases - the cases the ledger is run against
|
|
114
|
+
* @param ledger - the known divergences
|
|
115
|
+
* @returns one message per problem, empty when the ledger names only real cases
|
|
116
|
+
*/
|
|
117
|
+
export declare const check_known_divergence_ledger: (cases: ReadonlyArray<ConformanceCase>, ledger: ReadonlyArray<KnownDivergence>) => Array<string>;
|
|
118
|
+
/**
|
|
119
|
+
* Register a `describe` block sending every case to both backends and
|
|
120
|
+
* asserting their normalized responses are equal, or — for a case in the
|
|
121
|
+
* known-divergence ledger — that they still differ, and only where listed.
|
|
122
|
+
*
|
|
123
|
+
* @param options - the cases, the two backends, and the shared surface
|
|
124
|
+
*/
|
|
125
|
+
export declare const describe_differential_conformance_tests: (options: DifferentialConformanceOptions) => void;
|
|
126
|
+
//# sourceMappingURL=differential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"differential.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/differential.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6C9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAIN,KAAK,0BAA0B,EAC/B,MAAM,wBAAwB,CAAC;AAShC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IACnC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC7B;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEnF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG;IACpD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;CACnE,CAAC;AAEF,6DAA6D;AAC7D,MAAM,WAAW,8BAA8B;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IACvE,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IACtD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,oDAAoD;IACpD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC5D,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAgBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,GACzC,OAAO,aAAa,CAAC,eAAe,CAAC,EACrC,QAAQ,aAAa,CAAC,eAAe,CAAC,KACpC,KAAK,CAAC,MAAM,CA0Bd,CAAC;AA6BF;;;;;;GAMG;AACH,eAAO,MAAM,uCAAuC,GACnD,SAAS,8BAA8B,KACrC,IA0GF,CAAC"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* The cross-backend differential: each impl is the oracle for the other.
|
|
4
|
+
*
|
|
5
|
+
* A conformance case pins its status and reason on one backend at a time,
|
|
6
|
+
* and an output schema is often loose (`z.looseObject`, or Zod's default
|
|
7
|
+
* strip), so an extra field from one spine — possibly a leak — passes, as
|
|
8
|
+
* does any body difference on a case pinned by status alone.
|
|
9
|
+
* `describe_differential_conformance_tests` sends every case to two backends,
|
|
10
|
+
* normalizes both responses (`normalize_differential_response`), and asserts
|
|
11
|
+
* they are equal: status, header names, and the whole body.
|
|
12
|
+
*
|
|
13
|
+
* Each case runs on a fresh fixture per backend — each backend's own
|
|
14
|
+
* `setup_test`, so separate databases and separate keepers, which is why the
|
|
15
|
+
* normalization is positional. The principal resolves and the request is sent
|
|
16
|
+
* through the table runner's own `send_conformance_case`, so a case means the
|
|
17
|
+
* same thing here as in `describe_conformance_table_tests`. The per-case
|
|
18
|
+
* expectations (`expect.status`, `error_reason`, …) are not asserted here —
|
|
19
|
+
* each backend's conformance leg already holds them; the differential holds
|
|
20
|
+
* the two answers to each other.
|
|
21
|
+
*
|
|
22
|
+
* A difference that is tracked and accepted goes in the **known-divergence
|
|
23
|
+
* ledger** (`KnownDivergence`), matched by case names or by methods, naming
|
|
24
|
+
* the paths that differ (`what_differs`), each optionally with the value each
|
|
25
|
+
* side must show there. A case may match several entries and differ at any
|
|
26
|
+
* of their paths, with the declared values, and nowhere else: any other
|
|
27
|
+
* difference fails like any other. The ledger cleans itself, with the
|
|
28
|
+
* `xfail_until` semantics — red when a divergence closes, naming the entry to
|
|
29
|
+
* delete: a case an entry names by `case_name` must still show it, checked in
|
|
30
|
+
* that case's own test, and an entry matched by `method` must still show it on
|
|
31
|
+
* at least one of its cases, checked by a closing test once they have all run.
|
|
32
|
+
* The closing test also fails an entry that matches no case, or that names
|
|
33
|
+
* a case that doesn't exist, and leaves an entry whose cases didn't all run
|
|
34
|
+
* (a `-t` filter, a send that threw) unjudged rather than calling it closed.
|
|
35
|
+
*
|
|
36
|
+
* Use the backends' cross-process fixtures — the differential needs two live
|
|
37
|
+
* backends, so it runs in a dual-spawn project. fuz_app's own wiring is the
|
|
38
|
+
* `cross_backend_parity` project's `differential.cross.test.ts`.
|
|
39
|
+
*
|
|
40
|
+
* @module
|
|
41
|
+
*/
|
|
42
|
+
import { assert, describe, test } from 'vitest';
|
|
43
|
+
import { is_ws_conformance_case, resolve_conformance_rpc_endpoints, send_conformance_case } from './conformance_table.js';
|
|
44
|
+
import { diff_differential_responses, differential_path_covers, format_differential_diffs, normalize_differential_response } from './differential_normalize.js';
|
|
45
|
+
const as_list = (value) => typeof value === 'string' ? [value] : value;
|
|
46
|
+
/** The ledger entries matching a case. */
|
|
47
|
+
const find_known_divergences = (c, ledger) => ledger.filter((d) => d.case_name !== undefined
|
|
48
|
+
? as_list(d.case_name).includes(c.name)
|
|
49
|
+
: as_list(d.method).includes(c.request.method));
|
|
50
|
+
/**
|
|
51
|
+
* The ledger's static problems against a case list: an entry that matches no
|
|
52
|
+
* case, and every `case_name` it lists that no case carries, or every `method`
|
|
53
|
+
* no case sends — a dead name would otherwise ride along behind a live one in
|
|
54
|
+
* the same entry.
|
|
55
|
+
*
|
|
56
|
+
* Needs no backend; the differential's closing test reports these, and a
|
|
57
|
+
* consumer can check its ledger with it directly.
|
|
58
|
+
*
|
|
59
|
+
* @param cases - the cases the ledger is run against
|
|
60
|
+
* @param ledger - the known divergences
|
|
61
|
+
* @returns one message per problem, empty when the ledger names only real cases
|
|
62
|
+
*/
|
|
63
|
+
export const check_known_divergence_ledger = (cases, ledger) => {
|
|
64
|
+
const problems = [];
|
|
65
|
+
const case_names = new Set(cases.map((c) => c.name));
|
|
66
|
+
const case_methods = new Set(cases.map((c) => c.request.method));
|
|
67
|
+
for (const d of ledger) {
|
|
68
|
+
if (!cases.some((c) => find_known_divergences(c, [d]).length > 0)) {
|
|
69
|
+
problems.push(`'${d.tracking_id}' matches no case — fix its case name or method`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (d.case_name !== undefined) {
|
|
73
|
+
const dead = as_list(d.case_name).filter((name) => !case_names.has(name));
|
|
74
|
+
if (dead.length > 0) {
|
|
75
|
+
problems.push(`'${d.tracking_id}' names no such case: ${dead.map((n) => `'${n}'`).join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const dead = as_list(d.method).filter((method) => !case_methods.has(method));
|
|
80
|
+
if (dead.length > 0) {
|
|
81
|
+
problems.push(`'${d.tracking_id}' names a method no case sends: ${dead.map((m) => `'${m}'`).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return problems;
|
|
86
|
+
};
|
|
87
|
+
const to_divergence_path = (entry) => typeof entry === 'string' ? { path: entry } : entry;
|
|
88
|
+
/** Whether `diff` is one `d` declares: under a listed path, with its values. */
|
|
89
|
+
const is_covered = (d, diff) => d.what_differs.some((entry) => {
|
|
90
|
+
const p = to_divergence_path(entry);
|
|
91
|
+
return (differential_path_covers(p.path, diff.path) &&
|
|
92
|
+
(!('a' in p) || is_deep_equal(diff.a, p.a)) &&
|
|
93
|
+
(!('b' in p) || is_deep_equal(diff.b, p.b)));
|
|
94
|
+
});
|
|
95
|
+
const is_deep_equal = (x, y) => JSON.stringify(x) === JSON.stringify(y);
|
|
96
|
+
const format_what_differs = (d) => d.what_differs
|
|
97
|
+
.map((entry) => {
|
|
98
|
+
const p = to_divergence_path(entry);
|
|
99
|
+
const sides = ['a', 'b']
|
|
100
|
+
.filter((side) => side in p)
|
|
101
|
+
.map((side) => `${side}=${JSON.stringify(p[side]) ?? 'undefined'}`);
|
|
102
|
+
return sides.length > 0 ? `${p.path} (${sides.join(' ')})` : p.path;
|
|
103
|
+
})
|
|
104
|
+
.join(', ');
|
|
105
|
+
/**
|
|
106
|
+
* Register a `describe` block sending every case to both backends and
|
|
107
|
+
* asserting their normalized responses are equal, or — for a case in the
|
|
108
|
+
* known-divergence ledger — that they still differ, and only where listed.
|
|
109
|
+
*
|
|
110
|
+
* @param options - the cases, the two backends, and the shared surface
|
|
111
|
+
*/
|
|
112
|
+
export const describe_differential_conformance_tests = (options) => {
|
|
113
|
+
const { backends, surface_source, principals } = options;
|
|
114
|
+
const ledger = options.known_divergences ?? [];
|
|
115
|
+
const resolved_rpc_endpoints = resolve_conformance_rpc_endpoints(options);
|
|
116
|
+
const [a, b] = backends;
|
|
117
|
+
const labels = { a: a.name, b: b.name };
|
|
118
|
+
if ((a.ws === undefined) !== (b.ws === undefined)) {
|
|
119
|
+
throw new Error(`differential: backend '${(a.ws ? b : a).name}' has no ws target while ` +
|
|
120
|
+
`'${(a.ws ? a : b).name}' does — set ws on both backends or neither.`);
|
|
121
|
+
}
|
|
122
|
+
const over_ws = a.ws !== undefined;
|
|
123
|
+
const cases = over_ws ? options.cases.filter(is_ws_conformance_case) : options.cases;
|
|
124
|
+
const send = async (backend, c) => normalize_differential_response(await send_conformance_case(c, {
|
|
125
|
+
fixture: await backend.setup_test(),
|
|
126
|
+
surface_source,
|
|
127
|
+
...(principals && { principals }),
|
|
128
|
+
resolved_rpc_endpoints,
|
|
129
|
+
...(backend.ws && { ws: backend.ws })
|
|
130
|
+
}));
|
|
131
|
+
describe(options.suite_name ?? 'differential conformance', () => {
|
|
132
|
+
// per entry: the cases it matched, the ones that ran to a diff, and
|
|
133
|
+
// whether any of them showed it
|
|
134
|
+
const matched = new Map();
|
|
135
|
+
const ran = new Map();
|
|
136
|
+
const observed = new Set();
|
|
137
|
+
for (const c of cases) {
|
|
138
|
+
const known = find_known_divergences(c, ledger);
|
|
139
|
+
for (const d of known) {
|
|
140
|
+
const names = matched.get(d) ?? new Set();
|
|
141
|
+
names.add(c.name);
|
|
142
|
+
matched.set(d, names);
|
|
143
|
+
}
|
|
144
|
+
const label = known.length > 0
|
|
145
|
+
? `${c.name} [known: ${known.map((d) => d.tracking_id).join(', ')}]`
|
|
146
|
+
: c.name;
|
|
147
|
+
test(label, async () => {
|
|
148
|
+
const [response_a, response_b] = await Promise.all([send(a, c), send(b, c)]);
|
|
149
|
+
const diffs = diff_differential_responses(response_a, response_b);
|
|
150
|
+
for (const d of known) {
|
|
151
|
+
const names = ran.get(d) ?? new Set();
|
|
152
|
+
names.add(c.name);
|
|
153
|
+
ran.set(d, names);
|
|
154
|
+
if (diffs.some((diff) => is_covered(d, diff)))
|
|
155
|
+
observed.add(d);
|
|
156
|
+
}
|
|
157
|
+
const unlisted = diffs.filter((diff) => !known.some((d) => is_covered(d, diff)));
|
|
158
|
+
assert.ok(unlisted.length === 0, `${c.name}: ${a.name} and ${b.name} answered differently` +
|
|
159
|
+
(known.length > 0
|
|
160
|
+
? ` outside known divergences ${known.map((d) => d.tracking_id).join(', ')}`
|
|
161
|
+
: '') +
|
|
162
|
+
`:\n` +
|
|
163
|
+
format_differential_diffs(unlisted, labels));
|
|
164
|
+
// an entry naming this case must still show here
|
|
165
|
+
const closed = known.filter((d) => d.case_name !== undefined && !diffs.some((diff) => is_covered(d, diff)));
|
|
166
|
+
assert.ok(closed.length === 0, `${c.name}: known divergence ${closed.map((d) => `'${d.tracking_id}'`).join(', ')} ` +
|
|
167
|
+
`has closed on this case — ${a.name} and ${b.name} no longer differ at ` +
|
|
168
|
+
`${closed.map(format_what_differs).join('; ')}; drop the case from the entry`);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Registered after the cases, so it runs once every case has recorded.
|
|
172
|
+
test('every known divergence matches a case and still differs', (ctx) => {
|
|
173
|
+
const problems = check_known_divergence_ledger(cases, ledger);
|
|
174
|
+
let judged = 0;
|
|
175
|
+
let unjudged = 0;
|
|
176
|
+
for (const d of ledger) {
|
|
177
|
+
const names = matched.get(d);
|
|
178
|
+
if (!names)
|
|
179
|
+
continue;
|
|
180
|
+
// case-named entries are judged in their cases' own tests; a
|
|
181
|
+
// method entry only once every matched case ran, since a filtered
|
|
182
|
+
// or failed run can't tell a closed divergence from an unrun case
|
|
183
|
+
if (d.case_name !== undefined)
|
|
184
|
+
continue;
|
|
185
|
+
if ((ran.get(d)?.size ?? 0) < names.size) {
|
|
186
|
+
unjudged++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
judged++;
|
|
190
|
+
if (!observed.has(d)) {
|
|
191
|
+
problems.push(`'${d.tracking_id}' has closed — ${a.name} and ${b.name} answer the same at ` +
|
|
192
|
+
`${format_what_differs(d)} on every matched case; delete its ledger entry`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
assert.deepEqual(problems, [], 'known-divergence ledger is stale');
|
|
196
|
+
// nothing left to judge: say so rather than pass as if checked
|
|
197
|
+
if (unjudged > 0 && judged === 0)
|
|
198
|
+
ctx.skip();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
};
|