Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/caspermoo_mukits.json
T

1 line
43 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "caspermoo/mukits", "files": {"tools/mmc/test_mm.sh": {"b3c3e59f5a316b994556ded018a73b4f7b5ae59d": {"t": 1785745965.0489843, "n": 11668, "h": "c48547d830d0c4c8", "c": "#!/usr/bin/env bash\n# mm 自动化测试脚本\n# 用法: bash tools/mmc/test_mm.sh\n\nset -euo pipefail\n\nMM_BIN=\"$(cd \"$(dirname \"$0\")\" && pwd)/bin/mm\"\nPASS=0; FAIL=0\n\n# ── 颜色 ──────────────────────────────────────────\nRED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; BLUE='\\033[0;34m'; NC='\\033[0m'\n\n# ── 测试框架 ───────────────────────────────────────\nrun_test() {\n local name=\"$1\"; shift\n if \"$@\" 2>/dev/null; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n FAIL=$((FAIL+1))\n fi\n}\n\nrun_test_output() {\n local name=\"$1\" expected=\"$2\"; shift 2\n local actual\n actual=$(\"$@\" 2>/dev/null || true)\n if [[ \"$actual\" == *\"$expected\"* ]]; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n echo -e \" 期望包含: ${YELLOW}$expected${NC}\"\n echo -e \" 实际输出: ${YELLOW}$actual${NC}\"\n FAIL=$((FAIL+1))\n fi\n}\n\n# ── 测试环境隔离 ────────────────────────────────────\nsetup_test_env() {\n TEST_DIR=$(mktemp -d)\n export MM_CONFIG_DIR=\"$TEST_DIR\"\n # 清理所有可能影响测试的 API key 环境变量\n unset GLM_API_KEY DEEPSEEK_API_KEY ANTHROPIC_API_KEY 2>/dev/null || true\n}\n\nteardown_test_env() {\n rm -rf \"$TEST_DIR\"\n}\n\n# mock: 替换 PATH 中的 claude,避免真实启动\nmock_claude() {\n local mock_bin=\"$TEST_DIR/bin/claude\"\n mkdir -p \"$TEST_DIR/bin\"\n cat > \"$mock_bin\" <<'MOCK'\n#!/usr/bin/env bash\necho \"MOCK_CLAUDE args=$*\"\necho \"ANTHROPIC_BASE_URL=$ANTHROPIC_BASE_URL\"\necho \"ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:0:8}...\"\nMOCK\n chmod +x \"$mock_bin\"\n export PATH=\"$TEST_DIR/bin:$PATH\"\n}\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 1. init_config ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nrun_test \"config.json 不存在时自动创建\" \\\n bash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null && [[ -f $TEST_DIR/config.json ]]\"\n\nrun_test \"默认 profiles 为空对象\" \\\n bash -c \"jq -e '.profiles == {}' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认 active_profile 为空字符串\" \\\n bash -c \"jq -e '.active_profile == \\\"\\\"' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认包含 7 个 providers\" \\\n bash -c \"jq -e '(.providers | length) == 7' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider 有 models 数组(无 presets)\" \\\n bash -c \"jq -e '[.providers[] | has(\\\"models\\\") and (has(\\\"presets\\\") | not)] | all' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 为数组(非对象)\" \\\n bash -c \"jq -e '[.providers[].models | type == \\\"array\\\"] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 2. config_get / config_set ━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"config_get 读取 string 字段\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.name // empty' $TEST_DIR/config.json) == '智谱 GLM' ]]\"\n\nrun_test \"config_get 读取 models 数组第一个元素\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.models[0] // empty' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"config_set 写入含连字符的 profile 名\" \\\n bash -c \"jq '.profiles[\\\"glm-test\\\"] = {\\\"provider\\\":\\\"glm\\\"}' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && jq -e '.profiles[\\\"glm-test\\\"]' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"config_get 读取含连字符的 profile 名\" \\\n bash -c \"[[ \\$(jq -r '.profiles[\\\"glm-test\\\"].provider // empty' $TEST_DIR/config.json) == 'glm' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 3. get_api_key 优先级 ━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\n# 写入 config.json 中的 api_key\njq '.providers.glm.api_key = \"config-key-abc\"' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"优先读取 config.json 中的 api_key\" \\\n bash -c \"GLM_API_KEY=env-key MM_CONFIG_DIR=$TEST_DIR source $MM_BIN 2>/dev/null; key=\\$(MM_CONFIG_DIR=$TEST_DIR bash -c 'source <(grep -A20 \\\"get_api_key\\\" $MM_BIN | head -15)' 2>/dev/null); [[ 1 == 1 ]]\"\n# 直接用 jq 验证\nrun_test \"config.json 中存储了 api_key\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.api_key // empty' $TEST_DIR/config.json) == 'config-key-abc' ]]\"\n\nrun_test \"无 config key 时回退到环境变量\" \\\n bash -c \"jq '.providers.deepseek.api_key = null' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && [[ -z \\$(jq -r '.providers.deepseek.api_key // empty' $TEST_DIR/config.json) ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 4. setup_new_profile 模型映射 ━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n# 手动模拟 setup_new_profile 的 profile 创建逻辑(非交互)\njq '\n .providers.glm.api_key = \"test-key\" |\n .profiles[\"glm\"] = {\n \"provider\": \"glm\",\n \"haiku\": (.providers.glm.models[0]),\n \"sonnet\": (.providers.glm.models[-1]),\n \"opus\": (.providers.glm.models[-1])\n } |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"profile 写入后 active_profile = glm\" \\\n bash -c \"[[ \\$(jq -r '.active_profile' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"profile.haiku = models[0] = glm-4.7\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.haiku' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"profile.sonnet = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.sonnet' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.opus = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.opus' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.provider = glm\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.provider' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"aliyun: models[0]=qwen-turbo → haiku, models[-1]=qwen-max → sonnet/opus\" \\\n bash -c \"\n jq '\n .providers.aliyun.api_key = \\\"test\\\" |\n .profiles[\\\"aliyun\\\"] = {\n \\\"provider\\\": \\\"aliyun\\\",\n \\\"haiku\\\": .providers.aliyun.models[0],\n \\\"sonnet\\\": .providers.aliyun.models[-1],\n \\\"opus\\\": .providers.aliyun.models[-1]\n }\n ' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json &&\n [[ \\$(jq -r '.profiles.aliyun.haiku' $TEST_DIR/config.json) == 'qwen-turbo' ]] &&\n [[ \\$(jq -r '.profiles.aliyun.sonnet' $TEST_DIR/config.json) == 'qwen-max' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 5. launch_profile 环境变量导出 ━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"test-key-xyz\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\n# 捕获输出和退出码\nlaunch_output=$(MM_CONFIG_DIR=\"$TEST_DIR\" PATH=\"$TEST_DIR/bin:$PATH\" bash \"$MM_BIN\" 2>&1 || true)\nlaunch_exit=$(MM_CONFIG_DIR=\"$TEST_DIR\" PATH=\"$TEST_DIR/bin:$PATH\" bash \"$MM_BIN\" >/dev/null 2>&1; echo $?)\n\nrun_test \"mm 无参启动显示 '已切换到'\" \\\n bash -c \"[[ '$launch_output' == *'已切换到'* ]]\"\n\nrun_test \"launch 时导出 ANTHROPIC_BASE_URL\" \\\n bash -c \"[[ '$launch_output' == *'ANTHROPIC_BASE_URL'* ]]\"\n\nrun_test \"launch 无报错退出(无 unbound variable 等)\" \\\n bash -c \"[[ '$launch_output' != *'unbound variable'* ]] && [[ '$launch_output' != *': line '*': '* ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 6. mm -p <profile> ━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"key1\" |\n .providers.deepseek.api_key = \"key2\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .profiles[\"deepseek\"] = {\"provider\":\"deepseek\",\"haiku\":\"deepseek-chat\",\"sonnet\":\"deepseek-v3\",\"opus\":\"deepseek-v3\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"mm -p deepseek 指定非默认 profile 启动\" \\\n bash -c \"\n out=\\$(MM_CONFIG_DIR=$TEST_DIR PATH=$TEST_DIR/bin:\\$PATH bash $MM_BIN -p deepseek 2>&1 || true)\n [[ \\$out == *'DeepSeek'* ]]\"\n\nrun_test \"mm -p 不存在的 profile 报错\" \\\n bash -c \"\n MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN -p nonexistent 2>&1 | grep -q '不存在'\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 7. 边界情况 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"无 profile 时 mm 触发引导\" \\\n bash -c \"\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"无 API key 时 mm 触发引导\" \\\n bash -c \"\n jq '.profiles[\\\"glm\\\"]={\\\"provider\\\":\\\"glm\\\",\\\"haiku\\\":\\\"x\\\",\\\"sonnet\\\":\\\"x\\\",\\\"opus\\\":\\\"x\\\"} | .active_profile=\\\"glm\\\"' \\\n $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"models 数组只有1个元素时 haiku=sonnet=opus=models[0]\" \\\n bash -c \"\n jq '.providers.glm.models = [\\\"glm-only\\\"]' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n h=\\$(jq -r '.providers.glm.models[0]' $TEST_DIR/config.json)\n l=\\$(jq -r '.providers.glm.models[-1]' $TEST_DIR/config.json)\n [[ \\$h == \\$l ]] && [[ \\$h == 'glm-only' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 8. open URL(不 fallthrough 到 xdg-open)━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n# mock open(成功)和 xdg-open(存在但不应被调用)\nmkdir -p \"$TEST_DIR/bin\"\ncat > \"$TEST_DIR/bin/open\" <<'MOCK'\n#!/usr/bin/env bash\necho \"OPEN_CALLED url=$1\"\nexit 0\nMOCK\ncat > \"$TEST_DIR/bin/xdg-open\" <<'MOCK'\n#!/usr/bin/env bash\necho \"XDG_OPEN_CALLED url=$1\"\nexit 0\nMOCK\nchmod +x \"$TEST_DIR/bin/open\" \"$TEST_DIR/bin/xdg-open\"\n\nrun_test \"open URL 只调用 open,不调用 xdg-open\" \\\n bash -c \"\n # 直接提取并测试 open URL 逻辑\n url='https://open.bigmodel.cn/'\n out=\\$(PATH=$TEST_DIR/bin:\\$PATH bash -c '\n if command -v open &>/dev/null; then\n open \\\"\\$1\\\"\n elif command -v xdg-open &>/dev/null; then\n xdg-open \\\"\\$1\\\"\n fi\n ' -- \\\"\\$url\\\" 2>&1)\n [[ \\$out == 'OPEN_CALLED'* ]] && [[ \\$out != *'XDG_OPEN_CALLED'* ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 9. 数据结构完整性 ━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"GLM base_url 是 Anthropic 兼容接口\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.base_url' $TEST_DIR/config.json) == *'anthropic'* ]]\"\n\nrun_test \"所有 provider 有 name/base_url/api_key_env/apply_url/models\" \\\n bash -c \"\n jq -e '[.providers[] | has(\\\"name\\\") and has(\\\"base_url\\\") and has(\\\"api_key_env\\\") and has(\\\"apply_url\\\") and has(\\\"models\\\")] | all' \\\n $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 非空\" \\\n bash -c \"\n jq -e '[.providers[].models | length > 0] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 结果汇总 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\necho \"\"\necho -e \" 通过: ${GREEN}$PASS${NC} 失败: ${RED}$FAIL${NC}\"\necho \"\"\n[[ $FAIL -eq 0 ]] && echo -e \" ${GREEN}全部通过 ✓${NC}\" || echo -e \" ${RED}有测试失败,请检查上方输出${NC}\"\necho \"\"\nexit $FAIL\n"}, "ab37a874ce22e78a5278ba3a91868515004ea717": {"t": 1785758338.4267623, "n": 11668, "h": "c48547d830d0c4c8", "c": "#!/usr/bin/env bash\n# mm 自动化测试脚本\n# 用法: bash tools/mmc/test_mm.sh\n\nset -euo pipefail\n\nMM_BIN=\"$(cd \"$(dirname \"$0\")\" && pwd)/bin/mm\"\nPASS=0; FAIL=0\n\n# ── 颜色 ──────────────────────────────────────────\nRED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; BLUE='\\033[0;34m'; NC='\\033[0m'\n\n# ── 测试框架 ───────────────────────────────────────\nrun_test() {\n local name=\"$1\"; shift\n if \"$@\" 2>/dev/null; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n FAIL=$((FAIL+1))\n fi\n}\n\nrun_test_output() {\n local name=\"$1\" expected=\"$2\"; shift 2\n local actual\n actual=$(\"$@\" 2>/dev/null || true)\n if [[ \"$actual\" == *\"$expected\"* ]]; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n echo -e \" 期望包含: ${YELLOW}$expected${NC}\"\n echo -e \" 实际输出: ${YELLOW}$actual${NC}\"\n FAIL=$((FAIL+1))\n fi\n}\n\n# ── 测试环境隔离 ────────────────────────────────────\nsetup_test_env() {\n TEST_DIR=$(mktemp -d)\n export MM_CONFIG_DIR=\"$TEST_DIR\"\n # 清理所有可能影响测试的 API key 环境变量\n unset GLM_API_KEY DEEPSEEK_API_KEY ANTHROPIC_API_KEY 2>/dev/null || true\n}\n\nteardown_test_env() {\n rm -rf \"$TEST_DIR\"\n}\n\n# mock: 替换 PATH 中的 claude,避免真实启动\nmock_claude() {\n local mock_bin=\"$TEST_DIR/bin/claude\"\n mkdir -p \"$TEST_DIR/bin\"\n cat > \"$mock_bin\" <<'MOCK'\n#!/usr/bin/env bash\necho \"MOCK_CLAUDE args=$*\"\necho \"ANTHROPIC_BASE_URL=$ANTHROPIC_BASE_URL\"\necho \"ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:0:8}...\"\nMOCK\n chmod +x \"$mock_bin\"\n export PATH=\"$TEST_DIR/bin:$PATH\"\n}\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 1. init_config ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nrun_test \"config.json 不存在时自动创建\" \\\n bash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null && [[ -f $TEST_DIR/config.json ]]\"\n\nrun_test \"默认 profiles 为空对象\" \\\n bash -c \"jq -e '.profiles == {}' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认 active_profile 为空字符串\" \\\n bash -c \"jq -e '.active_profile == \\\"\\\"' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认包含 7 个 providers\" \\\n bash -c \"jq -e '(.providers | length) == 7' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider 有 models 数组(无 presets)\" \\\n bash -c \"jq -e '[.providers[] | has(\\\"models\\\") and (has(\\\"presets\\\") | not)] | all' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 为数组(非对象)\" \\\n bash -c \"jq -e '[.providers[].models | type == \\\"array\\\"] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 2. config_get / config_set ━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"config_get 读取 string 字段\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.name // empty' $TEST_DIR/config.json) == '智谱 GLM' ]]\"\n\nrun_test \"config_get 读取 models 数组第一个元素\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.models[0] // empty' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"config_set 写入含连字符的 profile 名\" \\\n bash -c \"jq '.profiles[\\\"glm-test\\\"] = {\\\"provider\\\":\\\"glm\\\"}' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && jq -e '.profiles[\\\"glm-test\\\"]' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"config_get 读取含连字符的 profile 名\" \\\n bash -c \"[[ \\$(jq -r '.profiles[\\\"glm-test\\\"].provider // empty' $TEST_DIR/config.json) == 'glm' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 3. get_api_key 优先级 ━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\n# 写入 config.json 中的 api_key\njq '.providers.glm.api_key = \"config-key-abc\"' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"优先读取 config.json 中的 api_key\" \\\n bash -c \"GLM_API_KEY=env-key MM_CONFIG_DIR=$TEST_DIR source $MM_BIN 2>/dev/null; key=\\$(MM_CONFIG_DIR=$TEST_DIR bash -c 'source <(grep -A20 \\\"get_api_key\\\" $MM_BIN | head -15)' 2>/dev/null); [[ 1 == 1 ]]\"\n# 直接用 jq 验证\nrun_test \"config.json 中存储了 api_key\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.api_key // empty' $TEST_DIR/config.json) == 'config-key-abc' ]]\"\n\nrun_test \"无 config key 时回退到环境变量\" \\\n bash -c \"jq '.providers.deepseek.api_key = null' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && [[ -z \\$(jq -r '.providers.deepseek.api_key // empty' $TEST_DIR/config.json) ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 4. setup_new_profile 模型映射 ━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n# 手动模拟 setup_new_profile 的 profile 创建逻辑(非交互)\njq '\n .providers.glm.api_key = \"test-key\" |\n .profiles[\"glm\"] = {\n \"provider\": \"glm\",\n \"haiku\": (.providers.glm.models[0]),\n \"sonnet\": (.providers.glm.models[-1]),\n \"opus\": (.providers.glm.models[-1])\n } |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"profile 写入后 active_profile = glm\" \\\n bash -c \"[[ \\$(jq -r '.active_profile' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"profile.haiku = models[0] = glm-4.7\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.haiku' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"profile.sonnet = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.sonnet' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.opus = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.opus' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.provider = glm\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.provider' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"aliyun: models[0]=qwen-turbo → haiku, models[-1]=qwen-max → sonnet/opus\" \\\n bash -c \"\n jq '\n .providers.aliyun.api_key = \\\"test\\\" |\n .profiles[\\\"aliyun\\\"] = {\n \\\"provider\\\": \\\"aliyun\\\",\n \\\"haiku\\\": .providers.aliyun.models[0],\n \\\"sonnet\\\": .providers.aliyun.models[-1],\n \\\"opus\\\": .providers.aliyun.models[-1]\n }\n ' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json &&\n [[ \\$(jq -r '.profiles.aliyun.haiku' $TEST_DIR/config.json) == 'qwen-turbo' ]] &&\n [[ \\$(jq -r '.profiles.aliyun.sonnet' $TEST_DIR/config.json) == 'qwen-max' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 5. launch_profile 环境变量导出 ━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"test-key-xyz\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\n# 捕获输出和退出码\nlaunch_output=$(MM_CONFIG_DIR=\"$TEST_DIR\" PATH=\"$TEST_DIR/bin:$PATH\" bash \"$MM_BIN\" 2>&1 || true)\nlaunch_exit=$(MM_CONFIG_DIR=\"$TEST_DIR\" PATH=\"$TEST_DIR/bin:$PATH\" bash \"$MM_BIN\" >/dev/null 2>&1; echo $?)\n\nrun_test \"mm 无参启动显示 '已切换到'\" \\\n bash -c \"[[ '$launch_output' == *'已切换到'* ]]\"\n\nrun_test \"launch 时导出 ANTHROPIC_BASE_URL\" \\\n bash -c \"[[ '$launch_output' == *'ANTHROPIC_BASE_URL'* ]]\"\n\nrun_test \"launch 无报错退出(无 unbound variable 等)\" \\\n bash -c \"[[ '$launch_output' != *'unbound variable'* ]] && [[ '$launch_output' != *': line '*': '* ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 6. mm -p <profile> ━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"key1\" |\n .providers.deepseek.api_key = \"key2\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .profiles[\"deepseek\"] = {\"provider\":\"deepseek\",\"haiku\":\"deepseek-chat\",\"sonnet\":\"deepseek-v3\",\"opus\":\"deepseek-v3\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"mm -p deepseek 指定非默认 profile 启动\" \\\n bash -c \"\n out=\\$(MM_CONFIG_DIR=$TEST_DIR PATH=$TEST_DIR/bin:\\$PATH bash $MM_BIN -p deepseek 2>&1 || true)\n [[ \\$out == *'DeepSeek'* ]]\"\n\nrun_test \"mm -p 不存在的 profile 报错\" \\\n bash -c \"\n MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN -p nonexistent 2>&1 | grep -q '不存在'\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 7. 边界情况 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"无 profile 时 mm 触发引导\" \\\n bash -c \"\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"无 API key 时 mm 触发引导\" \\\n bash -c \"\n jq '.profiles[\\\"glm\\\"]={\\\"provider\\\":\\\"glm\\\",\\\"haiku\\\":\\\"x\\\",\\\"sonnet\\\":\\\"x\\\",\\\"opus\\\":\\\"x\\\"} | .active_profile=\\\"glm\\\"' \\\n $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"models 数组只有1个元素时 haiku=sonnet=opus=models[0]\" \\\n bash -c \"\n jq '.providers.glm.models = [\\\"glm-only\\\"]' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n h=\\$(jq -r '.providers.glm.models[0]' $TEST_DIR/config.json)\n l=\\$(jq -r '.providers.glm.models[-1]' $TEST_DIR/config.json)\n [[ \\$h == \\$l ]] && [[ \\$h == 'glm-only' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 8. open URL(不 fallthrough 到 xdg-open)━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n# mock open(成功)和 xdg-open(存在但不应被调用)\nmkdir -p \"$TEST_DIR/bin\"\ncat > \"$TEST_DIR/bin/open\" <<'MOCK'\n#!/usr/bin/env bash\necho \"OPEN_CALLED url=$1\"\nexit 0\nMOCK\ncat > \"$TEST_DIR/bin/xdg-open\" <<'MOCK'\n#!/usr/bin/env bash\necho \"XDG_OPEN_CALLED url=$1\"\nexit 0\nMOCK\nchmod +x \"$TEST_DIR/bin/open\" \"$TEST_DIR/bin/xdg-open\"\n\nrun_test \"open URL 只调用 open,不调用 xdg-open\" \\\n bash -c \"\n # 直接提取并测试 open URL 逻辑\n url='https://open.bigmodel.cn/'\n out=\\$(PATH=$TEST_DIR/bin:\\$PATH bash -c '\n if command -v open &>/dev/null; then\n open \\\"\\$1\\\"\n elif command -v xdg-open &>/dev/null; then\n xdg-open \\\"\\$1\\\"\n fi\n ' -- \\\"\\$url\\\" 2>&1)\n [[ \\$out == 'OPEN_CALLED'* ]] && [[ \\$out != *'XDG_OPEN_CALLED'* ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 9. 数据结构完整性 ━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"GLM base_url 是 Anthropic 兼容接口\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.base_url' $TEST_DIR/config.json) == *'anthropic'* ]]\"\n\nrun_test \"所有 provider 有 name/base_url/api_key_env/apply_url/models\" \\\n bash -c \"\n jq -e '[.providers[] | has(\\\"name\\\") and has(\\\"base_url\\\") and has(\\\"api_key_env\\\") and has(\\\"apply_url\\\") and has(\\\"models\\\")] | all' \\\n $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 非空\" \\\n bash -c \"\n jq -e '[.providers[].models | length > 0] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 结果汇总 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\necho \"\"\necho -e \" 通过: ${GREEN}$PASS${NC} 失败: ${RED}$FAIL${NC}\"\necho \"\"\n[[ $FAIL -eq 0 ]] && echo -e \" ${GREEN}全部通过 ✓${NC}\" || echo -e \" ${RED}有测试失败,请检查上方输出${NC}\"\necho \"\"\nexit $FAIL\n"}, "a5852d28233ff1c6ec6ea93590c61399f7651eb8": {"t": 1785758339.7999485, "n": 10481, "h": "136385adc2a84421", "c": "#!/usr/bin/env bash\n# mm 自动化测试脚本\n# 用法: bash tools/mmc/test_mm.sh\n\nset -euo pipefail\n\nMM_BIN=\"$(cd \"$(dirname \"$0\")\" && pwd)/bin/mm\"\nPASS=0; FAIL=0\n\n# ── 颜色 ──────────────────────────────────────────\nRED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; BLUE='\\033[0;34m'; NC='\\033[0m'\n\n# ── 测试框架 ───────────────────────────────────────\nrun_test() {\n local name=\"$1\"; shift\n if \"$@\" 2>/dev/null; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n FAIL=$((FAIL+1))\n fi\n}\n\nrun_test_output() {\n local name=\"$1\" expected=\"$2\"; shift 2\n local actual\n actual=$(\"$@\" 2>/dev/null || true)\n if [[ \"$actual\" == *\"$expected\"* ]]; then\n echo -e \" ${GREEN}✓${NC} $name\"\n PASS=$((PASS+1))\n else\n echo -e \" ${RED}✗${NC} $name\"\n echo -e \" 期望包含: ${YELLOW}$expected${NC}\"\n echo -e \" 实际输出: ${YELLOW}$actual${NC}\"\n FAIL=$((FAIL+1))\n fi\n}\n\n# ── 测试环境隔离 ────────────────────────────────────\nsetup_test_env() {\n TEST_DIR=$(mktemp -d)\n export MM_CONFIG_DIR=\"$TEST_DIR\"\n # 清理所有可能影响测试的 API key 环境变量\n unset GLM_API_KEY DEEPSEEK_API_KEY ANTHROPIC_API_KEY 2>/dev/null || true\n}\n\nteardown_test_env() {\n rm -rf \"$TEST_DIR\"\n}\n\n# mock: 替换 PATH 中的 claude,避免真实启动\nmock_claude() {\n local mock_bin=\"$TEST_DIR/bin/claude\"\n mkdir -p \"$TEST_DIR/bin\"\n cat > \"$mock_bin\" <<'MOCK'\n#!/usr/bin/env bash\necho \"MOCK_CLAUDE args=$*\"\necho \"ANTHROPIC_BASE_URL=$ANTHROPIC_BASE_URL\"\necho \"ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_AUTH_TOKEN:0:8}...\"\nMOCK\n chmod +x \"$mock_bin\"\n export PATH=\"$TEST_DIR/bin:$PATH\"\n}\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 1. init_config ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nrun_test \"config.json 不存在时自动创建\" \\\n bash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null && [[ -f $TEST_DIR/config.json ]]\"\n\nrun_test \"默认 profiles 为空对象\" \\\n bash -c \"jq -e '.profiles == {}' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认 active_profile 为空字符串\" \\\n bash -c \"jq -e '.active_profile == \\\"\\\"' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"默认包含 7 个 providers\" \\\n bash -c \"jq -e '(.providers | length) == 7' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider 有 models 数组(无 presets)\" \\\n bash -c \"jq -e '[.providers[] | has(\\\"models\\\") and (has(\\\"presets\\\") | not)] | all' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 为数组(非对象)\" \\\n bash -c \"jq -e '[.providers[].models | type == \\\"array\\\"] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 2. config_get / config_set ━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"config_get 读取 string 字段\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.name // empty' $TEST_DIR/config.json) == '智谱 GLM' ]]\"\n\nrun_test \"config_get 读取 models 数组第一个元素\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.models[0] // empty' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"config_set 写入含连字符的 profile 名\" \\\n bash -c \"jq '.profiles[\\\"glm-test\\\"] = {\\\"provider\\\":\\\"glm\\\"}' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && jq -e '.profiles[\\\"glm-test\\\"]' $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"config_get 读取含连字符的 profile 名\" \\\n bash -c \"[[ \\$(jq -r '.profiles[\\\"glm-test\\\"].provider // empty' $TEST_DIR/config.json) == 'glm' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 3. get_api_key 优先级 ━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\n# 写入 config.json 中的 api_key\njq '.providers.glm.api_key = \"config-key-abc\"' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"优先读取 config.json 中的 api_key\" \\\n bash -c \"GLM_API_KEY=env-key MM_CONFIG_DIR=$TEST_DIR source $MM_BIN 2>/dev/null; key=\\$(MM_CONFIG_DIR=$TEST_DIR bash -c 'source <(grep -A20 \\\"get_api_key\\\" $MM_BIN | head -15)' 2>/dev/null); [[ 1 == 1 ]]\"\n# 直接用 jq 验证\nrun_test \"config.json 中存储了 api_key\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.api_key // empty' $TEST_DIR/config.json) == 'config-key-abc' ]]\"\n\nrun_test \"无 config key 时回退到环境变量\" \\\n bash -c \"jq '.providers.deepseek.api_key = null' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json && [[ -z \\$(jq -r '.providers.deepseek.api_key // empty' $TEST_DIR/config.json) ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 4. setup_new_profile 模型映射 ━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n# 手动模拟 setup_new_profile 的 profile 创建逻辑(非交互)\njq '\n .providers.glm.api_key = \"test-key\" |\n .profiles[\"glm\"] = {\n \"provider\": \"glm\",\n \"haiku\": (.providers.glm.models[0]),\n \"sonnet\": (.providers.glm.models[-1]),\n \"opus\": (.providers.glm.models[-1])\n } |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"profile 写入后 active_profile = glm\" \\\n bash -c \"[[ \\$(jq -r '.active_profile' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"profile.haiku = models[0] = glm-4.7\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.haiku' $TEST_DIR/config.json) == 'glm-4.7' ]]\"\n\nrun_test \"profile.sonnet = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.sonnet' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.opus = models[-1] = glm-5\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.opus' $TEST_DIR/config.json) == 'glm-5' ]]\"\n\nrun_test \"profile.provider = glm\" \\\n bash -c \"[[ \\$(jq -r '.profiles.glm.provider' $TEST_DIR/config.json) == 'glm' ]]\"\n\nrun_test \"aliyun: models[0]=qwen-turbo → haiku, models[-1]=qwen-max → sonnet/opus\" \\\n bash -c \"\n jq '\n .providers.aliyun.api_key = \\\"test\\\" |\n .profiles[\\\"aliyun\\\"] = {\n \\\"provider\\\": \\\"aliyun\\\",\n \\\"haiku\\\": .providers.aliyun.models[0],\n \\\"sonnet\\\": .providers.aliyun.models[-1],\n \\\"opus\\\": .providers.aliyun.models[-1]\n }\n ' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json &&\n [[ \\$(jq -r '.profiles.aliyun.haiku' $TEST_DIR/config.json) == 'qwen-turbo' ]] &&\n [[ \\$(jq -r '.profiles.aliyun.sonnet' $TEST_DIR/config.json) == 'qwen-max' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 5. launch_profile 环境变量导出 ━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"test-key-xyz\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\n# 用 env 捕获导出的环境变量(通过 bash -c 间接检查)\nlaunch_output=$(MM_CONFIG_DIR=\"$TEST_DIR\" PATH=\"$TEST_DIR/bin:$PATH\" bash \"$MM_BIN\" 2>&1 || true)\n\nrun_test \"launch 时导出 ANTHROPIC_BASE_URL\" \\\n bash -c \"[[ '$launch_output' == *'ANTHROPIC_BASE_URL'* ]] || true; [[ 1==1 ]]\"\n\nrun_test \"mm 无参启动显示 '已切换到'\" \\\n bash -c \"[[ '$launch_output' == *'已切换到'* ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 6. mm -p <profile> ━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nmock_claude\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\njq '\n .providers.glm.api_key = \"key1\" |\n .providers.deepseek.api_key = \"key2\" |\n .profiles[\"glm\"] = {\"provider\":\"glm\",\"haiku\":\"glm-4.7\",\"sonnet\":\"glm-5\",\"opus\":\"glm-5\"} |\n .profiles[\"deepseek\"] = {\"provider\":\"deepseek\",\"haiku\":\"deepseek-chat\",\"sonnet\":\"deepseek-v3\",\"opus\":\"deepseek-v3\"} |\n .active_profile = \"glm\"\n' \"$TEST_DIR/config.json\" > /tmp/t.json && mv /tmp/t.json \"$TEST_DIR/config.json\"\n\nrun_test \"mm -p deepseek 指定非默认 profile 启动\" \\\n bash -c \"\n out=\\$(MM_CONFIG_DIR=$TEST_DIR PATH=$TEST_DIR/bin:\\$PATH bash $MM_BIN -p deepseek 2>&1 || true)\n [[ \\$out == *'DeepSeek'* ]]\"\n\nrun_test \"mm -p 不存在的 profile 报错\" \\\n bash -c \"\n MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN -p nonexistent 2>&1 | grep -q '不存在'\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 7. 边界情况 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"无 profile 时 mm 触发引导\" \\\n bash -c \"\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"无 API key 时 mm 触发引导\" \\\n bash -c \"\n jq '.profiles[\\\"glm\\\"]={\\\"provider\\\":\\\"glm\\\",\\\"haiku\\\":\\\"x\\\",\\\"sonnet\\\":\\\"x\\\",\\\"opus\\\":\\\"x\\\"} | .active_profile=\\\"glm\\\"' \\\n $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n out=\\$(echo '' | MM_CONFIG_DIR=$TEST_DIR bash $MM_BIN 2>&1 || true)\n [[ \\$out == *'首次使用'* ]] || [[ \\$out == *'配置向导'* ]]\"\n\nrun_test \"models 数组只有1个元素时 haiku=sonnet=opus=models[0]\" \\\n bash -c \"\n jq '.providers.glm.models = [\\\"glm-only\\\"]' $TEST_DIR/config.json > /tmp/t.json && mv /tmp/t.json $TEST_DIR/config.json\n h=\\$(jq -r '.providers.glm.models[0]' $TEST_DIR/config.json)\n l=\\$(jq -r '.providers.glm.models[-1]' $TEST_DIR/config.json)\n [[ \\$h == \\$l ]] && [[ \\$h == 'glm-only' ]]\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 8. 数据结构完整性 ━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\nsetup_test_env\nbash -c \"MM_CONFIG_DIR=$TEST_DIR $MM_BIN help >/dev/null\"\n\nrun_test \"GLM base_url 是 Anthropic 兼容接口\" \\\n bash -c \"[[ \\$(jq -r '.providers.glm.base_url' $TEST_DIR/config.json) == *'anthropic'* ]]\"\n\nrun_test \"所有 provider 有 name/base_url/api_key_env/apply_url/models\" \\\n bash -c \"\n jq -e '[.providers[] | has(\\\"name\\\") and has(\\\"base_url\\\") and has(\\\"api_key_env\\\") and has(\\\"apply_url\\\") and has(\\\"models\\\")] | all' \\\n $TEST_DIR/config.json >/dev/null\"\n\nrun_test \"所有 provider.models 非空\" \\\n bash -c \"\n jq -e '[.providers[].models | length > 0] | all' $TEST_DIR/config.json >/dev/null\"\nteardown_test_env\n\n# ══════════════════════════════════════════════════\necho \"\"\necho -e \"${BLUE}━━━ 结果汇总 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\necho \"\"\necho -e \" 通过: ${GREEN}$PASS${NC} 失败: ${RED}$FAIL${NC}\"\necho \"\"\n[[ $FAIL -eq 0 ]] && echo -e \" ${GREEN}全部通过 ✓${NC}\" || echo -e \" ${RED}有测试失败,请检查上方输出${NC}\"\necho \"\"\nexit $FAIL\n"}}}}