#!/usr/bin/env bats # Seam: the agent-trash purger. # Behavioural contract only — what an operator sees (stdout plan lines, summary # line, exit code, the log line it leaves behind) and what survives on disk. # The script's own choice of implementation is not asserted. load helpers PURGE_SH="$REPO_ROOT/scripts/purge-agenttrash.sh" # fixture_home — a fake $HOME holding every shape the purger must handle: # the home's own .AGENTTRASH (the dated-staging convention, emptied in place) # and one populated plus one flat per-repo .AGENTTRASH (removed outright), plus # a file that is not trash and must survive. fixture_home() { mkdir -p "$1/repo-a/.AGENTTRASH/bucket" "$1/repo-b/.AGENTTRASH" \ "$1/.AGENTTRASH/20260101-topic/.AGENTTRASH/inner" : > "$1/repo-a/.AGENTTRASH/bucket/staged.txt" : > "$1/repo-b/.AGENTTRASH/loose.md" : > "$1/.AGENTTRASH/20260101-topic/staged.txt" : > "$1/.AGENTTRASH/20260101-topic/.AGENTTRASH/inner/deep.txt" : > "$1/repo-a/keep.txt" } @test "script exists and is executable" { [ -f "$PURGE_SH" ] [ -x "$PURGE_SH" ] } @test "usage lists every documented option" { run "$PURGE_SH" --help [ "$status" -eq 0 ] printf '%s\n' "$output" | grep -q -- '--execute' printf '%s\n' "$output" | grep -q -- '--quiet' printf '%s\n' "$output" | grep -q -- '--log' } @test "unknown option is a usage error" { run bash -c 'export HOME="${1:?}"; mkdir -p "$HOME"; exec "$2" --bogus' \ _ "$BATS_TEST_TMPDIR" "$PURGE_SH" [ "$status" -eq 2 ] } @test "every positional root is honoured, not just the first" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home" mkdir -p "$home/x1/.AGENTTRASH" "$home/x2/.AGENTTRASH" : > "$home/x1/.AGENTTRASH/a" : > "$home/x2/.AGENTTRASH/b" run "$PURGE_SH" --log "$home/purge.log" "$home/x1" "$home/x2" [ "$status" -eq 0 ] printf '%s\n' "$output" | grep -qxF "plan empty $home/x1/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF "plan empty $home/x2/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF 'removed=2 deleted=0 refused=0' } @test "--log=FORM accepts its value in one word" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home" run "$PURGE_SH" "--log=$home/purge.log" "$home" [ "$status" -eq 0 ] [ -f "$home/purge.log" ] } @test "dry run plans the removals and deletes nothing" { home="$BATS_TEST_TMPDIR/home" fixture_home "$home" run "$PURGE_SH" --log "$home/purge.log" "$home" [ "$status" -eq 0 ] printf '%s\n' "$output" | grep -qxF "plan empty $home/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-a/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-b/.AGENTTRASH" [ "$(printf '%s\n' "$output" | grep -c '^plan ')" -eq 3 ] printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=0 refused=0' [ -f "$home/repo-a/.AGENTTRASH/bucket/staged.txt" ] [ -f "$home/repo-b/.AGENTTRASH/loose.md" ] [ -f "$home/.AGENTTRASH/20260101-topic/staged.txt" ] [ -f "$home/purge.log" ] } @test "execute empties the home trash and removes the repo trees" { home="$BATS_TEST_TMPDIR/home" fixture_home "$home" run "$PURGE_SH" --execute --log "$home/purge.log" "$home" [ "$status" -eq 0 ] printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=3 refused=0' # the home trash is emptied in place: it survives, empty — including a # .AGENTTRASH nested inside it, which dies with its parent. printf '%s\n' "$output" | grep -qxF "plan empty $home/.AGENTTRASH" [ -d "$home/.AGENTTRASH" ] [ -z "$(ls -A "$home/.AGENTTRASH")" ] # the per-repo trees are removed outright, their content is what was staged. printf '%s\n' "$output" | grep -qxF "plan remove $home/repo-a/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF "removed $home/repo-a/.AGENTTRASH" printf '%s\n' "$output" | grep -qxF "removed $home/repo-b/.AGENTTRASH" [ ! -e "$home/repo-a/.AGENTTRASH" ] [ ! -e "$home/repo-b/.AGENTTRASH" ] [ -f "$home/repo-a/keep.txt" ] [ -f "$home/purge.log" ] } @test "execute leaves no lock behind" { home="$BATS_TEST_TMPDIR/home" fixture_home "$home" "$PURGE_SH" --quiet --execute --log "$home/purge.log" "$home" [ ! -e "$home/.agenttrash-purge.lock" ] } @test "the second execute is a no-op with an empty plan" { home="$BATS_TEST_TMPDIR/home" fixture_home "$home" "$PURGE_SH" --quiet --execute --log "$home/purge.log" "$home" run "$PURGE_SH" --execute --log "$home/purge.log" "$home" [ "$status" -eq 0 ] } @test "a symlinked trash dir is refused, its target survives" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home/live" : > "$home/live/keep.txt" ln -s "$home/live" "$home/.AGENTTRASH" run "$PURGE_SH" --execute --log "$home/purge.log" "$home" [ "$status" -eq 1 ] printf '%s\n' "$output" | grep -q 'refused' [ -L "$home/.AGENTTRASH" ] [ -f "$home/live/keep.txt" ] } @test "/ is refused as a root without touching anything else" { home="$BATS_TEST_TMPDIR/home" fixture_home "$home" run "$PURGE_SH" --execute --log "$home/purge.log" / "$home" [ "$status" -eq 1 ] printf '%s\n' "$output" | grep -qxF 'purge-agenttrash: refused / (root refusal)' printf '%s\n' "$output" | grep -qxF 'removed=3 deleted=3 refused=1' } @test "a root that is a plain file is refused and exits nonzero" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home" : > "$home/stray.txt" run "$PURGE_SH" --execute --log "$home/purge.log" "$home/stray.txt" [ "$status" -eq 1 ] printf '%s\n' "$output" | grep -qxF "purge-agenttrash: refused $home/stray.txt (not a real directory)" printf '%s\n' "$output" | grep -qxF 'removed=0 deleted=0 refused=1' [ -f "$home/stray.txt" ] } @test "a root path containing whitespace is a usage error" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home" run "$PURGE_SH" --log "$home/purge.log" "my project" [ "$status" -eq 2 ] printf '%s\n' "$output" | grep -q 'root contains whitespace' } @test "an empty trash dir yields an empty plan and an empty log line" { home="$BATS_TEST_TMPDIR/home" mkdir -p "$home" run "$PURGE_SH" --log "$home/purge.log" "$home" [ "$status" -eq 0 ] printf '%s\n' "$output" | grep -qxF 'removed=0 deleted=0 refused=0' printf '%s\n' "$output" >"$BATS_TEST_TMPDIR/out.txt" [ "$(grep -c . "$BATS_TEST_TMPDIR/out.txt")" -eq 1 ] grep -q 'plan' "$home/purge.log" }