Files
small-scripts/tests/test-claude-profile.sh
Paul O'Reilly 4cd9757abc feat(claude-profile): warn when non-Anthropic provider used with OAuth profile
Add a warning when ANTHROPIC_BASE_URL would be set but the active profile's
.credentials.json would override ANTHROPIC_API_KEY with subscription OAuth,
routing requests to Anthropic instead of the intended provider.

Fixes the MiniMax routing issue: use --profile minimax (~/.claude-minimax/,
no stored credentials) for non-Anthropic providers.

Update presets example to use profile: minimax for quick-minimax preset.
63 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:33:39 +12:00

238 lines
9.1 KiB
Bash
Executable File

#!/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_not_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 NOT to contain: %s\n" "$needle"
((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" "$@" </dev/null 2>&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"
assert_contains "--help shows --provider" "--provider" "$out"
# === Test 2: chat dryrun ===
echo
echo "Test 2: chat mode dryrun"
out=$(run --dryrun oreillyit --mode chat --provider anthropic-haiku)
rc=$?
assert_exit_code "chat dryrun exits 0" 0 "$rc"
assert_contains "chat shows haiku driver" "Driver: haiku" "$out"
assert_contains "chat shows provider" "Provider: anthropic-haiku" "$out"
assert_contains "chat shows model ID" "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" "<not asked in this mode>" "$out"
assert_contains "chat shows model in exec command" "claude --model claude-haiku-4-5-20251001" "$out"
# === Test 3: deep dryrun with project flag ===
echo
echo "Test 3: deep mode dryrun"
out=$(run --dryrun oreillyit --mode deep --project cluster-bootstrap --provider anthropic-sonnet)
rc=$?
assert_exit_code "deep dryrun exits 0" 0 "$rc"
assert_contains "deep shows sonnet driver" "Driver: sonnet" "$out"
assert_contains "deep shows provider" "Provider: anthropic-sonnet" "$out"
assert_contains "deep shows model ID" "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 --provider anthropic-sonnet)
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"
assert_contains "orch provider is anthropic-sonnet" "Provider: anthropic-sonnet" "$out"
# === Test 5: hybrid dryrun ===
echo
echo "Test 5: hybrid mode dryrun"
out=$(run --dryrun oreillyit --mode hybrid --provider anthropic-haiku)
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"
assert_contains "hybrid shows provider" "Provider: anthropic-haiku" "$out"
# === Test 6: quick dryrun (no time horizon question) ===
echo
echo "Test 6: quick mode dryrun"
out=$(run --dryrun oreillyit --mode quick --provider anthropic-sonnet)
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" "<not asked in this mode>" "$out"
assert_contains "quick shows provider" "Provider: anthropic-sonnet" "$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 --provider anthropic-haiku -- --resume some-session)
rc=$?
assert_exit_code "pass-through exits 0" 0 "$rc"
assert_contains "pass-through preserves --resume" "--resume some-session" "$out"
# === Test 11: minimax provider dryrun ===
echo
echo "Test 11: minimax provider dryrun"
out=$(run --dryrun oreillyit --mode quick --provider minimax-sonnet)
rc=$?
assert_exit_code "minimax dryrun exits 0" 0 "$rc"
assert_contains "minimax shows provider" "Provider: minimax-sonnet" "$out"
assert_contains "minimax shows model ID" "Model ID: MiniMax-M2.7" "$out"
assert_contains "minimax shows base URL" "Base URL: https://api.minimax.io/anthropic" "$out"
assert_contains "minimax shows API key env" "API key env: ANTHROPIC_API_KEY" "$out"
assert_contains "minimax references key file" "minimax-auth-token" "$out"
assert_contains "minimax shows model in exec command" "claude --model MiniMax-M2.7" "$out"
# === Test 12: error — unknown provider ===
echo
echo "Test 12: error path — unknown provider"
out=$(run --dryrun oreillyit --mode quick --provider bogus-provider)
rc=$?
assert_exit_code "unknown provider exits 1" 1 "$rc"
assert_contains "unknown provider error message" "Provider not found" "$out"
# === Test 13: default provider from driver (no --provider flag) ===
echo
echo "Test 13: default provider selected from driver"
out=$(run --dryrun oreillyit --mode chat)
rc=$?
assert_exit_code "default provider dryrun exits 0" 0 "$rc"
assert_contains "chat default provider is anthropic-haiku" "Provider: anthropic-haiku" "$out"
assert_contains "chat default model ID is haiku" "Model ID: claude-haiku-4-5-20251001" "$out"
# === Test 14: --preset + --provider mutual exclusion ===
echo
echo "Test 14: error path — --preset + --provider mutual exclusion"
out=$(run --dryrun --preset whatever --provider minimax-sonnet)
rc=$?
assert_exit_code "preset+provider exits 1" 1 "$rc"
assert_contains "preset+provider error message" "mutually exclusive" "$out"
# === Test 15: OAuth credentials warning fires for non-Anthropic provider ===
echo
echo "Test 15: OAuth credentials warning"
out=$(run --dryrun oreillyit --mode quick --provider minimax-sonnet)
rc=$?
assert_exit_code "credentials warning dryrun exits 0" 0 "$rc"
assert_contains "warning mentions stored credentials" ".credentials.json" "$out"
assert_contains "warning names the provider" "minimax-sonnet" "$out"
# === Test 16: No warning when profile has no OAuth credentials ===
echo
echo "Test 16: no warning for credentials-free profile"
out=$(run --dryrun minimax --mode quick --provider minimax-sonnet)
rc=$?
assert_exit_code "no-credentials dryrun exits 0" 0 "$rc"
assert_not_contains "no credentials warning" ".credentials.json" "$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