fix: make doctor checks resilient to slow mcporter calls (#103)
This commit is contained in:
@@ -29,15 +29,35 @@ class BossZhipinChannel(Channel):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
[mcporter, "list"], capture_output=True,
|
[mcporter, "config", "get", "bosszhipin", "--json"],
|
||||||
encoding="utf-8", errors="replace", timeout=10
|
capture_output=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
timeout=5,
|
||||||
)
|
)
|
||||||
out = r.stdout.lower()
|
if r.returncode != 0 or "bosszhipin" not in r.stdout.lower():
|
||||||
if "boss" in out or "zhipin" in out:
|
|
||||||
return "ok", "可搜索职位、向 HR 打招呼"
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return "off", (
|
return "off", (
|
||||||
"mcporter 已装但 Boss直聘 MCP 未配置。\n"
|
"mcporter 已装但 Boss直聘 MCP 未配置。\n"
|
||||||
" 详见 https://github.com/mucsbr/mcp-bosszp"
|
" 详见 https://github.com/mucsbr/mcp-bosszp"
|
||||||
)
|
)
|
||||||
|
except Exception:
|
||||||
|
return "off", "mcporter 连接异常"
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
[mcporter, "call", "bosszhipin.get_login_info_tool", "--output", "json"],
|
||||||
|
capture_output=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
out = r.stdout.lower()
|
||||||
|
if r.returncode == 0 and "\"is_logged_in\": true" in out:
|
||||||
|
return "ok", "完整可用(职位搜索、登录态检查、向 HR 打招呼)"
|
||||||
|
if r.returncode == 0:
|
||||||
|
return "ok", "MCP 已连接,可搜索职位;打招呼前可能需要先登录"
|
||||||
|
return "warn", "MCP 已配置,但连接异常;请检查 mcp-bosszp 服务状态"
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return "warn", "MCP 已配置,但健康检查超时;请检查 mcp-bosszp 服务状态"
|
||||||
|
except Exception:
|
||||||
|
return "warn", "MCP 已配置,但连接异常;请检查 mcp-bosszp 服务状态"
|
||||||
|
|||||||
@@ -51,10 +51,13 @@ class XiaoHongShuChannel(Channel):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
[mcporter, "config", "list"], capture_output=True,
|
[mcporter, "config", "get", "xiaohongshu", "--json"],
|
||||||
encoding="utf-8", errors="replace", timeout=5
|
capture_output=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
timeout=5,
|
||||||
)
|
)
|
||||||
if "xiaohongshu" not in r.stdout:
|
if r.returncode != 0 or "xiaohongshu" not in r.stdout.lower():
|
||||||
return "off", (
|
return "off", (
|
||||||
"mcporter 已装但小红书 MCP 未配置。运行:\n"
|
"mcporter 已装但小红书 MCP 未配置。运行:\n"
|
||||||
+ _docker_run_hint() + "\n"
|
+ _docker_run_hint() + "\n"
|
||||||
@@ -62,13 +65,20 @@ class XiaoHongShuChannel(Channel):
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return "off", "mcporter 连接异常"
|
return "off", "mcporter 连接异常"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
[mcporter, "call", "xiaohongshu.check_login_status()"],
|
[mcporter, "list", "xiaohongshu", "--json"],
|
||||||
capture_output=True, encoding="utf-8", errors="replace", timeout=10
|
capture_output=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
timeout=10,
|
||||||
)
|
)
|
||||||
if "已登录" in r.stdout or "logged" in r.stdout.lower():
|
out = r.stdout.lower()
|
||||||
return "ok", "完整可用(阅读、搜索、发帖、评论、点赞)"
|
if r.returncode == 0 and '"status": "ok"' in out:
|
||||||
return "warn", "MCP 已连接但未登录,需扫码登录"
|
return "ok", "MCP 已连接(阅读、搜索、发帖、评论、点赞)"
|
||||||
|
return "warn", "MCP 已配置,但连接异常;请检查 xiaohongshu-mcp 服务状态"
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return "warn", "MCP 已配置,但健康检查超时;请检查 xiaohongshu-mcp 服务状态"
|
||||||
except Exception:
|
except Exception:
|
||||||
return "warn", "MCP 连接异常,检查 xiaohongshu-mcp 服务是否在运行"
|
return "warn", "MCP 已配置,但连接异常;请检查 xiaohongshu-mcp 服务状态"
|
||||||
|
|||||||
+44
-1
@@ -1,7 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Tests for channel registry basics."""
|
"""Tests for channel registry basics and health checks."""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from agent_reach.channels import get_all_channels, get_channel
|
from agent_reach.channels import get_all_channels, get_channel
|
||||||
|
from agent_reach.channels.bosszhipin import BossZhipinChannel
|
||||||
|
from agent_reach.channels.xiaohongshu import XiaoHongShuChannel
|
||||||
|
|
||||||
|
|
||||||
class TestChannelRegistry:
|
class TestChannelRegistry:
|
||||||
@@ -19,3 +24,41 @@ class TestChannelRegistry:
|
|||||||
assert "web" in names
|
assert "web" in names
|
||||||
assert "github" in names
|
assert "github" in names
|
||||||
assert "twitter" in names
|
assert "twitter" in names
|
||||||
|
|
||||||
|
|
||||||
|
class TestBossZhipinChannel:
|
||||||
|
def test_reports_ok_when_configured_and_logged_in(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda _: "/opt/homebrew/bin/mcporter")
|
||||||
|
|
||||||
|
def fake_run(cmd, **kwargs):
|
||||||
|
if cmd[:4] == ["/opt/homebrew/bin/mcporter", "config", "get", "bosszhipin"]:
|
||||||
|
return subprocess.CompletedProcess(cmd, 0, '{"name":"bosszhipin"}', "")
|
||||||
|
if cmd[:3] == ["/opt/homebrew/bin/mcporter", "call", "bosszhipin.get_login_info_tool"]:
|
||||||
|
return subprocess.CompletedProcess(cmd, 0, '{"is_logged_in": true}', "")
|
||||||
|
raise AssertionError(f"unexpected command: {cmd}")
|
||||||
|
|
||||||
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
||||||
|
|
||||||
|
assert BossZhipinChannel().check() == (
|
||||||
|
"ok",
|
||||||
|
"完整可用(职位搜索、登录态检查、向 HR 打招呼)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestXiaoHongShuChannel:
|
||||||
|
def test_reports_ok_when_server_health_is_ok(self, monkeypatch):
|
||||||
|
monkeypatch.setattr(shutil, "which", lambda _: "/opt/homebrew/bin/mcporter")
|
||||||
|
|
||||||
|
def fake_run(cmd, **kwargs):
|
||||||
|
if cmd[:4] == ["/opt/homebrew/bin/mcporter", "config", "get", "xiaohongshu"]:
|
||||||
|
return subprocess.CompletedProcess(cmd, 0, '{"name":"xiaohongshu"}', "")
|
||||||
|
if cmd[:4] == ["/opt/homebrew/bin/mcporter", "list", "xiaohongshu", "--json"]:
|
||||||
|
return subprocess.CompletedProcess(cmd, 0, '{"status": "ok"}', "")
|
||||||
|
raise AssertionError(f"unexpected command: {cmd}")
|
||||||
|
|
||||||
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
||||||
|
|
||||||
|
assert XiaoHongShuChannel().check() == (
|
||||||
|
"ok",
|
||||||
|
"MCP 已连接(阅读、搜索、发帖、评论、点赞)",
|
||||||
|
)
|
||||||
|
|||||||
+81
-20
@@ -2,8 +2,22 @@
|
|||||||
"""Tests for doctor module."""
|
"""Tests for doctor module."""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import agent_reach.doctor as doctor
|
||||||
from agent_reach.config import Config
|
from agent_reach.config import Config
|
||||||
from agent_reach.doctor import check_all, format_report
|
|
||||||
|
|
||||||
|
class _StubChannel:
|
||||||
|
def __init__(self, name, description, tier, status, message, backends=None):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.tier = tier
|
||||||
|
self._status = status
|
||||||
|
self._message = message
|
||||||
|
self.backends = backends or []
|
||||||
|
|
||||||
|
def check(self, config=None):
|
||||||
|
return self._status, self._message
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -12,26 +26,73 @@ def tmp_config(tmp_path):
|
|||||||
|
|
||||||
|
|
||||||
class TestDoctor:
|
class TestDoctor:
|
||||||
def test_zero_config_channels_ok(self, tmp_config):
|
def test_check_all_collects_channel_results(self, tmp_config, monkeypatch):
|
||||||
results = check_all(tmp_config)
|
monkeypatch.setattr(
|
||||||
assert results["web"]["status"] == "ok"
|
doctor,
|
||||||
assert results["github"]["status"] in ("ok", "warn") # warn if gh CLI not installed
|
"get_all_channels",
|
||||||
assert results["bilibili"]["status"] in ("ok", "warn") # warn on servers
|
lambda: [
|
||||||
assert results["rss"]["status"] == "ok"
|
_StubChannel("web", "网页", 0, "ok", "可抓取网页", ["requests"]),
|
||||||
|
_StubChannel("github", "GitHub", 0, "warn", "gh 未安装", ["gh"]),
|
||||||
|
_StubChannel("exa_search", "全网语义搜索", 1, "off", "mcporter 未配置", ["Exa"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_exa_off_without_key(self, tmp_config):
|
results = doctor.check_all(tmp_config)
|
||||||
results = check_all(tmp_config)
|
|
||||||
assert results["exa_search"]["status"] == "off"
|
|
||||||
|
|
||||||
def test_exa_key_does_not_force_enabled(self, tmp_config):
|
assert results == {
|
||||||
# Exa availability is determined by mcporter runtime/config state.
|
"web": {
|
||||||
tmp_config.set("exa_api_key", "test-key")
|
"status": "ok",
|
||||||
results = check_all(tmp_config)
|
"name": "网页",
|
||||||
assert results["exa_search"]["status"] in ("off", "ok")
|
"message": "可抓取网页",
|
||||||
|
"tier": 0,
|
||||||
|
"backends": ["requests"],
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"status": "warn",
|
||||||
|
"name": "GitHub",
|
||||||
|
"message": "gh 未安装",
|
||||||
|
"tier": 0,
|
||||||
|
"backends": ["gh"],
|
||||||
|
},
|
||||||
|
"exa_search": {
|
||||||
|
"status": "off",
|
||||||
|
"name": "全网语义搜索",
|
||||||
|
"message": "mcporter 未配置",
|
||||||
|
"tier": 1,
|
||||||
|
"backends": ["Exa"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_format_report(self):
|
||||||
|
report = doctor.format_report(
|
||||||
|
{
|
||||||
|
"web": {
|
||||||
|
"status": "ok",
|
||||||
|
"name": "网页",
|
||||||
|
"message": "可抓取网页",
|
||||||
|
"tier": 0,
|
||||||
|
"backends": ["requests"],
|
||||||
|
},
|
||||||
|
"exa_search": {
|
||||||
|
"status": "off",
|
||||||
|
"name": "全网语义搜索",
|
||||||
|
"message": "mcporter 未配置",
|
||||||
|
"tier": 1,
|
||||||
|
"backends": ["Exa"],
|
||||||
|
},
|
||||||
|
"xiaohongshu": {
|
||||||
|
"status": "warn",
|
||||||
|
"name": "小红书",
|
||||||
|
"message": "MCP 已配置,但健康检查超时",
|
||||||
|
"tier": 2,
|
||||||
|
"backends": ["mcporter"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def test_format_report(self, tmp_config):
|
|
||||||
results = check_all(tmp_config)
|
|
||||||
report = format_report(results)
|
|
||||||
assert "Agent Reach" in report
|
assert "Agent Reach" in report
|
||||||
assert "✅" in report
|
assert "✅ 装好即用:" in report
|
||||||
assert "渠道可用" in report
|
assert "🔍 搜索(mcporter 即可解锁):" in report
|
||||||
|
assert "🔧 配置后可用:" in report
|
||||||
|
assert "状态:1/3 个渠道可用" in report
|
||||||
|
assert "运行 `agent-reach setup` 解锁更多渠道" in report
|
||||||
|
|||||||
Reference in New Issue
Block a user