Files
buzz/scripts/run-tests.sh
Ian Oberst 85b7331784 fix(relay): partition audit observability follow-ups (#7994)
> [!IMPORTANT]
> **The alert rules changed in c3a314e71.** If you reviewed the alert
guidance earlier, please look again.
> - **The two range-based rules now apply only to live pods.** A range
selector such as `[30m]` keeps reading a terminated pod's samples until
they leave the window. So `BuzzPartitionAuditNotRunning` could fire for
about 10 minutes after normal pod turnover, and
`BuzzPartitionAuditFailing` could keep firing for up to an hour after
the pod was gone. Each rule now adds `and <counter>`, which requires the
counter to be exported right now:
>   ```yaml
>   expr: |
> sum without (outcome) (increase(buzz_partition_audit_runs_total[30m]))
== 0
>       and sum without (outcome) (buzz_partition_audit_runs_total)
>   ```
>   ```yaml
>   expr: |
>     increase(buzz_partition_audit_failures_total[1h]) >= 2
>       and buzz_partition_audit_failures_total
>   ```
> - **The audit counters start at 0.** When the relay starts, it exports
every `buzz_partition_audit_runs_total{table, outcome}` series and
`buzz_partition_audit_failures_total` at 0, before the first audit.
`increase()` ignores a new series' first sample. Before this change, it
could miss the first periodic failure, or read a table's first
`degraded` run as a stopped loop. This change is also what makes the
`and <counter>` gate work: the counters now exist for a relay's whole
life.
>
> The doc's old claim that pod turnover can't leave an alert firing was
true only of the instant-selector stale rule. The doc now explains the
difference.

## Why

These are the observability follow-ups deferred from #6515 ("fix(db):
audit partition catalog before creation"):

- **Timestamp-age alerts go silent** ([review
comment](https://github.com/block/buzz/pull/6515#discussion_r4127212141)).
Partition gauges are set only when a table audits successfully, and the
exporter evicts idle gauges after 2,700 s by default. After sustained
failures, `buzz_partition_audit_last_success_timestamp_seconds`
disappears. A table that has never audited successfully never has it. An
alert on `time() - last_success` then has no input.
- **Startup fallback failures are not counted** ([review
comment](https://github.com/block/buzz/pull/6515#discussion_r4127212158)).
When the startup fallback audit fails, it logs an error but does not
increment `buzz_partition_audit_failures_total`. Periodic failures do.
- **Repaired environments are always `degraded`**.
`PartitionTableAudit::degraded()` counts every legacy leaf, even one
wholly in the past. The `bb-block-staging` audit of the repaired catalog
reports `degraded` only because of `events_p2026_07_08_legacy`
`[2026-07-01, 2026-09-01)`. With this rule, every repaired environment
stays `degraded`, so nobody can alert on the outcome.
- **Partition config tests can see a poisoned env** ([review
comment](https://github.com/block/buzz/pull/6515#discussion_r4137416297)).
Two partition config tests take only `ENV_MUTEX`. A NIP-FI test running
in the same process can make them see its deliberately invalid
environment.

Partition health is still diagnostic only, through
`/_status.partition_catalog` and metrics. Nothing here affects
`/_readiness`. Handling concurrent creation (#6515 item 4) is out of
scope and stays with the catch-all advancement work.

## What

- **Success timestamp survives failures.** Each failed table audit
refreshes `buzz_partition_audit_last_success_timestamp_seconds` with
`increment(0.0)`, which leaves the value unchanged. It still resets the
exporter's idle tracking, the same mechanism as
`refresh_legacy_active_gauge_recency`. Sustained failures therefore keep
the last success exported and its age rising. A table that has never
succeeded exports `0`. A failed audit still never makes the last success
look fresh.
- **Alert guidance.** New `docs/partition-catalog-monitoring.md` covers
the metrics, how long each series lives, and the audit outcomes. It
notes that `runs_total` has no `unsafe` outcome. It gives three alert
rules:
- timestamp age, which also covers never-successful tables and sustained
failures;
- `sum without (outcome)
(increase(buzz_partition_audit_runs_total[30m])) == 0` for a stopped
audit loop. Counters are never idle-evicted, and this rule fires before
the stopped loop's timestamp is evicted, so there is no false resolve;
  - an aggregate failure-counter rule.

Both range-based rules also require their counter to be exported right
now (`and <counter>`), so a terminated pod's leftover samples can't
raise or hold an alert.
- **Zero counter baselines.** `try_install` exports each
`buzz_partition_audit_runs_total{table, outcome}` series (`ok`,
`degraded`, and `error` for both tables) and
`buzz_partition_audit_failures_total` at 0 before the startup audit, the
same way the auth series already start at 0. A failure in the startup
audit itself can still happen before the first scrape. The doc says so;
the stale rule covers that case.
- **Failure counter.** `buzz_partition_audit_failures_total` now counts
failed explicit audit attempts: the startup fallback audit and each
periodic audit. Both paths go through one helper,
`counted_partition_audit`. A failed `ensure_future_partitions` call,
such as a DDL error that the fallback then recovers, is not counted by
itself. A single attempt never counts twice.
- **Historical legacy leaves.** A legacy leaf is historical when its
finite upper bound is at or before the current month start. It is still
reported under `children` as `legacy_leaf`, but it no longer degrades
the table. A legacy leaf that still covers the current month or any
lookahead month stays degraded. All other degradation causes are
unchanged. The verdict now lives in `PartitionAuditReport::outcome()`,
which `buzz-admin partition-audit` uses. The JSON outcome names and exit
codes are the same as before.
- **Test hygiene.** Both partition config tests now use `env_guards()`.

## How it was tested

- `cargo fmt --all -- --check` and `cargo clippy -p buzz-db -p
buzz-relay -p buzz-admin --all-targets -- -D warnings`.
- New unit tests:
- The repaired incident layout with an injected clock: `degraded` in
August 2026 and `ok` on 2026-09-01T00:00Z, when the upper bound equals
the current month start, and on 2026-09-15. The legacy leaf is still in
the serialized report. The `outcome` label on the run counter follows
the verdict.
- A table test of the legacy-leaf predicate: the upper bound before, at,
and one second after the month start; lookahead and beyond-horizon
leaves; historical leaves with trigger drift; anomalous and `DEFAULT`
children; no audited month.
- Outcome precedence (error, then unsafe, then degraded) and the
`buzz-admin` exit-code mapping.
- Startup: a failed fallback counts once, a successful fallback after a
DDL failure counts nothing, and a successful ensure never runs the
fallback. Each periodic failure counts once. Against an unreachable
database, startup counts one aggregate failure while each table still
records two `error` runs.
- On the production exporter, never-successful audits leave run counters
and a `0` success timestamp for both tables.
- On the production exporter, all six run-counter series and the failure
counter read 0 before any audit. After one failed audit, `error` reads 1
and `ok`/`degraded` stay at 0. Removing the initialization fails this
test.
- PostgreSQL lane (`scripts/postgres-test-run.sh`) with new or extended
tests:
- The repaired layout is not degraded in September. The same catalog is
degraded in August.
- With a 1 s idle timeout, a failed audit leaves the success timestamp
unchanged, and sustained failures keep it exported past the idle
timeout. When audits stop, the timestamp is evicted but the run counters
remain. The next success exports a fresh timestamp.
- Mutation check: removing the failure-path refresh fails both alert
fixtures.
- These fixtures check the emitted series. They do not evaluate the
rules themselves, including the `and <counter>` gate. `promtool` isn't
available locally or in CI, and the doc points to `promtool test rules`
for that.
- `scripts/test-postgres-test-discovery.sh`.
- `config::` and `nip_fi_config::` tests run together in one process
five times.

## Related

This PR conflicts textually with #7993 (catch-all advancement) in
`crates/buzz-db/src/store/partition.rs` and
`crates/buzz-relay/src/main.rs`. Whichever PR merges second will be
rebased onto the other.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Ian Oberst <ioberst@block.xyz>
Co-authored-by: Ian Oberst <ioberst@block.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:57:54 -07:00

422 lines
19 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
# Cross-pod disconnect payload tests (NIP-FI and community archive fence;
# infra-free). Mirrors `just test-unit`.
run_test_step "buzz-pubsub conn_control tests" \
cargo test -p buzz-pubsub --lib conn_control::tests:: -- --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-db source-policy tests" \
cargo test -p buzz-db --test observability_source -- --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
run_test_step "buzz-admin community archive command tests" \
cargo test -p buzz-admin communities -- --nocapture
run_test_step "buzz-admin partition audit outcome contract test" \
cargo test -p buzz-admin tests::partition_audit_outcomes_keep_their_json_names_and_exit_codes -- --exact --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 push runtime tests" \
cargo test -p buzz-relay --lib push_runtime:: -- --nocapture
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
run_test_step "buzz-relay startup step tests" \
cargo test -p buzz-relay --lib startup_steps::tests:: -- --nocapture
# telemetry and config tests mutate process env. libtest runs one process
# with many threads (nextest, used by `just test-unit`, isolates each test),
# so run them single-threaded here. `--skip` keeps the substring filter
# `config::tests::` from also selecting nip_fi_config, which runs above.
run_test_step "buzz-relay telemetry tests" \
cargo test -p buzz-relay --lib telemetry::tests:: -- --test-threads=1 --nocapture
run_test_step "buzz-relay config tests" \
cargo test -p buzz-relay --lib config::tests:: -- --skip nip_fi_config:: --test-threads=1 --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 \
handlers::req::tests::count_fallback_covers_filters_that_can_match_private_deletions
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 archive revalidation fence test" \
cargo test -p buzz-relay --lib state::tests::periodic_revalidation_disconnects_inside_the_fenced_callback -- --exact --nocapture
run_test_step "buzz-relay bare disconnect fail-closed test" \
cargo test -p buzz-relay --lib state::tests::bare_community_disconnect_fails_closed_when_the_fence_is_unavailable -- --exact --nocapture
run_test_step "buzz-relay archived close-reason test" \
cargo test -p buzz-relay --lib connection::tests::send_loop_sends_policy_close_when_community_is_archived -- --exact --nocapture
run_test_step "buzz-relay community close first-writer tests" \
cargo test -p buzz-relay --lib state::tests::community_disconnect_then_nip_fi_keeps_community_deleted_reason -- --exact --nocapture
run_test_step "buzz-relay community close suppresses losing denial frame test" \
cargo test -p buzz-relay --lib state::tests::disconnect_community_wins_reason_losing_nip_fi_does_not_enqueue_frame -- --exact --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
run_test_step "buzz-relay partition alert series tests" \
cargo test -p buzz-relay --lib metrics::partition_alert_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
run_test_step "buzz-relay boot lifecycle tests" \
cargo test -p buzz-relay --test boot_lifecycle -- --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