1 line
43 KiB
JSON
1 line
43 KiB
JSON
{"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"}}}} |