aboutsummaryrefslogtreecommitdiff
path: root/scripts/tests/ai-launcher-characterization.bats
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-19 20:47:44 -0500
committerCraig Jennings <c@cjennings.net>2026-07-19 20:47:44 -0500
commit2b619f1769cae4a96202ca521a9ed4a9db65bc8f (patch)
tree069e35cd322e6b99a18b3b0ceea81edf09ba2959 /scripts/tests/ai-launcher-characterization.bats
parent113e8d868fdd95b269c0cc2ce4ec265aa76b539c (diff)
downloadrulesets-2b619f1769cae4a96202ca521a9ed4a9db65bc8f.tar.gz
rulesets-2b619f1769cae4a96202ca521a9ed4a9db65bc8f.zip
refactor(ai-launcher): extract pure decision cores from the git/tmux paths
I moved the decision logic out of the launcher's I/O-coupled functions so it's unit-testable on plain inputs, the hardening the audit called for. Three pure cores now carry logic that was buried in git and tmux calls: _git_prep_action classifies a repo's state into none/pull/report, _order_windows sorts the window listing, and _match_window_id resolves a name to a window id. _git_is_dirty replaces the staged/unstaged/untracked check that was copy-pasted across three functions. prep_git_single and auto_pull_if_clean both route their pull-or-report choice through _git_prep_action, so the "never auto-pull a dirty repo" rule lives in one place with its own test instead of two parallel branches. sort_windows keeps its two-pass move (park high, then reassign) but hands the ordering to _order_windows, and find_window_id is now a thin tmux wrapper over _match_window_id. Each core gets a Normal/Boundary/Error set: the git-prep truth table including diverged and behind-while-dirty, the window ordering including whole-line-not-substring matching. The 42 launcher tests stay green and a live black-box run still prints the right launch command for claude and codex, so the extraction is behavior-neutral. I also ran shfmt (-i 2 -ci) to match the house style the other bin/ scripts follow.
Diffstat (limited to 'scripts/tests/ai-launcher-characterization.bats')
-rw-r--r--scripts/tests/ai-launcher-characterization.bats79
1 files changed, 79 insertions, 0 deletions
diff --git a/scripts/tests/ai-launcher-characterization.bats b/scripts/tests/ai-launcher-characterization.bats
index 773181d..f53855f 100644
--- a/scripts/tests/ai-launcher-characterization.bats
+++ b/scripts/tests/ai-launcher-characterization.bats
@@ -243,3 +243,82 @@ _mk_repo_upstream() {
[ "$status" -eq 1 ]
[[ "$output" == *"no 'ai' session"* ]]
}
+
+# --- extracted pure cores -----------------------------------------------------
+
+@test "_git_prep_action: no upstream is always 'none'" {
+ run _git_prep_action 0 0 0 5
+ [ "$output" = none ]
+}
+
+@test "_git_prep_action: clean and purely behind is 'pull'" {
+ run _git_prep_action 1 0 0 3
+ [ "$output" = pull ]
+}
+
+@test "_git_prep_action: in sync with upstream is 'none'" {
+ run _git_prep_action 1 0 0 0
+ [ "$output" = none ]
+}
+
+@test "_git_prep_action: ahead is 'report', never 'pull'" {
+ run _git_prep_action 1 0 2 0
+ [ "$output" = report ]
+}
+
+@test "_git_prep_action: dirty is 'report', never 'pull'" {
+ run _git_prep_action 1 1 0 0
+ [ "$output" = report ]
+}
+
+@test "_git_prep_action: behind AND dirty is 'report' — a dirty repo is never auto-pulled" {
+ run _git_prep_action 1 1 0 3
+ [ "$output" = report ]
+}
+
+@test "_git_prep_action: diverged (ahead and behind) is 'report'" {
+ run _git_prep_action 1 0 2 3
+ [ "$output" = report ]
+}
+
+@test "_order_windows: others alpha, then projects alpha" {
+ local listing names out
+ listing="$(printf 'beta\t@1\nzzz-other\t@2\nalpha\t@3\naaa-other\t@4')"
+ names="$(printf 'alpha\nbeta')"
+ out="$(printf '%s\n' "$listing" | _order_windows "$names" | cut -f1 | paste -sd, -)"
+ [ "$out" = "aaa-other,zzz-other,alpha,beta" ]
+}
+
+@test "_order_windows: all-projects input keeps only the projects, sorted" {
+ local out
+ out="$(printf 'beta\t@1\nalpha\t@2\n' | _order_windows "$(printf 'alpha\nbeta')" | cut -f1 | paste -sd, -)"
+ [ "$out" = "alpha,beta" ]
+}
+
+@test "_order_windows: empty input yields empty output" {
+ run _order_windows "$(printf 'alpha\nbeta')" <<<""
+ [ -z "$output" ]
+}
+
+@test "_order_windows: a name matches only as a whole line, not as a substring" {
+ # 'alph' must not match project 'alpha' (grep -qxF is a full-line match).
+ local out
+ out="$(printf 'alph\t@1\n' | _order_windows "$(printf 'alpha')" | cut -f1)"
+ # 'alph' is not a project, so it lands in others, still present.
+ [ "$out" = "alph" ]
+}
+
+@test "_match_window_id: returns the id for a name, empty for a miss" {
+ local listing out
+ listing="$(printf 'one\t@1\ntwo\t@2\n')"
+ out="$(printf '%s\n' "$listing" | _match_window_id two)"
+ [ "$out" = "@2" ]
+ out="$(printf '%s\n' "$listing" | _match_window_id nope)"
+ [ -z "$out" ]
+}
+
+@test "_match_window_id: first match wins and stops" {
+ local out
+ out="$(printf 'dup\t@1\ndup\t@2\n' | _match_window_id dup)"
+ [ "$out" = "@1" ]
+}