#!/usr/bin/env bash # test-claude-profile.sh — Verify claude-profile dryrun behaviour against the spec. # # Strategy: every assertion runs claude-profile --dryrun, so nothing is launched # and no real profile state is touched. For preset testing, a fake profile # directory is created under a tempdir-based HOME override. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" CLAUDE_PROFILE="$REPO_ROOT/scripts/claude-profile" GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' PASS=0 FAIL=0 assert_contains() { local label=$1 needle=$2 haystack=$3 if [[ "$haystack" == *"$needle"* ]]; then printf " ${GREEN}PASS${NC} %s\n" "$label" ((PASS++)) else printf " ${RED}FAIL${NC} %s\n" "$label" printf " expected to contain: %s\n" "$needle" printf " actual: %s\n" "$haystack" ((FAIL++)) fi } assert_exit_code() { local label=$1 expected=$2 actual=$3 if [[ "$expected" == "$actual" ]]; then printf " ${GREEN}PASS${NC} %s (exit=%s)\n" "$label" "$actual" ((PASS++)) else printf " ${RED}FAIL${NC} %s (expected exit=%s, got exit=%s)\n" "$label" "$expected" "$actual" ((FAIL++)) fi } run() { "$CLAUDE_PROFILE" "$@" &1 } # === Pre-flight === if [[ ! -x "$CLAUDE_PROFILE" ]]; then echo "claude-profile not found or not executable at $CLAUDE_PROFILE" >&2 exit 1 fi # === Test 1: --help === echo "Test 1: --help" out=$(run --help) rc=$? assert_exit_code "--help exits 0" 0 "$rc" assert_contains "--help shows usage" "claude-profile" "$out" assert_contains "--help shows --mode" "--mode" "$out" assert_contains "--help shows --preset" "--preset" "$out" # === Test 2: chat dryrun === echo echo "Test 2: chat mode dryrun" out=$(run --dryrun oreillyit --mode chat) rc=$? assert_exit_code "chat dryrun exits 0" 0 "$rc" assert_contains "chat shows haiku driver" "Driver: haiku" "$out" assert_contains "chat shows full haiku model ID" "claude-haiku-4-5-20251001" "$out" assert_contains "chat shows mode tag" "Mode: chat" "$out" assert_contains "chat says no changes" "No changes made" "$out" assert_contains "chat does not ask time horizon" "" "$out" # === Test 3: deep dryrun with project flag === echo echo "Test 3: deep mode dryrun" out=$(run --dryrun oreillyit --mode deep --project cluster-bootstrap) rc=$? assert_exit_code "deep dryrun exits 0" 0 "$rc" assert_contains "deep shows sonnet driver" "Driver: sonnet" "$out" assert_contains "deep shows full sonnet model ID" "claude-sonnet-4-6" "$out" assert_contains "deep shows --model in command" "claude --model claude-sonnet-4-6" "$out" assert_contains "deep records the project" "Project: cluster-bootstrap" "$out" assert_contains "deep defaults time horizon to hours" "Time horizon: hours" "$out" assert_contains "deep says async OK" "Async OK: yes" "$out" assert_contains "deep says plan_mode_auto yes" "Plan mode auto: yes" "$out" assert_contains "deep says spec_driven yes" "Spec driven: yes" "$out" # === Test 4: orch dryrun preserves autoloop === echo echo "Test 4: orch mode dryrun (autoloop preserved)" out=$(run --dryrun oreillyit --mode orch --project ai-image-gen) rc=$? assert_exit_code "orch dryrun exits 0" 0 "$rc" assert_contains "orch preserves the slash command in autoloop" "/loop 2m /orchestrate" "$out" assert_contains "orch driver is sonnet" "Driver: sonnet" "$out" # === Test 5: hybrid dryrun === echo echo "Test 5: hybrid mode dryrun" out=$(run --dryrun oreillyit --mode hybrid) rc=$? assert_exit_code "hybrid dryrun exits 0" 0 "$rc" assert_contains "hybrid uses haiku driver" "Driver: haiku" "$out" assert_contains "hybrid allows async" "Async OK: yes" "$out" # === Test 6: quick dryrun (no time horizon question) === echo echo "Test 6: quick mode dryrun" out=$(run --dryrun oreillyit --mode quick) rc=$? assert_exit_code "quick dryrun exits 0" 0 "$rc" assert_contains "quick uses sonnet driver" "Driver: sonnet" "$out" assert_contains "quick disables async" "Async OK: no" "$out" assert_contains "quick does not ask time horizon" "" "$out" # === Test 7: error — unknown mode === echo echo "Test 7: error path — unknown mode" out=$(run --dryrun oreillyit --mode bogus) rc=$? assert_exit_code "unknown mode exits 1" 1 "$rc" assert_contains "unknown mode error message" "Mode file not found" "$out" # === Test 8: error — --preset and --mode together === echo echo "Test 8: error path — --preset + --mode mutual exclusion" out=$(run --dryrun --preset whatever --mode quick) rc=$? assert_exit_code "preset+mode exits 1" 1 "$rc" assert_contains "preset+mode error message" "mutually exclusive" "$out" # === Test 9: error — preset not found === echo echo "Test 9: error path — preset not found" out=$(run --dryrun --preset definitely-not-a-real-preset-name) rc=$? assert_exit_code "missing preset exits 1" 1 "$rc" assert_contains "missing preset error message" "not found" "$out" # === Test 10: pass-through args via -- === echo echo "Test 10: pass-through args" out=$(run --dryrun oreillyit --mode chat -- --resume some-session) rc=$? assert_exit_code "pass-through exits 0" 0 "$rc" assert_contains "pass-through preserves --resume" "--resume some-session" "$out" # === Summary === echo echo "================================" TOTAL=$((PASS + FAIL)) if (( FAIL == 0 )); then printf "${GREEN}All %d tests passed${NC}\n" "$TOTAL" exit 0 else printf "${RED}%d of %d tests failed${NC}\n" "$FAIL" "$TOTAL" exit 1 fi