#!/usr/bin/env bash # Behavioral coverage for the QML handoff after the helper commits a restore. # The harness has a unique shell identity, isolated XDG roots, and fake external # consumers. It records the real SettingsBackup call order without touching the # daily-driver shell, compositor, keymap, or wallpaper. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" service="$repo_dir/config/dot/quickshell/services/SettingsBackup.qml" harness="$repo_dir/config/dot/quickshell/settings-backup-harness.qml" work="$(mktemp -d /tmp/panama-settings-backup-live.XXXXXX)" fail() { printf 'settings backup live contract: %s\n' "$1" >&2 exit 1 } qs_test() { XDG_CONFIG_HOME="$work/config" XDG_STATE_HOME="$work/state" qs -p "$harness" "$@" } cleanup() { qs_test kill >/dev/null 2>&1 || true rm -rf "$work" } trap cleanup EXIT # The production command boundary must remain argv-only. rg -Fq 'actionRun.exec([root.helperPath, "save", root.serialiseHomeState()]);' "$service" \ || fail 'save does not pass live Home state as one argument' rg -Fq 'actionRun.exec([root.helperPath, "restore", name]);' "$service" \ || fail 'restore is not executed through an argument array' if rg -q 'bash.*-c|sh.*-c' "$service"; then fail 'the restore service constructs a shell command' fi # The harness replaces these seams, while these mappings prove the production # defaults still delegate to Panama's existing public service APIs. for mapping in \ 'HomePreferences.resetHomeDefaults();' \ 'HomePreferences.initialize(ids);' \ 'HomePreferences.setAlias(id, alias);' \ 'DesktopPreferences.reload();' \ 'SystemSettings.applyPersistedDisplayPolicy();' \ 'Keybinds.applyReload();' \ 'Wallpaper.set(path);' \ 'Quickshell.reload(false);'; do rg -Fq "$mapping" "$service" || fail "production restore seam is missing: $mapping" done qs_test --daemonize >"$work/quickshell.log" 2>&1 ready=false for _ in $(seq 1 60); do if qs_test ipc show 2>/dev/null | rg -q '^target settings-backup-behavior$'; then ready=true break fi sleep 0.1 done if [[ "$ready" != true ]]; then sed -n '1,200p' "$work/quickshell.log" >&2 fail 'isolated SettingsBackup harness did not start' fi qs_test ipc call settings-backup-behavior reset >/dev/null payload='{"restored":"settings-20260818-010203004.json","home":{"present":true,"data":{"initialized":true,"favorites":[{"id":"light.desk","alias":"Desk"},{"id":"light.office","alias":"Office"}]}}}' [[ "$(qs_test ipc call settings-backup-behavior apply "$payload")" == "true" ]] \ || fail 'valid restore output was rejected' status="" for _ in $(seq 1 50); do status="$(qs_test ipc call settings-backup-behavior status)" jq -e '.calls[-1] == "shell.reload"' <<<"$status" >/dev/null 2>&1 && break sleep 0.1 done jq -e ' .calls == [ "home.reset", "home.initialize:light.desk,light.office", "home.alias:light.desk=Desk", "home.alias:light.office=Office", "desktop.reload", "system.apply", "keybinds.reload", "wallpaper.set:/tmp/restored-wallpaper.jpg", "shell.reload" ] and .initialized == true and .favorites == [ {"id":"light.desk","alias":"Desk"}, {"id":"light.office","alias":"Office"} ] ' <<<"$status" >/dev/null || fail "restore handoff order/state was wrong: $status" # Invalid output is rejected before Home state or external consumers change. qs_test ipc call settings-backup-behavior reset >/dev/null invalid='{"home":{"present":true,"data":{"initialized":true,"favorites":[{"id":"light.desk","alias":"One"},{"id":"light.desk","alias":"Two"}]}}}' [[ "$(qs_test ipc call settings-backup-behavior apply "$invalid")" == "false" ]] \ || fail 'duplicate Home state was accepted' status="$(qs_test ipc call settings-backup-behavior status)" jq -e '.calls == [] and .initialized == false and .favorites == []' <<<"$status" >/dev/null \ || fail 'invalid restore output caused partial live mutations' # An absent Home generation uses the same ordered external handoff but leaves # the live Home service reset rather than manufacturing an initialized store. qs_test ipc call settings-backup-behavior reset >/dev/null absent='{"restored":"settings-20260818-010203005.json","home":{"present":false}}' [[ "$(qs_test ipc call settings-backup-behavior apply "$absent")" == "true" ]] \ || fail 'absent Home restore output was rejected' for _ in $(seq 1 50); do status="$(qs_test ipc call settings-backup-behavior status)" jq -e '.calls[-1] == "shell.reload"' <<<"$status" >/dev/null 2>&1 && break sleep 0.1 done jq -e ' .calls == [ "home.reset", "desktop.reload", "system.apply", "keybinds.reload", "wallpaper.set:/tmp/restored-wallpaper.jpg", "shell.reload" ] and .initialized == false and .favorites == [] ' <<<"$status" >/dev/null || fail "absent Home handoff was wrong: $status" trap - EXIT cleanup printf 'settings backup live contract: PASS\n'