#!/bin/bash set -euo pipefail source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh" require_command jq require_command python3 export OMARCHY_PATH="$ROOT" export PATH="$ROOT/bin:$PATH" TEST_HOME=$(mktemp -d) MOCK_PID="" cleanup() { [[ -n $MOCK_PID ]] && kill "$MOCK_PID" 2>/dev/null rm -rf "$TEST_HOME" } trap cleanup EXIT export HOME="$TEST_HOME" # Force the file backend: production Secret Service / pass must never be touched mkdir -p "$TEST_HOME/stubs" printf '#!/bin/bash exit 1 ' > "$TEST_HOME/stubs/secret-tool" printf '#!/bin/bash exit 1 ' > "$TEST_HOME/stubs/pass" chmod +x "$TEST_HOME/stubs/secret-tool" "$TEST_HOME/stubs/pass" export PATH="$TEST_HOME/stubs:$PATH" # Registries parse and carry the P0 providers and harnesses for reg in ai-providers ai-harnesses ai-compatibility; do jq -e . "$ROOT/cn/registry/$reg.json" > /dev/null || fail "$reg.json parses" done for p in deepseek kimi zai minimax; do jq -e --arg p "$p" '.providers[$p].endpoints.anthropic // .providers[$p].endpoints.openai' \ "$ROOT/cn/registry/ai-providers.json" > /dev/null || fail "provider $p has an endpoint" done jq -e '.providers.ollama | (.static_token == "ollama") and (.models_dynamic == true) and (.endpoints.anthropic == "http://localhost:11434")' \ "$ROOT/cn/registry/ai-providers.json" > /dev/null || fail "ollama provider contract" jq -e '.combos["claude-code"].ollama.adapter' \ "$ROOT/cn/registry/ai-compatibility.json" > /dev/null || fail "claude-code x ollama combo present" pass "AI registries parse with P0 providers" # Secret file backend roundtrip with 0600 echo "sk-secret-1" | omarchy-cn-ai-secret set deepseek > /dev/null [[ $(omarchy-cn-ai-secret get deepseek) == "sk-secret-1" ]] || fail "secret roundtrip" [[ $(stat -c %a "$HOME/.local/state/omarchycn/secrets/deepseek") == 600 ]] || fail "secret file 0600" pass "secret backend file fallback stores and returns the key at 0600" # Profile validation matrix omarchy-cn-ai-profile-create good claude-code deepseek default-coding > /dev/null omarchy-cn-ai-profile-create cdx codex deepseek deepseek-v4-flash > /dev/null omarchy-cn-ai-profile-create ock opencode deepseek default-coding > /dev/null if omarchy-cn-ai-profile-create bad codex kimi fast > /dev/null 2>&1; then fail "unsupported combo rejected" fi if omarchy-cn-ai-profile-create bad claude-code kimi kimi-k2.6 > /dev/null 2>&1; then fail "protocol-mismatched model rejected" fi if omarchy-cn-ai-profile-create bad codex deepseek "deepseek-v4-pro[1m]" > /dev/null 2>&1; then fail "codex rejects provider-namespace slug" fi omarchy-cn-ai-profile-create loc claude-code ollama qwen3-coder > /dev/null || fail "dynamic-model provider accepts a runtime-listed model" pass "profile validation enforces combos, protocols, and allowlists" # Static-token provider needs no stored secret; absent fields stay silent source "$ROOT/cn/lib/ai.sh" [[ $(cn_ai_resolve_key ollama) == "ollama" ]] || fail "ollama resolves its static token" static_out=$(cn_ai_static_token deepseek || true) [[ -z $static_out ]] || fail "absent static_token must print nothing" "$static_out" pass "static token resolution: fixed ollama key, silent absent field" # claude-code env render for all three providers, exact endpoints source "$ROOT/cn/lib/ai.sh" declare -A bases=( [deepseek]="https://api.deepseek.com/anthropic" [kimi]="https://api.kimi.com/coding/" [zai]="https://api.z.ai/api/anthropic" [minimax]="https://api.minimaxi.com/anthropic" ) for p in deepseek kimi zai minimax; do out=$(cn_ai_render_env claude-code "$p" test-model sk-x) grep -qF "ANTHROPIC_BASE_URL=${bases[$p]}" <<<"$out" || fail "claude-code $p base url" "$out" grep -q "ANTHROPIC_AUTH_TOKEN=sk-x" <<<"$out" || fail "claude-code $p auth token" grep -q "ANTHROPIC_API_KEY=sk-x" <<<"$out" || fail "claude-code $p api key" done out=$(cn_ai_render_env claude-code minimax "MiniMax-M3[1m]" sk-x) grep -qF "ANTHROPIC_DEFAULT_HAIKU_MODEL=MiniMax-M3" <<<"$out" || fail "minimax haiku slot falls back to default-coding" out=$(cn_ai_render_env claude-code ollama qwen3-coder "$(cn_ai_resolve_key ollama)") grep -qF "ANTHROPIC_BASE_URL=http://localhost:11434" <<<"$out" || fail "ollama base url" "$out" grep -q "ANTHROPIC_AUTH_TOKEN=ollama" <<<"$out" || fail "ollama static token rendered" grep -qF "ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder" <<<"$out" || fail "ollama haiku slot falls back to the chosen model" out=$(cn_ai_render_env opencode deepseek test-model sk-x) grep -q "DEEPSEEK_API_KEY=sk-x" <<<"$out" || fail "opencode deepseek env" if cn_ai_render_env opencode zai m k > /dev/null 2>&1; then fail "opencode zai refused" fi pass "env adapters render official endpoints for every stable combo" # codex config render: official contract, user content preserved, idempotent mkdir -p "$HOME/.codex" printf '[mcp_servers.x]\ncommand = "npx"\n' > "$HOME/.codex/config.toml" cn_ai_render_codex_config deepseek deepseek-v4-flash sk-x for want in 'model = "deepseek-v4-flash"' 'model_catalog_json = "~/.codex/models.json"' \ 'preferred_auth_method = "apikey"' 'forced_login_method = "api"' \ 'wire_api = "responses"' '[model_providers.deepseek]'; do grep -qF "$want" "$HOME/.codex/config.toml" || fail "codex contract: $want" done grep -q "mcp_servers.x" "$HOME/.codex/config.toml" || fail "codex user section preserved" [[ -f $HOME/.codex/models.json ]] || fail "codex models catalog installed" cn_ai_render_codex_config deepseek deepseek-v4-flash sk-x [[ $(grep -c "OmarchyCN ai model begin" "$HOME/.codex/config.toml") == 1 ]] || fail "codex render idempotent" pass "codex adapter implements the official DeepSeek contract" # Mock server regression: anthropic 200/401 and codex responses path port=$(python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()') python3 - "$port" <<'PYEOF' & import http.server, sys class H(http.server.BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers.get("Content-Length", 0))) key = self.headers.get("x-api-key") or (self.headers.get("Authorization") or "").replace("Bearer ", "") if key not in ("sk-secret-1", "ollama"): code = 401 elif self.path == "/v1/messages" and b"messages" in body and self.headers.get("anthropic-version"): code = 200 elif self.path == "/responses" and b"input" in body and b"messages" not in body: code = 200 else: code = 404 self.send_response(code) self.send_header("content-length", "2") self.end_headers() self.wfile.write(b"{}") def log_message(self, *a): pass http.server.HTTPServer(("127.0.0.1", int(sys.argv[1])), H).serve_forever() PYEOF MOCK_PID=$! for _ in $(seq 1 20); do curl -sf -o /dev/null -X POST "http://127.0.0.1:$port/v1/messages" -H "x-api-key: sk-secret-1" && break sleep 0.2 done OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good | grep -q "PASS" || fail "anthropic mock 200" OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test cdx | grep -q "PASS" || fail "codex responses mock 200" OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test loc | grep -q "PASS" || fail "ollama static-token mock 200" echo "wrong-key" | omarchy-cn-ai-secret set deepseek > /dev/null if OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good > /dev/null 2>&1; then fail "anthropic mock 401 fails the test" fi pass "mock regression: 200 pass, 401 fail, responses path exercised" # ai-default merges into claude settings without clobbering user config. # claude is absent; the install branch must run our omarchy-mise-install stub, # which "installs" the claude stub and leaves a marker. cat > "$TEST_HOME/stubs/omarchy-mise-install" < "$TEST_HOME/stubs/claude" chmod +x "$TEST_HOME/stubs/claude" STUB chmod +x "$TEST_HOME/stubs/omarchy-mise-install" echo "sk-secret-1" | omarchy-cn-ai-secret set deepseek > /dev/null mkdir -p "$HOME/.claude" printf '{"permissions":{"allow":["Bash"]},"env":{"KEEP":"1"}}' > "$HOME/.claude/settings.json" # Restricted PATH guarantees claude is absent so the installer branch runs restricted="$TEST_HOME/stubs:$ROOT/bin:/usr/bin:/bin" if PATH="$restricted" command -v claude > /dev/null 2>&1; then fail "precondition: claude must not be visible on the restricted PATH" fi PATH="$restricted" omarchy-cn-ai-default good > /dev/null [[ $(jq -r '.env.KEEP' "$HOME/.claude/settings.json") == 1 ]] || fail "ai-default preserves user env" [[ $(jq -r '.permissions.allow[0]' "$HOME/.claude/settings.json") == Bash ]] || fail "ai-default preserves permissions" [[ $(jq -r '.env.ANTHROPIC_BASE_URL' "$HOME/.claude/settings.json") == "https://api.deepseek.com/anthropic" ]] || fail "ai-default env written" [[ $(cat "$HOME/.config/omarchy/defaults/agent") == claude ]] || fail "upstream default agent set" [[ $(stat -c %a "$HOME/.claude/settings.json") == 600 ]] || fail "claude settings 0600" [[ -f $TEST_HOME/installer-ran ]] || fail "missing harness triggered installer" pass "ai-default maps the profile onto the upstream agent entry" # ai-default with the static-token local provider writes the official contract PATH="$restricted" omarchy-cn-ai-default loc > /dev/null [[ $(jq -r '.env.ANTHROPIC_BASE_URL' "$HOME/.claude/settings.json") == "http://localhost:11434" ]] || fail "ollama ai-default base url" [[ $(jq -r '.env.ANTHROPIC_AUTH_TOKEN' "$HOME/.claude/settings.json") == "ollama" ]] || fail "ollama ai-default static token" [[ $(jq -r '.env.ANTHROPIC_DEFAULT_HAIKU_MODEL' "$HOME/.claude/settings.json") == "qwen3-coder" ]] || fail "ollama ai-default haiku fallback" [[ $(jq -r '.env.KEEP' "$HOME/.claude/settings.json") == 1 ]] || fail "ollama ai-default preserves user env" pass "ai-default renders the ollama static-token contract"