#!/usr/bin/env bats # tests/deploy.bats — black-box tests for scripts/deploy.sh (Refs #42) # # Contract (see script header): the repo checkout IS the deployment; sync adopts # identicals, links new files, quarantines-and-replaces divergents, and stows the # zz-$(hostname) package last. One thing it must NEVER do is deploy generated theme # output: noctalia rewrites `noctalia.*` / `themes/noctalia*` in place, so a tracked # copy is a stale snapshot, and a sync would revert a live palette to whatever colour # scheme was current on the day it was committed. # # Nothing is observed except exit status, stdout, the file/symlink shape under $HOME, # and the quarantine directory. setup() { TEST_TMP="$(mktemp -d)" REPO="$TEST_TMP/repo" DEPLOY="$BATS_TEST_DIRNAME/../scripts/deploy.sh" mkdir -p "$REPO/scripts" "$REPO/dotfiles" # A git repo, because the deployment checkout IS one and deploy.sh arms hooksPath. git init -q -b main "$REPO" cp "$DEPLOY" "$REPO/scripts/deploy.sh" chmod +x "$REPO/scripts/deploy.sh" export HOME="$TEST_TMP/home" mkdir -p "$HOME" export DEPLOY_REPO="$REPO" DEPLOY_HOST=box # systemctl stub: records every call and answers is-enabled from a file. The real # systemctl must never run here — on this box it would start a screen lock. mkdir -p "$TEST_TMP/bin" export STUB_LOG="$TEST_TMP/systemctl.log" UNIT_STATE="$TEST_TMP/unit-state" : > "$STUB_LOG" cat > "$TEST_TMP/bin/systemctl" <<'STUB' #!/usr/bin/env bash echo "systemctl $*" >> "$STUB_LOG" if [ "$2" = is-enabled ]; then state="$(cat "$UNIT_STATE" 2>/dev/null || echo disabled)" printf '%s\n' "$state" case "$state" in disabled | "") exit 1 ;; *) exit 0 ;; esac fi if [ "$2" = enable ]; then # Modelled, not stubbed flat: systemd refuses to enable a unit whose file has no [Install] # section, and the deployed file is what it reads. A stub that always says yes hides the # one case this repo actually ships (live-sync.service is driven by its timer). target="" for a in "$@"; do case "$a" in *.service | *.timer | *.socket) target="$a" ;; esac done file="$HOME/.config/systemd/user/${target##*/}" if [ -e "$file" ] && ! grep -q '^\[Install\]' "$file"; then printf 'The unit files have no installation config (WantedBy=, RequiredBy=, Also= settings in the [Install] section). This means they are not meant to be enabled using this command.\n' >&2 exit 1 fi fi STUB chmod +x "$TEST_TMP/bin/systemctl" export PATH="$TEST_TMP/bin:$PATH" } teardown() { rm -rf "$TEST_TMP"; } # repo_file repo_file() { mkdir -p "$(dirname "$REPO/dotfiles/$1")"; printf '%s\n' "$2" > "$REPO/dotfiles/$1"; } # live_file live_file() { mkdir -p "$(dirname "$1")"; printf '%s\n' "$2" > "$1"; } # systemd_unit — a unit in the systemd-user package systemd_unit() { mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user" printf '[Unit]\nDescription=%s\n[Service]\nExecStart=%s\n[Install]\nWantedBy=default.target\n' "$1" "$2" \ > "$REPO/dotfiles/systemd-user/.config/systemd/user/$1" } # systemd_unit_no_install — a unit nothing can enable (live-sync.service: # it is pulled in by live-sync.timer, so it carries no [Install] section on purpose). systemd_unit_no_install() { mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user" printf '[Unit]\nDescription=%s\n[Service]\nType=oneshot\nExecStart=%s\n' "$1" "$2" \ > "$REPO/dotfiles/systemd-user/.config/systemd/user/$1" } # systemd_timer — a schedule; it has no ExecStart of its own. systemd_timer() { mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user" printf '[Unit]\nDescription=%s\n[Timer]\nOnCalendar=hourly\nPersistent=true\n[Install]\nWantedBy=timers.target\n' "$1" \ > "$REPO/dotfiles/systemd-user/.config/systemd/user/$1" } @test "generated theme output is never adopted into a symlink" { repo_file "ghostty/.config/ghostty/themes/noctalia" 'background = "#f43841"' live_file "$HOME/.config/ghostty/themes/noctalia" 'background = "#f43841"' run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" == *"SKIP"*".config/ghostty/themes/noctalia"* ]] [ -f "$HOME/.config/ghostty/themes/noctalia" ] [ ! -L "$HOME/.config/ghostty/themes/noctalia" ] } @test "a diverged live palette is never quarantined and reverted to a stale copy" { # The dangerous shape: the tracked file matches today's palette, the theme changes # tomorrow, and sync would quarantine the live file and restore the stale one. repo_file "niri/.config/niri/noctalia.kdl" 'active-color "#e01b24"' live_file "$HOME/.config/niri/noctalia.kdl" 'active-color "#ff8c00"' run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] grep -q 'active-color "#ff8c00"' "$HOME/.config/niri/noctalia.kdl" [ ! -L "$HOME/.config/niri/noctalia.kdl" ] [ ! -d "$REPO/.quarantine" ] } @test "an ordinary identical config is still adopted" { # Control against an exclusion so broad the tool stops deploying anything. repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"' live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "gruber"' run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [ -L "$HOME/.config/ghostty/config.ghostty" ] [ "$(realpath "$HOME/.config/ghostty/config.ghostty")" = "$(realpath "$REPO/dotfiles/ghostty/.config/ghostty/config.ghostty")" ] } @test "--check reports and writes nothing" { repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"' live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "tokyo-night"' run "$REPO/scripts/deploy.sh" --check [ "$status" -eq 0 ] [[ "$output" == *"DRIFT"* || "$output" == *"QUAR"* ]] [ ! -L "$HOME/.config/ghostty/config.ghostty" ] grep -q 'theme = "tokyo-night"' "$HOME/.config/ghostty/config.ghostty" [ ! -d "$REPO/.quarantine" ] } @test "a unit already enabled is enabled but not started" { systemd_unit lock.service /bin/sh printf 'enabled\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" == *"not started"* ]] grep -q 'systemctl --user enable lock.service' "$STUB_LOG" ! grep -q -- '--now' "$STUB_LOG" } @test "a linked unit is not started either" { # `is-enabled` exits 1 for a linked unit, exactly as it does for a disabled one, so the # printed state is the signal — exit status cannot tell them apart. systemd_unit timer-driven.service /bin/sh printf 'linked\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] ! grep -q -- '--now' "$STUB_LOG" } @test "a disabled unit is enabled and started, so a fresh box still comes up" { systemd_unit fresh.service /bin/sh printf 'disabled\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] grep -q 'systemctl --user enable --now fresh.service' "$STUB_LOG" } @test "a unit whose ExecStart is absent here stays inert" { systemd_unit otherbox.service /nonexistent/daemon printf 'disabled\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" == *"SKIP"*otherbox.service* ]] ! grep -q enable "$STUB_LOG" } @test "a unit whose ExecStart uses a systemd specifier still resolves here" { # `%h` is expanded by systemd, not by the shell, so matching the literal string makes a # unit belonging to THIS box look foreign and skip it in silence. The victim is this repo's # own heartbeat: `ExecStart=%h/.omp/scripts/live-sync.sh`, skipped on every box. Refs #44. mkdir -p "$HOME/.local/bin" printf '#!/bin/sh\ntrue\n' > "$HOME/.local/bin/helper" chmod +x "$HOME/.local/bin/helper" systemd_unit heartbeat.service '%h/.local/bin/helper' printf 'linked\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" == *"heartbeat.service (already linked"* ]] grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG" ! grep -q -- '--now' "$STUB_LOG" } @test "a unit nothing can enable is reported as what it is, never as ENABLE" { # `systemctl --user enable live-sync.service` fails on every deploy: the file has no # [Install] section (its timer arms it), and systemd says so with a non-zero exit. The # deploy swallowed that and printed ENABLE, so the one line the operator reads to learn # what a rebuild armed was false on every box, on the repo's own heartbeat. systemd_unit_no_install heartbeat.service /bin/sh printf 'linked\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" == *"heartbeat.service"*"[Install]"* ]] [[ "$output" != *"ENABLE heartbeat.service"* ]] # No futile call: the section is readable from the file being deployed. ! grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG" } @test "a timer in the tier is armed, because a service on its own has no schedule" { # The loop walked *.service only. On a rebuilt box live-sync.service is deployed and the # timer is never enabled, so the heartbeat that commits machine state simply does not run # — and the deploy report says nothing about it. systemd_unit heartbeat.service /bin/sh systemd_timer heartbeat.timer heartbeat.service printf 'disabled\n' > "$UNIT_STATE" run "$REPO/scripts/deploy.sh" [ "$status" -eq 0 ] [[ "$output" != *"SKIP heartbeat.timer"* ]] grep -q 'systemctl --user enable --now heartbeat.timer' "$STUB_LOG" }