aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/tests/task-review-staleness.bats
blob: abb7585960d267e4f4d834355c20c4960eabbf04 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env bats
#
# Tests for claude-templates/.ai/scripts/task-review-staleness.sh —
# counts top-level todo.org tasks whose review has gone stale.
#
# Strategy: write a synthetic todo.org into a temp dir per test, with
# LAST_REVIEWED dates generated relative to the real `date` (never
# hardcoded). Run the real script against it and assert the count it
# prints on stdout.
#
# Staleness rule under test:
#   - A qualifying task is a depth-2 (**) heading with a TODO/DOING/VERIFY
#     keyword and an [#A]/[#B]/[#C] priority cookie.
#   - It is stale when LAST_REVIEWED is missing/malformed (NIL → oldest),
#     or when its age strictly exceeds the threshold (age > N days).
#   - age == N exactly is fresh (the spec's wording is ">N days").

# The script under test is always the sibling-of-parent of this test file
# (scripts/task-review-staleness.sh next to scripts/tests/). This holds in
# both the canonical claude-templates/ tree and the rsync'd project mirror,
# so the suite runs identically from either location.
SCRIPT="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/task-review-staleness.sh"

setup() {
  TEST_DIR="$(mktemp -d -t task-review-bats.XXXXXX)"
  TODO="$TEST_DIR/todo.org"

  TODAY="$(date +%F)"
  D5="$(date -d '5 days ago' +%F)"
  D30="$(date -d '30 days ago' +%F)"
  D31="$(date -d '31 days ago' +%F)"
  D40="$(date -d '40 days ago' +%F)"
}

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

# Emit a qualifying task with an explicit LAST_REVIEWED date.
task_reviewed() {
  local keyword="$1" prio="$2" title="$3" date="$4"
  printf '** %s [#%s] %s\n:PROPERTIES:\n:LAST_REVIEWED: %s\n:END:\nBody.\n\n' \
    "$keyword" "$prio" "$title" "$date" >> "$TODO"
}

# Emit a qualifying task with no PROPERTIES drawer at all.
task_unreviewed() {
  local keyword="$1" prio="$2" title="$3"
  printf '** %s [#%s] %s\nBody.\n\n' "$keyword" "$prio" "$title" >> "$TODO"
}

# ---- Normal cases ----------------------------------------------------

@test "staleness: empty file reports zero" {
  : > "$TODO"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "0" ]
}

@test "staleness: all tasks fresh reports zero" {
  task_reviewed TODO A "Fresh one" "$D5"
  task_reviewed TODO B "Fresh two" "$D5"
  task_reviewed DOING A "Fresh three" "$TODAY"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "0" ]
}

@test "staleness: all tasks stale reports full count" {
  task_reviewed TODO A "Stale one" "$D40"
  task_reviewed TODO B "Stale two" "$D40"
  task_reviewed VERIFY C "Stale three" "$D40"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "3" ]
}

@test "staleness: mixed fresh, stale, and unreviewed counts only the latter two" {
  task_reviewed TODO A "Fresh" "$D5"
  task_reviewed TODO B "Stale" "$D40"
  task_unreviewed DOING A "Never reviewed"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "2" ]
}

# ---- Boundary cases --------------------------------------------------

@test "staleness: age exactly equal to threshold is fresh" {
  task_reviewed TODO A "Exactly at cutoff" "$D30"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "0" ]
}

@test "staleness: age one day past threshold is stale" {
  task_reviewed TODO A "One day over" "$D31"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "1" ]
}

@test "staleness: unreviewed task (no drawer) counts as stale" {
  task_unreviewed TODO A "Never reviewed"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "1" ]
}

@test "staleness: threshold of 7 is softer than 30 on the same list" {
  task_reviewed TODO A "Reviewed five days ago" "$D5"
  task_reviewed TODO B "Reviewed thirty-one days ago" "$D31"
  run bash "$SCRIPT" "$TODO" 7
  [ "$status" -eq 0 ]
  [ "$output" = "1" ]
}

# ---- Error / exclusion cases -----------------------------------------

@test "staleness: DONE and CANCELLED tasks are excluded even when old" {
  task_reviewed DONE A "Shipped long ago" "$D40"
  task_reviewed CANCELLED B "Abandoned long ago" "$D40"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "0" ]
}

@test "staleness: deeper headings and cookie-less headings are excluded" {
  # Depth-3 child with an old review date — not a review unit.
  printf '*** TODO [#A] Child task\n:PROPERTIES:\n:LAST_REVIEWED: %s\n:END:\n\n' "$D40" >> "$TODO"
  # Depth-2 but no priority cookie — not a review unit.
  printf '** TODO Cookie-less task\nBody.\n\n' >> "$TODO"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "0" ]
}

@test "staleness: malformed LAST_REVIEWED is treated as stale" {
  task_reviewed TODO A "Bad date" "not-a-date"
  run bash "$SCRIPT" "$TODO" 30
  [ "$status" -eq 0 ]
  [ "$output" = "1" ]
}

@test "staleness: missing todo file exits non-zero" {
  run bash "$SCRIPT" "$TEST_DIR/does-not-exist.org" 30
  [ "$status" -ne 0 ]
}