mirror of
https://github.com/block/buzz.git
synced 2026-10-27 20:41:57 +00:00
Stack: this PR → #8062 🤖 Follows #8028 (merged). Adds `BUZZ_NIP_FI_MODE=shadow`. The relay loads and validates the full enforce configuration (issuers, communities, lifetimes, command issuers), evaluates NIP-FI evidence wherever enforce would decide, and records what enforce would have done, while every request, upgrade and session behaves exactly as in `off`. The one approved exception is admin disconnect: shadow verifies the command and returns `200`, where Off has no verifier and returns `503`. It lets an operator measure the would-deny rate, including Hosts missing from `BUZZ_NIP_FI_COMMUNITIES`, before switching to `enforce`. - **Mode predicates.** `NipFiMode` gains `is_off`, `restricts`, `evaluates`, `enforces`, `denies_unconditionally` and `observes_only`, built on two exhaustive `match` bases (`restricts`, `evaluates`); `is_off` is a `matches!` and the rest derive from the bases, so a new mode cannot silently fall into one side. Every call site uses them, including the Git membership-lookup failure from #8005, which uses `restricts()` so shadow keeps Off's body. A test scans each crate's `src/` and fails on any `NipFiMode::<variant>` in production code outside the predicate definitions and the env parser, which catches `==`, `matches!` on any number of lines, `if let` and or-patterns. - **Startup.** Shadow goes through enforce's path, so every enforce startup refusal applies, including the required connection lifetime. Missing-setting errors name the configured mode through one formatter (`mode_requires`); the enforce text is byte-identical. Shadow builds the assertion verifier and claims command replays under its own Redis prefix, both through the production `AppState::new`. - **HTTP and Blossom.** `admit_nip_fi_http` keeps Off's result in shadow and additionally replays enforce in enforce's order: first the router guard's steps (community, then assertion), then the handler's steps (NIP-98, pairing, deny set) on the same NIP-98 proof, through `evaluate_enforce_steps`, which enforce shares. A Host mapped in config but missing from the communities table, with a valid assertion, records an admit, because enforce's guard passes and its 404 is not a NIP-FI denial. A dev-mode `X-Pubkey` identity is marked unsigned and is never an enforce proof, so shadow records it as a `nip98` would-deny. On bridge and Blossom routes, the strict proof check (payload tag, Blossom `Strict`) runs as a separate pure check that consumes no replay entry and records on its own series, so it never counts as an admission verdict. Shadow records exactly one verdict for every identity, community or credential refusal enforce would make, with Off's status, body and challenge unchanged: bridge, workflows, Blossom, Git transport and settings, GIF search and share, and invite minting record the `community` would-deny when the Host is unmapped. Git's missing or malformed credentials still reject before any database work, and shadow records the enforce verdict for that failed proof. The router guard stays transparent in shadow. - **WebSocket, root and audio.** The upgrade check evaluates the attached assertion once. An upgrade enforce would refuse records that would-deny; otherwise the assertion goes into a shadow-only session object and the upgrade returns as in Off. The assertion never reaches the connection, the identity registries, the admission gate, terminal frames or cancellation. Each session records at most one admission verdict. **Pairing** is recorded on both ingresses right after a valid NIP-42 proof, before ordinary relay policy, where enforce pairs: a key mismatch or claimless assertion is a `pairing` denial. **The deny-set check** runs where enforce runs it, after registration: a hit is a `deny_set` denial, and a clean read keeps the session pending and registered. **The admit** is recorded only when the connection is actually admitted: on root at the AUTH commit, after ordinary policy; on audio when the participant join transaction commits, after relay membership, channel membership, the final ban and membership decision, mesh routing and the join's own checks, and before publication. A NIP-42 failure, which comes before pairing, and any refusal between pairing and the admit (ordinary policy, mesh, or a join refusal inside the join transaction) retires the observer when the refusal is decided, before any refusal frame, rollback or cleanup is awaited, so it leaves no record even if the deadline passes during that cleanup. A record-only deadline task, armed at upgrade with enforce's `compute_session_deadline`, records `expired` when enforce would have closed an admitted session. If the deadline passes while the session is still pending, that is its single admission verdict, a denial with `stage=deadline`, and a later AUTH records nothing. A deny for the key (admin or cross-pod) records `revoked` on an admitted session; if it arrives after the deny-set check but before the admit, including a deny refused for capacity, the session keeps a pending-revocation mark and records the admit followed by exactly one `revoked` end, matching enforce's phase-independent close, and the deadline cannot take that end. The phase, the mark and retirement change together under one session lock. Each session records at most one end. The session holds the socket's cancellation token for recording only: once the socket is cancelled, no transition records anything, even while connection references remain (a cancel fence; the token never feeds a close action). Upgrade would-denies carry the ingress route, `ws` or `audio`. - **Admin disconnect.** Shadow verifies the command and records the deny entry, so later admissions record would-denies, but closes no session; the response stays `{"disconnected": true}`. Shadow publishes cross-pod on its own channel, `buzz:nip-fi:shadow-disconnect`, which only shadow pods subscribe to, so no enforce pod (including older builds that know only `buzz:nip-fi:disconnect`) acts on a shadow command. Shadow pods also keep listening on `buzz:nip-fi:disconnect`, so their deny record includes enforce's real disconnects. Shadow claims command replays under `buzz:nip-fi:shadow-command:{hash}`: replays are still rejected within shadow, and a shadow accept never uses up the enforce claim. Enforce and Off keep the same channel, keys and close behavior. - **NIP-11.** Shadow serves the same document as enforce, including `limitation.federated_identity`, because clients only attach evidence to a relay that advertises it. ## Recording | Series | Labels | One increment per | |---|---|---| | `buzz_nip_fi_shadow_total` | `route` (`http`, `ws`, `audio`), `stage`, `outcome`, `community` | decision enforce would make: an HTTP admission, a refused upgrade, a session's NIP-FI AUTH decision, or its deadline passing before AUTH (`stage=deadline`) | | `buzz_nip_fi_shadow_strict_proof_total` | `route` (`bridge`, `blossom`), `outcome` (`pass`, `rejected`), `community` | strict NIP-98 side check | | `buzz_nip_fi_shadow_session_end_total` | `route` (`ws`, `audio`), `reason` (`expired`, `revoked`), `community` | admitted session enforce would have ended (admitted sessions only) | | `buzz_nip_fi_shadow_disconnect_total` | `route` (`admin`, `cross_pod`), `outcome` | disconnect-path event, in place of the real disconnect, lag, capacity and poison counters | `community` is the configured canonical URI or `unmapped`, never the raw `Host`; an unmapped or empty Host records `stage=community, outcome=unavailable`. A shadow pod never moves an enforce disconnect or failure counter. Strict-proof rates use their own `pass + rejected` total, and session-end rates divide by the `admit` count for the same route. There is no per-verdict `info` log; a would-deny writes a `debug` line with no token, claim or issuer. The pre-existing enforce `debug` line for the proven NIP-98 key is shared and also fires in shadow. Admin commands rejected for a bad header, body or proof, a replay, or a dependency error record no shadow verdict; only capacity rejection is counted. ## Not observed - Handler-side path-validation exits: an invalid Git owner or repo name on default-branch, a media path that is not a valid hash, and a Git pack URL with a query string record nothing from the handler. The router guard still records what enforce would refuse at the guard (for example a missing assertion), because enforce refuses those requests before it validates the path. - A refusal that is not a NIP-FI decision, such as a tenant-binding 404 behind a passing guard, or an ordinary WebSocket refusal after pairing (a ban, membership or join refusal before admission). ## Follow-ups - A bounded `route` label per HTTP surface (bridge, Blossom, Git, …) on `buzz_nip_fi_shadow_total`; today every HTTP verdict carries `route=http`. - One shared helper for the ten copied `observe_unbound` recording hooks, stacked on this PR. The shadow recorder and session suites and the root pairing witness run in `just test-unit` and the `scripts/run-tests.sh` fallback; the `AppState::new` shadow witness, which exercises the command verifier against real Redis, runs in the `external_infra_redis` lane. NIP-FI env tests recover a poisoned env lock so one panic cannot cascade. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
376 lines
16 KiB
Bash
Executable File
376 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# run-tests.sh — Run Buzz test suite
|
|
# =============================================================================
|
|
# Usage:
|
|
# ./scripts/run-tests.sh # run all tests (default)
|
|
# ./scripts/run-tests.sh unit # unit tests only (no infra needed)
|
|
# ./scripts/run-tests.sh integration # integration tests only
|
|
# ./scripts/run-tests.sh all # explicit all
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
MODE="${1:-all}"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${BLUE}[run-tests]${NC} $*"; }
|
|
success(){ echo -e "${GREEN}[run-tests]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[run-tests]${NC} $*"; }
|
|
error() { echo -e "${RED}[run-tests]${NC} $*" >&2; }
|
|
section(){ echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
|
|
|
|
cd "${REPO_ROOT}"
|
|
|
|
# ---- Load .env if present ---------------------------------------------------
|
|
|
|
if [[ -f ".env" ]]; then
|
|
log "Loading .env..."
|
|
set -o allexport
|
|
# shellcheck disable=SC1091
|
|
source .env
|
|
set +o allexport
|
|
else
|
|
# Use defaults matching docker-compose.yml
|
|
export DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/buzz" # sadscan:disable np.postgres.1
|
|
export PGHOST=localhost
|
|
export PGPORT=5432
|
|
export PGUSER=buzz
|
|
export PGPASSWORD=buzz_dev
|
|
export PGDATABASE=buzz
|
|
export REDIS_URL="redis://localhost:6379"
|
|
fi
|
|
|
|
# ---- Track results ----------------------------------------------------------
|
|
|
|
declare -a PASSED=()
|
|
declare -a FAILED=()
|
|
|
|
run_test_step() {
|
|
local name="$1"
|
|
shift
|
|
log "Running: ${name}"
|
|
if "$@"; then
|
|
success "${name} passed"
|
|
PASSED+=("${name}")
|
|
else
|
|
error "${name} FAILED"
|
|
FAILED+=("${name}")
|
|
fi
|
|
}
|
|
|
|
# ---- Check / start infra (for integration tests) ----------------------------
|
|
|
|
ensure_infra() {
|
|
"${REPO_ROOT}/bin/just" _ensure-migrations
|
|
}
|
|
|
|
# ---- Unit tests (no infra needed) -------------------------------------------
|
|
|
|
run_unit_tests() {
|
|
section "Unit Tests (no infra required)"
|
|
|
|
run_test_step "buzz-core tests" \
|
|
cargo test -p buzz-core --lib -- --nocapture
|
|
|
|
run_test_step "buzz-audit tests" \
|
|
cargo test -p buzz-audit --lib -- --nocapture
|
|
|
|
run_test_step "buzz-auth unit tests" \
|
|
cargo test -p buzz-auth --lib -- --nocapture
|
|
|
|
# S4 cross-pod NIP-FI disconnect payload tests (infra-free). Mirrors
|
|
# `just test-unit`.
|
|
run_test_step "buzz-pubsub conn_control NIP-FI tests" \
|
|
cargo test -p buzz-pubsub --lib conn_control::tests::nip_fi_disconnect_ -- --nocapture
|
|
|
|
run_test_step "buzz-voice tests" \
|
|
cargo test -p buzz-voice --lib -- --nocapture
|
|
|
|
run_test_step "buzz-cli tests" \
|
|
cargo test -p buzz-cli -- --nocapture
|
|
|
|
# buzz-sdk builder/validation unit tests: pure event-builder and input
|
|
# validation, no infra. Mirrors the nextest path in `just test-unit` — the
|
|
# two lists must stay in step. `--lib` matches the nextest invocation and
|
|
# avoids the full-package rustdoc dependency-resolution flake.
|
|
run_test_step "buzz-sdk unit tests" \
|
|
cargo test -p buzz-sdk --lib -- --nocapture
|
|
|
|
# Keep the relay-to-agent trust-boundary regressions in the fallback path
|
|
# when cargo-nextest is unavailable.
|
|
run_test_step "buzz-acp tests" \
|
|
cargo test -p buzz-acp -- --nocapture
|
|
|
|
# Keep MCP lifecycle coverage in step with the nextest path.
|
|
run_test_step "buzz-dev-mcp tests" \
|
|
cargo test -p buzz-dev-mcp -- --nocapture
|
|
|
|
|
|
# buzz-db migrator/lint unit tests (no infra): guard the embedded-migrator
|
|
# invariant (exactly the consolidated 0001; cutover/backfill stays an operator
|
|
# script, not startup state) and the tenant-scoping lints. The Postgres-backed
|
|
# buzz-db tests are #[ignore]d; nothing here (or in integration mode below,
|
|
# which runs `cargo test -p buzz-db` without --ignored) runs them — they need a
|
|
# separate isolated-DB gate, so --lib keeps this step infra-free.
|
|
run_test_step "buzz-db unit tests" \
|
|
cargo test -p buzz-db --lib -- --nocapture
|
|
|
|
run_test_step "buzz-media storage snapshot serialization test" \
|
|
cargo test -p buzz-media --lib bucket_index::tests::bucket_snapshot_json_round_trip_preserves_community_keys -- --exact --nocapture
|
|
|
|
run_test_step "buzz-admin storage snapshot tests" \
|
|
cargo test -p buzz-admin storage_snapshot -- --nocapture
|
|
|
|
# Multi-tenant conformance gate: independent replay checker + golden
|
|
# fixtures (buzz-conformance). Pure in-process trace replay, no infra.
|
|
run_test_step "buzz-conformance tests" \
|
|
cargo test -p buzz-conformance -- --nocapture
|
|
|
|
run_test_step "buzz-push-gateway tests" \
|
|
cargo test -p buzz-push-gateway -- --nocapture
|
|
run_test_step "buzz-push-gateway personal development tests" \
|
|
cargo test -p buzz-push-gateway --features personal-dev-app-attest -- --nocapture
|
|
|
|
# Kubernetes backend provider: pure decision layers driven by a fake
|
|
# substrate, no cluster. Mirrors the nextest path in `just test-unit` —
|
|
# the two lists must stay in step or the fallback silently covers less.
|
|
run_test_step "buzz-backend-kubernetes tests" \
|
|
cargo test -p buzz-backend-kubernetes -- --nocapture
|
|
|
|
# Keep fallback parity with `just test-unit`: one LaunchDarkly-feature run
|
|
# exercises both default and feature-gated buzz-feature-flags tests.
|
|
run_test_step "buzz-feature-flags tests (launchdarkly)" \
|
|
cargo test -p buzz-feature-flags --features launchdarkly -- --nocapture
|
|
|
|
# buzz-agent model-capabilities corpus: the Rust half of the cross-language
|
|
# drift guard. model_capabilities.rs embeds scripts/model-capabilities.json +
|
|
# scripts/normative-corpus.json via include_str! and replays the full locked
|
|
# corpus as pure in-process tests (no infra). Mirrors the nextest path in
|
|
# `just test-unit` — the two lists must stay in step.
|
|
run_test_step "buzz-agent unit tests" \
|
|
cargo test -p buzz-agent --lib -- --nocapture
|
|
|
|
# ACP author-gate and queue tests are pure unit tests. Keep this fallback in
|
|
# step with `just test-unit`; ignored lifecycle tests run elsewhere.
|
|
run_test_step "buzz-acp unit tests" \
|
|
cargo test -p buzz-acp --lib -- --nocapture
|
|
|
|
# Mirror the relay filters from `just test-unit`: the three handler modules,
|
|
# storage-snapshot helpers, readiness and router unit suites, and the single
|
|
# scoped admission regression in state::tests, plus the REQ lifecycle tests.
|
|
run_test_step "buzz-relay channel authorization tests" \
|
|
cargo test -p buzz-relay --lib handlers::channel_authz:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay moderation authorization tests" \
|
|
cargo test -p buzz-relay --lib handlers::moderation_authz:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay side-effects helper tests" \
|
|
cargo test -p buzz-relay --lib handlers::side_effects::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay storage snapshot tests" \
|
|
cargo test -p buzz-relay --lib storage_sweep::tests:: -- --nocapture
|
|
|
|
# Mirror the four audio/FI suites from `just test-unit`'s nextest expression.
|
|
# These are infra-free (no DB, no Redis); the `#[ignore]`-gated DB witnesses
|
|
# are excluded by cargo test's default filter. Keep in step with the Justfile
|
|
# `test-unit` relay expression.
|
|
run_test_step "buzz-relay audio join tests" \
|
|
cargo test -p buzz-relay --lib audio::join::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay audio handler tests" \
|
|
cargo test -p buzz-relay --lib audio::handler::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI gate tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_gate::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI session tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_session::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI shadow recorder tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_shadow::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI shadow session tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_shadow_session::tests:: -- --nocapture
|
|
|
|
# Mirror the NIP-FI (S3) stanza from `just test-unit`: module filters, then
|
|
# each exact name. Keep this list in step with that stanza's `test(=...)`s.
|
|
run_test_step "buzz-relay NIP-FI config tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_config:: -- --nocapture
|
|
|
|
# Infra-free REQ subscription-lifecycle tests, by exact name: the rest of
|
|
# handlers::req needs a database.
|
|
run_test_step "buzz-relay REQ subscription lifecycle tests" \
|
|
cargo test -p buzz-relay --lib -- --exact --nocapture \
|
|
handlers::req::tests::timed_out_historical_read_deregisters_before_closed \
|
|
handlers::req::tests::superseded_timeout_leaves_replacement_intact \
|
|
handlers::req::tests::search_claim_retires_live_and_yields_to_replacement \
|
|
handlers::req::tests::concurrent_claims_and_stale_teardowns_keep_the_last_owner \
|
|
handlers::req::tests::timeout_closed_is_emitted_before_a_replacement_can_claim \
|
|
handlers::req::tests::revoke_then_replacement_keeps_replacement_whole \
|
|
handlers::req::tests::claims_after_connection_cleanup_are_refused \
|
|
handlers::req::tests::dropped_terminal_frame_cancels_connection \
|
|
handlers::req::tests::revoke_dropped_terminal_frame_cancels_connection
|
|
|
|
run_test_step "buzz-relay readiness tests" \
|
|
cargo test -p buzz-relay --lib readiness::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay admission regression test" \
|
|
cargo test -p buzz-relay --lib state::tests::neither_a_confirmed_inactive_community_nor_a_failed_lookup_admits_the_socket -- --nocapture
|
|
|
|
run_test_step "buzz-relay router tests" \
|
|
cargo test -p buzz-relay --lib router::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI shared core tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_core::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI HTTP ingress tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_http::tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay API query parsing tests" \
|
|
cargo test -p buzz-relay --lib api::parse_query_tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay Git transport Off-mode precedence tests" \
|
|
cargo test -p buzz-relay --lib api::git::transport::off_mode_precedence_tests:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI upgrade tests" \
|
|
cargo test -p buzz-relay --lib nip_fi_upgrade:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay NIP-FI admin API tests" \
|
|
cargo test -p buzz-relay --lib api::nip_fi:: -- --nocapture
|
|
|
|
run_test_step "buzz-relay auth metrics contract tests" \
|
|
cargo test -p buzz-relay --lib metrics::contract_tests:: -- --nocapture
|
|
|
|
local nip_fi_exact_tests=(
|
|
audio::room::tests::roster_revisions_are_ordered_and_snapshot_is_authoritative
|
|
connection::tests::auth_lifecycle_reconciles_every_terminal_and_never_leaks_gauge
|
|
audio::room::tests::b1_pending_peer_removed_before_commit_emits_no_delta
|
|
audio::room::tests::b2_commit_peer_emits_exactly_one_joined_delta_and_marks_visible
|
|
audio::room::tests::b3_commit_peer_revision_is_monotone_between_concurrent_events
|
|
audio::room::tests::f7a_pending_peer_excluded_from_snapshot_until_committed
|
|
connection::tests::b2_cancelled_connection_event_frame_not_dispatched
|
|
connection::tests::b3_expiry_denial_precedes_close_through_send_loop
|
|
connection::tests::b3_root_pairing_denial_precedes_close_through_send_loop
|
|
connection::tests::cancellation_during_select_with_fi_denial_routes_through_bounded_path
|
|
connection::tests::cancelled_never_ready_sink_with_queued_fi_denial_exits_within_timeout
|
|
connection::tests::deadline_exp_is_earliest_selects_exp
|
|
connection::tests::deadline_max_connection_lifetime_is_earliest_selects_partition
|
|
connection::tests::deadline_no_lifetime_returns_upstream_only
|
|
connection::tests::expiry_notice_queued_on_ctrl_before_cancel
|
|
connection::tests::f3_root_outer_wrapper_delivers_denial_on_bootstrap_cancellation
|
|
connection::tests::f3_root_pre_built_expired_gate_terminates_connection
|
|
handlers::auth::tests::b2_pre_cancelled_connection_never_becomes_authenticated
|
|
handlers::auth::tests::fi_ban_check_error_emits_terminal_authorization_unavailable
|
|
handlers::auth::tests::fi_root_authorization_denied_rows_emit_identical_frames
|
|
handlers::auth::tests::fi_invalid_nip42_proof_emits_terminal_evidence_rejected
|
|
handlers::auth::tests::handle_auth_pairing_mismatch_runs_full_root_denial_path
|
|
handlers::auth::tests::shadow_root_auth_records_pairing_denial
|
|
handlers::auth::tests::shadow_root_invalid_nip42_retires_without_record
|
|
handlers::auth::tests::nip42_denial_class_separates_internal_failure_from_bad_evidence
|
|
handlers::event::tests::fanout_access::owner_only_kinds_keep_only_the_owner
|
|
handlers::event::tests::pubsub_fanout::pubsub_owner_only_kinds_reach_only_the_owner
|
|
handlers::event::tests::pubsub_fanout::dispatch_owner_only_kinds_reach_only_the_owner
|
|
handlers::req::tests::p1a_huddle_liveness_req_barrier_expiry_blocks_query_and_emission
|
|
state::tests::f3_cancellation_during_check_terminates_socket_without_waiting_for_check
|
|
state::tests::on_not_run_runs_once_on_each_deny_arm_and_never_on_admit
|
|
state::tests::conn_manager_disconnect_nip_fi_ignores_unproven_connection
|
|
state::tests::conn_manager_disconnect_nip_fi_is_issuer_scoped
|
|
state::tests::conn_manager_disconnect_nip_fi_sets_authorization_denied_reason
|
|
state::tests::nip_fi_disconnect_audio_is_issuer_scoped
|
|
state::tests::nip_fi_disconnect_closes_proven_audio_socket_and_sends_policy_close_reason
|
|
state::tests::nip_fi_disconnect_closes_target_audio_only_and_preserves_collocated_peer
|
|
state::tests::nip_fi_disconnect_does_not_close_different_pubkey_audio_socket
|
|
state::tests::nip_fi_disconnect_does_not_close_unproven_audio_socket
|
|
state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason
|
|
state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame
|
|
state::tests::manager_disconnect_sets_reason_enqueues_frame_then_cancels
|
|
)
|
|
local name
|
|
for name in "${nip_fi_exact_tests[@]}"; do
|
|
run_test_step "buzz-relay ${name}" \
|
|
cargo test -p buzz-relay --lib "$name" -- --exact --nocapture
|
|
done
|
|
|
|
run_test_step "buzz-relay binary tests" \
|
|
cargo test -p buzz-relay --bin buzz-relay -- --nocapture
|
|
}
|
|
|
|
# ---- DB / integration tests (infra required) --------------------------------
|
|
|
|
run_integration_tests() {
|
|
section "Integration Tests (requires running services)"
|
|
|
|
ensure_infra
|
|
|
|
run_test_step "buzz-db tests" \
|
|
cargo test -p buzz-db -- --nocapture
|
|
|
|
if find crates/buzz-auth/tests -maxdepth 1 -name '*.rs' -print -quit 2>/dev/null | grep -q .; then
|
|
run_test_step "buzz-auth integration tests" \
|
|
cargo test -p buzz-auth --test '*' -- --nocapture
|
|
else
|
|
run_test_step "buzz-auth (no integration tests found)" true
|
|
fi
|
|
|
|
run_test_step "workspace integration tests" \
|
|
cargo test --test '*' -- --nocapture 2>/dev/null || \
|
|
run_test_step "workspace integration tests (none found)" true
|
|
}
|
|
|
|
# ---- Main -------------------------------------------------------------------
|
|
|
|
START_TIME=$(date +%s)
|
|
|
|
case "${MODE}" in
|
|
unit)
|
|
run_unit_tests
|
|
;;
|
|
integration)
|
|
run_integration_tests
|
|
;;
|
|
all|*)
|
|
run_unit_tests
|
|
run_integration_tests
|
|
;;
|
|
esac
|
|
|
|
END_TIME=$(date +%s)
|
|
ELAPSED=$((END_TIME - START_TIME))
|
|
|
|
# ---- Summary ----------------------------------------------------------------
|
|
|
|
section "Test Summary"
|
|
echo ""
|
|
echo -e " Duration: ${ELAPSED}s"
|
|
echo ""
|
|
|
|
if [[ ${#PASSED[@]} -gt 0 ]]; then
|
|
echo -e " ${GREEN}Passed (${#PASSED[@]}):${NC}"
|
|
for t in "${PASSED[@]}"; do
|
|
echo -e " ${GREEN}pass${NC} ${t}"
|
|
done
|
|
fi
|
|
|
|
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo -e " ${RED}Failed (${#FAILED[@]}):${NC}"
|
|
for t in "${FAILED[@]}"; do
|
|
echo -e " ${RED}fail${NC} ${t}"
|
|
done
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
success "All tests passed!"
|
|
exit 0
|