"""上传直链:地址来源、令牌、转义与新工具 case_upload_link。""" from pathlib import Path import pytest from src.cases import upload_page_url from src.config import clear_config_cache, load_config from src.mcp.tools import materials @pytest.fixture def isolated_cases(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: root = tmp_path / "cases" root.mkdir() monkeypatch.setattr("src.cases.CASE_ROOT", root) # CaseStore.root 是类属性(导入时就绑定 CASE_ROOT),所以直接改实例属性最省事, # 与 test_mcp_analysis.py 的既有做法一致 monkeypatch.setattr(materials._store, "root", root, raising=False) return root def _set_base(monkeypatch: pytest.MonkeyPatch, value: str) -> None: monkeypatch.setenv("UPLOAD_BASE_URL", value) clear_config_cache() def test_upload_base_url_comes_from_env(monkeypatch: pytest.MonkeyPatch) -> None: _set_base(monkeypatch, "http://192.168.1.20:5055") try: assert upload_page_url() == "http://192.168.1.20:5055/" finally: monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) clear_config_cache() def test_upload_base_url_configured_wins_and_strips_slash(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: data = tmp_path / "scripts" / "data" data.mkdir(parents=True) (data / "config.json").write_text('{"upload_base_url": "http://10.0.0.5:6000/"}', encoding="utf-8") monkeypatch.setattr("src.config._CONFIG_PATH", data / "config.json") monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) clear_config_cache() try: assert load_config()["upload_base_url"] == "http://10.0.0.5:6000/" # 原样保留,拼链接时才去尾斜杠 assert upload_page_url() == "http://10.0.0.5:6000/" finally: clear_config_cache() def test_unconfigured_falls_back_to_probe(isolated_cases: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) monkeypatch.setattr("src.config._CONFIG_PATH", isolated_cases / "missing.json") clear_config_cache() try: url = upload_page_url() assert url.startswith("http://") and url.endswith("/") assert ":5055" in url or ":80" not in url # 探测出来的地址一定带端口 finally: clear_config_cache() def test_case_create_returns_entry_url_with_token(isolated_cases: Path, monkeypatch: pytest.MonkeyPatch) -> None: _set_base(monkeypatch, "http://192.168.31.86:5055") try: result = materials.case_create("毕业采集", "travel") assert result["ok"] is True url = result["upload_url"] assert url.startswith("http://192.168.31.86:5055/u/") assert "?t=" in url and len(url.split("?t=")[1]) >= 16 # 会话 id 含中文:链接必须是纯 ASCII,否则部分客户端打不开 assert all(ord(ch) < 128 for ch in url) finally: monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) clear_config_cache() def test_upload_link_is_stable_and_scoped(isolated_cases: Path, monkeypatch: pytest.MonkeyPatch) -> None: _set_base(monkeypatch, "http://127.0.0.1:5055") try: created = materials.case_create("稳", None) again = materials.case_upload_link(created["case_id"]) assert again["ok"] is True and again["upload_url"] == created["upload_url"] assert again["pending_inbox"] == 0 missing = materials.case_upload_link("没这个会话") assert missing["ok"] is False and "case_list" in missing["next_action"] finally: monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) clear_config_cache() def test_token_only_in_link_not_in_summary(isolated_cases: Path, monkeypatch: pytest.MonkeyPatch) -> None: """令牌允许出现在直链里,但不该混进常规状态输出,免得每次轮询都复制一遍。""" _set_base(monkeypatch, "http://127.0.0.1:5055") try: created = materials.case_create("秘", None) cid = created["case_id"] token = created["upload_url"].split("?t=")[1] status = materials.case_status(cid) assert token not in str(status) listing = materials.case_list() assert token not in str(listing) finally: monkeypatch.delenv("UPLOAD_BASE_URL", raising=False) clear_config_cache()