aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/tests/capture-guard.bats
blob: 31632a471a4b2d905fe13459aba21e991dc7a31e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env bats
#
# Tests for claude-templates/.ai/scripts/capture-guard — detects live
# org-capture buffers visiting a target file before a workflow edits that
# file on disk (the roam inbox, in inbox.org roam mode Phase D). Editing the file
# underneath an indirect org-capture buffer wedges the capture (see emacs.md).
#
# Contract under test:
#   capture-guard [TARGET_FILE]   (default TARGET_FILE = ~/org/roam/inbox.org)
#     exit 0  → safe to edit: emacsclient absent, daemon unreachable, or no
#               capture buffer visits TARGET_FILE.
#     exit 1  → a live capture buffer visits TARGET_FILE; its name(s) printed.
#
# Strategy: the emacsclient boundary is mocked with a PATH stub. The stub
# answers the reachability probe (`-e t`) per STUB_REACHABLE and returns a
# canned, real-emacsclient-shaped result (quoted string) for the buffer query
# per STUB_BUFS. The script's own quote-stripping and exit logic is the code
# under test; the file-equal-p precision is real-Emacs behavior we trust.

SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/capture-guard"
BASH_BIN="$(command -v bash)"

setup() {
  TEST_DIR="$(mktemp -d -t capture-guard-bats.XXXXXX)"
  STUB_DIR="$TEST_DIR/bin"
  mkdir -p "$STUB_DIR"

  cat > "$STUB_DIR/emacsclient" <<'STUB'
#!/usr/bin/env bash
# Mock emacsclient. `-e t` is the reachability probe; anything else is the
# buffer query, answered with the real-emacsclient-shaped quoted string.
expr="$2"
if [ "$expr" = "t" ]; then
  [ "${STUB_REACHABLE:-1}" = "1" ] && { echo t; exit 0; }
  exit 1
fi
printf '%s\n' "${STUB_BUFS:-\"\"}"
exit 0
STUB
  chmod +x "$STUB_DIR/emacsclient"

  EMPTY_DIR="$TEST_DIR/empty"
  mkdir -p "$EMPTY_DIR"
}

teardown() {
  rm -rf "$TEST_DIR"
}

# ---- Safe-to-edit (exit 0) cases ------------------------------------

@test "capture-guard: emacsclient absent is safe (exit 0, no output)" {
  run env PATH="$EMPTY_DIR" "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}

@test "capture-guard: daemon unreachable is safe (exit 0)" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=0 "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}

@test "capture-guard: reachable with no capture buffers is safe (exit 0)" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='""' "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}

# ---- Blocked (exit 1) cases -----------------------------------------

@test "capture-guard: one live capture buffer blocks (exit 1, name printed)" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='"CAPTURE-inbox.org"' \
    "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 1 ]
  [[ "$output" == *"CAPTURE-inbox.org"* ]]
}

@test "capture-guard: multiple live capture buffers all reported (exit 1)" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 \
    STUB_BUFS='"CAPTURE-inbox.org,CAPTURE-2-inbox.org"' \
    "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 1 ]
  [[ "$output" == *"CAPTURE-inbox.org"* ]]
  [[ "$output" == *"CAPTURE-2-inbox.org"* ]]
}

@test "capture-guard: blocked output does not contain stray surrounding quotes" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='"CAPTURE-inbox.org"' \
    "$BASH_BIN" "$SCRIPT"
  [ "$status" -eq 1 ]
  [[ "$output" != \"* ]]
  [[ "$output" != *\" ]]
}

# ---- Argument handling ----------------------------------------------

@test "capture-guard: accepts an explicit target-file argument" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='""' \
    "$BASH_BIN" "$SCRIPT" "$TEST_DIR/some-other-inbox.org"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}

# ---- --wait poll mode -----------------------------------------------

@test "capture-guard --wait: returns 0 instantly when already safe (no sleep)" {
  SECONDS=0
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='""' \
    "$BASH_BIN" "$SCRIPT" --wait
  [ "$status" -eq 0 ]
  [ -z "$output" ]
  [ "$SECONDS" -lt 2 ]   # didn't poll-sleep
}

@test "capture-guard --wait=1: times out to exit 1 when persistently blocked" {
  # Stub always reports the buffer, so it never clears — the short budget
  # forces a timeout. Capped sleep keeps this near 1s.
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='"CAPTURE-inbox.org"' \
    "$BASH_BIN" "$SCRIPT" --wait=1
  [ "$status" -eq 1 ]
  [[ "$output" == *"CAPTURE-inbox.org"* ]]
}

@test "capture-guard --wait=N accepts a target after the flag" {
  run env PATH="$STUB_DIR:$PATH" STUB_REACHABLE=1 STUB_BUFS='""' \
    "$BASH_BIN" "$SCRIPT" --wait=1 "$TEST_DIR/some-other-inbox.org"
  [ "$status" -eq 0 ]
  [ -z "$output" ]
}