#!/usr/bin/env bash # The doctor is deliberately exercised through its command boundary. The # fixture commands include sensitive-looking output so this test proves the # report only retains explicitly parsed, non-sensitive observations. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" doctor="$repo_dir/config/dot/quickshell/scripts/panama-doctor" fixture_root="$repo_dir/tests/quickshell/fixtures/doctor" fail() { printf 'panama doctor contract: %s\n' "$1" >&2 exit 1 } fixture="$(mktemp -d /tmp/panama-doctor.XXXXXX)" trap 'rm -rf "$fixture"' EXIT home="$fixture/home" config_home="$home/.config" state_home="$home/.local/state" runtime_dir="$fixture/runtime" bin_dir="$fixture/bin" data_home="$home/.local/share" mkdir -p "$config_home" "$state_home" "$runtime_dir" "$bin_dir" "$data_home/vicinae" cp "$fixture_root/bin/"* "$bin_dir/" chmod +x "$bin_dir"/* # These are intentionally tiny stand-ins for authored executable probes. The # named fixture scripts above cover probes whose output needs branch coverage. for tool in hyprctl wl-paste grim tesseract kitty nextcloud rustdesk kdeconnect-cli; do cat >"$bin_dir/$tool" <<'EOF' #!/usr/bin/bash case "${0##*/}" in hyprctl) printf 'Hyprland 0.50.0\n' ;; esac EOF chmod +x "$bin_dir/$tool" done cat >"$bin_dir/flatpak" <<'EOF' #!/usr/bin/bash if [[ "${1:-}" == "info" && "${2:-}" == "app.bluebubbles.BlueBubbles" \ && "${PANAMA_DOCTOR_FIXTURE_BLUEBUBBLES:-installed}" == "installed" ]]; then printf 'BlueBubbles fixture-secret-token\n' exit 0 fi exit 1 EOF chmod +x "$bin_dir/flatpak" cat >"$bin_dir/calendar-agenda" <<'EOF' #!/usr/bin/bash if [[ "${1:-}" != "probe" ]]; then exit 2 fi case "${PANAMA_DOCTOR_FIXTURE_CALENDAR:-ready}" in ready) printf '{"eds":true,"sourceRegistry":true,"enabledSources":2,"event":"fixture clipboard body"}\n' ;; malformed) printf 'calendar AA:BB:CC:DD:EE:FF\n' ;; timeout) /usr/bin/sleep 2; printf '{"enabledSources":2}\n' ;; *) printf '{"eds":true,"sourceRegistry":true,"enabledSources":0}\n' ;; esac EOF chmod +x "$bin_dir/calendar-agenda" cat >"$bin_dir/panama-brightness" <<'EOF' #!/usr/bin/bash case "${PANAMA_DOCTOR_FIXTURE_BRIGHTNESS:-ready}" in ready) printf '{"displays":[{"connector":"AA:BB:CC:DD:EE:FF"}],"error":""}\n' ;; denied) printf '{"displays":[],"error":"fixture-secret-token"}\n' ;; malformed) printf 'fixture clipboard body\n' ;; esac EOF chmod +x "$bin_dir/panama-brightness" mkdir -p "$config_home/autostart" touch "$config_home/autostart/nextcloud.desktop" for name in hypr quickshell uwsm vicinae; do ln -s "$repo_dir/config/dot/$name" "$config_home/$name" done ln -s "$repo_dir/config/local/share/vicinae/scripts" "$data_home/vicinae/scripts" run_doctor() { HOME="$home" \ PATH="$bin_dir" \ XDG_CURRENT_DESKTOP=Hyprland \ PANAMA_HOME_ASSISTANT_URL='https://fixture.invalid' \ PANAMA_HOME_ASSISTANT_TOKEN='fixture-secret-token' \ PANAMA_DOCTOR_ROOT="$repo_dir" \ PANAMA_DOCTOR_HOME="$home" \ PANAMA_DOCTOR_CONFIG_HOME="$config_home" \ PANAMA_DOCTOR_STATE_HOME="$state_home" \ PANAMA_DOCTOR_RUNTIME_DIR="$runtime_dir" \ PANAMA_DOCTOR_PATH="$bin_dir" \ PANAMA_DOCTOR_TIMEOUT="${PANAMA_DOCTOR_TIMEOUT:-0.2}" \ /usr/bin/python3 "$doctor" "$@" } expected_order=$'desktop.hyprland\ndesktop.quickshell\ndesktop.notifications\ndesktop.portals\ndesktop.hyprpaper\ndesktop.hypridle\ndesktop.vicinae\ninput.pipewire\ninput.clipboard\ninput.wallpaper\ninput.capture\ninput.ocr\ninput.brightness\nintegration.nextcloud\nintegration.rustdesk\nintegration.kdeconnect\nintegration.bluebubbles\nintegration.home-assistant\nintegration.calendar\npanama.runtime-links\npanama.vicinae-commands\npanama.selected-terminal\npanama.selected-launcher\npanama.processes\npanama.caffeine' assert_schema_and_redaction() { local snapshot="$1" jq -e '.schemaVersion == 1 and (.generatedAt | type == "string") and (.summary.status | IN("healthy", "warning", "error")) and (.context.session | IN("hyprland", "other")) and (.context.versions | type == "array") and ([.checks[].id] | length == 25) and ([.checks[].id] | unique | length == 25) and ([.checks[].status] | all(IN("ok", "warning", "error", "unconfigured")))' \ >/dev/null <<<"$snapshot" || fail "invalid schema: $snapshot" [[ "$(jq -r '.checks[].id' <<<"$snapshot")" == "$expected_order" ]] \ || fail "checks are not in the authored order" ! grep -Fq 'fixture-secret-token' <<<"$snapshot" \ || fail 'report exposed a fixture secret' ! grep -Fq 'fixture clipboard body' <<<"$snapshot" \ || fail 'report exposed clipboard or calendar content' ! grep -Fq 'AA:BB:CC:DD:EE:FF' <<<"$snapshot" \ || fail 'report exposed a device address' } check_status() { local snapshot="$1" id="$2" expected="$3" [[ "$(jq -r --arg id "$id" '.checks[] | select(.id == $id) | .status' <<<"$snapshot")" == "$expected" ]] \ || fail "$id did not report $expected: $snapshot" } snapshot="$(run_doctor --json)" assert_schema_and_redaction "$snapshot" # A healthy systemd-backed service stays healthy. check_status "$snapshot" desktop.hyprpaper ok # A missing required executable is an error rather than a crash. mv "$bin_dir/qs" "$bin_dir/qs.off" missing_qs="$(run_doctor --json)" check_status "$missing_qs" desktop.quickshell error mv "$bin_dir/qs.off" "$bin_dir/qs" # Optional integrations stay neutral until the user configures them. rm "$config_home/autostart/nextcloud.desktop" unconfigured_nextcloud="$(run_doctor --json)" check_status "$unconfigured_nextcloud" integration.nextcloud unconfigured touch "$config_home/autostart/nextcloud.desktop" # A configured integration that stopped is actionable with an authored label, # never an application name or command derived from probe output. stopped_nextcloud="$(PANAMA_DOCTOR_FIXTURE_STOPPED=nextcloud.service run_doctor --json)" check_status "$stopped_nextcloud" integration.nextcloud warning jq -e '.checks[] | select(.id == "integration.nextcloud") | .action == {kind:"open", label:"Open Nextcloud", confirm:false}' \ >/dev/null <<<"$stopped_nextcloud" || fail 'Nextcloud action was not authored' # DDC errors are classified without retaining connectors or bus addresses. denied_brightness="$(PANAMA_DOCTOR_FIXTURE_BRIGHTNESS=denied run_doctor --json)" check_status "$denied_brightness" input.brightness warning jq -e '.checks[] | select(.id == "input.brightness") | .action == {kind:"instructions", label:"View setup instructions", confirm:false, target:"ddc-permissions"}' \ >/dev/null <<<"$denied_brightness" || fail 'DDC instructions were not authored' # A bounded probe timeout becomes a result, never a helper failure. timed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=timeout PANAMA_DOCTOR_TIMEOUT=0.05 run_doctor --json)" check_status "$timed_calendar" integration.calendar warning jq -e '.checks[] | select(.id == "integration.calendar") | .action == {kind:"open", label:"Open Date & Time", confirm:false, target:"datetime"}' \ >/dev/null <<<"$timed_calendar" || fail 'calendar action was not authored' # Exact Panama/Caffeine inhibitor rows detect duplicates without exposing PIDs. duplicated_caffeine="$(PANAMA_DOCTOR_FIXTURE_CAFFEINE=duplicate run_doctor --json)" check_status "$duplicated_caffeine" panama.caffeine warning jq -e '.checks[] | select(.id == "panama.caffeine") | .action == {kind:"repair", label:"Release duplicate inhibitors", confirm:false}' \ >/dev/null <<<"$duplicated_caffeine" || fail 'Caffeine repair action was not authored' ! jq -r '.checks[] | select(.id == "panama.caffeine") | .detail' <<<"$duplicated_caffeine" | grep -Eq '[0-9]{3,}' \ || fail 'Caffeine detail exposed inhibitor PIDs' # Process counts use only exact authored names and never expose command lines or PIDs. duplicated_processes="$(PANAMA_DOCTOR_FIXTURE_PROCESSES=quickshell:duplicate run_doctor --json)" check_status "$duplicated_processes" panama.processes warning ! jq -r '.checks[] | select(.id == "panama.processes") | .detail' <<<"$duplicated_processes" | grep -Eq '[0-9]{3,}' \ || fail 'process detail exposed a PID' # Configured Home Assistant failures route to the exact authored Settings page. rm "$config_home/quickshell" mkdir -p "$config_home/quickshell/scripts" home_assistant_failure="$(run_doctor --json)" check_status "$home_assistant_failure" integration.home-assistant warning jq -e '.checks[] | select(.id == "integration.home-assistant") | .action == {kind:"open", label:"Open Home settings", confirm:false, target:"home-phone"}' \ >/dev/null <<<"$home_assistant_failure" || fail 'Home Assistant action was not routed to home-phone' # Invalid probe text is contained in its own check and never copied to JSON. malformed_calendar="$(PANAMA_DOCTOR_FIXTURE_CALENDAR=malformed run_doctor --json)" check_status "$malformed_calendar" integration.calendar warning assert_schema_and_redaction "$malformed_calendar" summary="$(run_doctor --summary)" [[ "$summary" =~ ^Panama\ system\ health:\ (healthy|warning|error)\ \([0-9]+\ ok,\ [0-9]+\ warnings,\ [0-9]+\ errors,\ [0-9]+\ unconfigured\)$ ]] \ || fail "summary is not concise: $summary" printf 'panama doctor contract: PASS\n'