Download tests/native_api/test_application.py from hunian003/message: direct link, hf CLI and curl.
- Browser
- Download file 31.4 kB
-
https://huggingface.co/spaces/hunian003/message/resolve/main/tests/native_api/test_application.py
- Command line
-
hf download hf://spaces/hunian003/message/tests/native_api/test_application.py
-
curl -L -o test_application.py https://huggingface.co/spaces/hunian003/message/resolve/main/tests/native_api/test_application.py
31.4 kB
| """真实 Node worker、HTTP 网关与官方 MCP SDK 的控制面验收。""" | |
| from __future__ import annotations | |
| import asyncio | |
| import io | |
| import gzip | |
| from functools import lru_cache | |
| import json | |
| import logging | |
| import os | |
| import socket | |
| import sys | |
| import tarfile | |
| import tempfile | |
| import unittest | |
| from pathlib import Path | |
| from dataclasses import replace | |
| from unittest.mock import patch | |
| import httpx | |
| import uvicorn | |
| from fastapi.testclient import TestClient | |
| from mcp import ClientSession | |
| from mcp.client.streamable_http import streamablehttp_client | |
| ROOT = Path(__file__).resolve().parents[2] | |
| sys.path.insert(0, str(ROOT / "apps/market-api")) | |
| from app.native.application import create_app | |
| from app.native.config import Settings | |
| from app.native.supervisor import loopback_http_client, Supervisor | |
| ADMIN = "fixture-admin-token-not-a-secret" | |
| TOKEN_A = "fixture-profile-a-token-not-a-secret" | |
| TOKEN_B = "fixture-profile-b-token-not-a-secret" | |
| def archive(files): | |
| stream = io.BytesIO() | |
| with tarfile.open(fileobj=stream, mode="w") as tar: | |
| for name, data in files.items(): | |
| raw = data.encode() if isinstance(data, str) else data | |
| info = tarfile.TarInfo("package/" + name) | |
| info.size = len(raw) | |
| tar.addfile(info, io.BytesIO(raw)) | |
| return gzip.compress(stream.getvalue(), mtime=0) | |
| def local_bundle(name): | |
| directory = ROOT / "plugins" / name | |
| return archive({str(path.relative_to(directory)): path.read_bytes() for path in directory.rglob("*") if path.is_file() and "node_modules" not in path.parts and "tests" not in path.parts}) | |
| def fixture(version="1.0.0", value="v1"): | |
| manifest = dict(name="@message/api-fixture", version=version, type="module", exports="./index.mjs", peerDependencies={"@deepseek-ai/dsh-tools": "0.2.0-rc.2", "@deepseek-ai/cordis": "4.0.4"}, dsh={"bundle": {"patch": "./cordis.patch.yml"}}) | |
| source = """import { defineTool } from '@deepseek-ai/dsh-tools'; | |
| export const inject=['tools']; | |
| export function apply(ctx){ | |
| ctx.tools.register(defineTool({name:'api_fixture',description:'验收 fixture',parameters:{},output:{schema:{type:'string'},render:(_a,v)=>[{type:'text',text:v}]},async execute(){return VALUE;}})); | |
| } | |
| """.replace("VALUE", json.dumps(value)) | |
| return archive({"package.json": json.dumps(manifest), "index.mjs": source, "cordis.patch.yml": '- insert:\n - id: api-fixture\n name: "@message/api-fixture"\n'}) | |
| def diagnostic_fixture(mode): | |
| name = "@message/diagnostic-" + mode | |
| manifest = dict(name=name, version="1.0.0", type="module", exports="./index.mjs", peerDependencies={"@deepseek-ai/dsh-tools": "0.2.0-rc.2", "@deepseek-ai/cordis": "4.0.4"}, dsh={"bundle": {"patch": "./cordis.patch.yml"}}) | |
| source = r""" | |
| import { writeSync } from 'node:fs'; | |
| const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
| async function writePieces(stream, text) { | |
| // 原生写入绕过 CLI write 过滤,独立验证 API 的第二层保护。 | |
| writeSync(stream.fd, text.slice(0, 5)); await sleep(40); writeSync(stream.fd, text.slice(5) + '\n'); | |
| } | |
| export async function apply() { | |
| const fail = async () => { | |
| await writePieces(process.stderr, process.env.SERVICE_A_TOKEN); | |
| for (const token of PLATFORM_TOKENS) await writePieces(process.stdout, token); | |
| process.exit(23); | |
| }; | |
| if (LATE) setTimeout(fail, 2000); else await fail(); | |
| } | |
| """.replace("PLATFORM_TOKENS", json.dumps([ADMIN, TOKEN_A, TOKEN_B])).replace("LATE", "true" if mode == "late" else "false") | |
| return archive({"package.json": json.dumps(manifest), "index.mjs": source, "cordis.patch.yml": f'- insert:\n - id: diagnostic-{mode}\n name: "{name}"\n'}) | |
| class ConfigurationTests(unittest.TestCase): | |
| def test_fail_closed_and_outside_repository(self): | |
| common = dict(data_dir=Path("/tmp/native-config-test"), admin_token=ADMIN, mcp_tokens={"a": TOKEN_A}, allowed_hosts=("localhost",), allowed_origins=("http://localhost",)) | |
| Settings(**common).validate() | |
| for changed in ({"admin_token": ""}, {"mcp_tokens": {"a": ADMIN}}, {"data_dir": ROOT / "profiles/test"}, {"allowed_hosts": ("*",)}, {"allowed_origins": ("null",)}): | |
| with self.subTest(changed=changed), self.assertRaises(ValueError): | |
| Settings(**(common | changed)).validate() | |
| def test_worker_environment_does_not_inherit_unrelated_credentials(self): | |
| settings = Settings(data_dir=Path("/tmp/native-config-test"), admin_token=ADMIN, mcp_tokens={}, allowed_hosts=("localhost",), allowed_origins=("http://localhost",)) | |
| with patch.dict(os.environ, {"MESSAGE_ADMIN_TOKEN": ADMIN, "OPENAI_API_KEY": "fixture-must-not-leak", "AWS_SECRET_ACCESS_KEY": "fixture-must-not-leak", "GITHUB_TOKEN": "fixture-must-not-leak"}): | |
| env = Supervisor(settings, None).env() | |
| self.assertNotIn("MESSAGE_ADMIN_TOKEN", env) | |
| self.assertNotIn("OPENAI_API_KEY", env) | |
| self.assertNotIn("AWS_SECRET_ACCESS_KEY", env) | |
| self.assertNotIn("GITHUB_TOKEN", env) | |
| self.assertIn("PATH", env) | |
| def test_plugin_environment_values_are_profile_and_worker_scoped(self): | |
| settings = Settings(data_dir=Path("/tmp/native-config-test"), admin_token=ADMIN, mcp_tokens={}, allowed_hosts=("localhost",), allowed_origins=("http://localhost",), plugin_env_allowlist={"a": ["SERVICE_A_TOKEN"], "b": ["SERVICE_B_TOKEN"]}) | |
| settings.validate() | |
| with patch.dict(os.environ, {"SERVICE_A_TOKEN": "fixture-value-a", "SERVICE_B_TOKEN": "fixture-value-b"}): | |
| supervisor = Supervisor(settings, None) | |
| self.assertEqual(supervisor.env("a", "http")["SERVICE_A_TOKEN"], "fixture-value-a") | |
| self.assertNotIn("SERVICE_B_TOKEN", supervisor.env("a", "http")) | |
| self.assertNotIn("SERVICE_A_TOKEN", supervisor.env("b", "http")) | |
| self.assertNotIn("SERVICE_A_TOKEN", supervisor.env("a", "configure")) | |
| self.assertEqual(json.loads(supervisor.env("a", "list")["MESSAGE_PLUGIN_ENV_ALLOWLIST"]), ["SERVICE_A_TOKEN"]) | |
| for invalid in ({"a": ["MESSAGE_ADMIN_TOKEN"]}, {"a": ["NODE_OPTIONS"]}, {"a": ["LD_PRELOAD"]}, {"a": ["HTTP_PROXY"]}, {"a": ["bad-name"]}, {"a": "SERVICE_A_TOKEN"}): | |
| with self.assertRaises(ValueError): | |
| replace(settings, plugin_env_allowlist=invalid).validate() | |
| def test_native_import_does_not_load_legacy(self): | |
| self.assertNotIn("app.plugins.manager", sys.modules) | |
| self.assertNotIn("app.main", sys.modules) | |
| class GatewayPolicyTests(unittest.TestCase): | |
| def test_authentication_precedes_body_parsing_and_origin_is_exact(self): | |
| with tempfile.TemporaryDirectory(prefix="message-policy-") as temporary: | |
| settings = Settings(data_dir=Path(temporary), admin_token=ADMIN, mcp_tokens={"a": TOKEN_A}, allowed_hosts=("testserver",), allowed_origins=("http://testserver",)) | |
| consumed = [] | |
| def body(): | |
| consumed.append(True) | |
| yield b"untrusted multipart body" | |
| with TestClient(create_app(settings)) as client: | |
| rejected = client.post("/api/native/catalog", content=body(), headers={"Content-Type": "multipart/form-data; boundary=fixture"}) | |
| self.assertEqual(rejected.status_code, 401) | |
| self.assertFalse(consumed, "鉴权前不得解析或读取上传请求体") | |
| self.assertEqual(client.get("/api/native/health", headers={"Origin": "null"}).status_code, 403) | |
| self.assertEqual(client.get("/api/native/health", headers={"Origin": "http://testserver.evil"}).status_code, 403) | |
| self.assertEqual(client.get("/api/native/health", headers={"Host": "testserver:invalid"}).status_code, 400) | |
| self.assertEqual(client.get("/api/native/catalog", headers={"Authorization": f"Bearer {TOKEN_A}"}).status_code, 401) | |
| self.assertEqual(client.get("/mcp/a", headers={"Authorization": f"Bearer {ADMIN}"}).status_code, 401) | |
| self.assertEqual(client.get("/api/native/readiness").json(), {"status": "ready"}) | |
| self.assertEqual(client.get("/api/plugins", headers={"Authorization": f"Bearer {ADMIN}"}).status_code, 404) | |
| class NativeIntegrationTests(unittest.IsolatedAsyncioTestCase): | |
| async def asyncSetUp(self): | |
| self.temp = tempfile.TemporaryDirectory(prefix="message-native-api-") | |
| self.root = Path(self.temp.name) | |
| self.settings = Settings(data_dir=self.root, admin_token=ADMIN, mcp_tokens={"a": TOKEN_A, "b": TOKEN_B}, plugin_env_allowlist={"a": ["SERVICE_A_TOKEN", "SERVICE_MISSING_TOKEN"], "b": ["SERVICE_B_TOKEN"]}, allowed_hosts=("127.0.0.1",), allowed_origins=("http://127.0.0.1",)) | |
| self.running = [] | |
| await self.start_server() | |
| self.client = httpx.AsyncClient(trust_env=False, base_url=self.url, headers={"Authorization": f"Bearer {ADMIN}"}, timeout=90) | |
| async def start_server(self): | |
| self.app = create_app(self.settings) | |
| sock = socket.socket() | |
| sock.bind(("127.0.0.1", 0)) | |
| self.url = f"http://127.0.0.1:{sock.getsockname()[1]}" | |
| server = uvicorn.Server(uvicorn.Config(self.app, log_level="error", lifespan="on")) | |
| task = asyncio.create_task(server.serve(sockets=[sock])) | |
| self.running.append((server, task, sock)) | |
| while not server.started: | |
| if task.done(): | |
| await task | |
| self.fail("API 未启动") | |
| await asyncio.sleep(.02) | |
| async def stop_server(self): | |
| server, task, sock = self.running.pop() | |
| server.should_exit = True | |
| await asyncio.wait_for(task, 45) | |
| sock.close() | |
| async def asyncTearDown(self): | |
| await self.client.aclose() | |
| while self.running: | |
| await self.stop_server() | |
| self.temp.cleanup() | |
| async def post(self, path, data=None, **kwargs): | |
| response = await self.client.post("/api/native" + path, json=data, **kwargs) | |
| self.assertLess(response.status_code, 300, response.text) | |
| return response.json() | |
| async def operation(self, profile_id, kind, payload=None, **kwargs): | |
| operation = await self.post(f"/profiles/{profile_id}/{kind}", payload, **kwargs) | |
| return await self.wait_operation(operation["operation_id"]) | |
| async def wait_operation(self, operation_id): | |
| for _ in range(3000): | |
| response = await self.client.get("/api/native/operations/" + operation_id) | |
| value = response.json() | |
| if value["status"] in {"succeeded", "failed", "cancelled"}: | |
| return value | |
| await asyncio.sleep(.05) | |
| self.fail("操作未在验收窗口完成") | |
| async def upload(self, data): | |
| response = await self.client.post("/api/native/catalog", files={"file": ("bundle.tgz", data, "application/gzip")}) | |
| self.assertEqual(response.status_code, 201, response.text) | |
| return response.json() | |
| async def install(self, profile_id, data): | |
| item = await self.upload(data) | |
| operation = await self.operation(profile_id, "install", {"artifact_id": item["id"]}) | |
| self.assertEqual(operation["status"], "succeeded", operation) | |
| return item | |
| async def call(self, profile_id="a", name="api_fixture"): | |
| return await self.post(f"/profiles/{profile_id}/call", {"name": name, "arguments": {}}) | |
| async def test_complete_real_lifecycle_gateway_persistence_and_isolation(self): | |
| for profile_id in ("a", "b"): | |
| await self.post("/profiles", {"id": profile_id}) | |
| self.assertEqual((await self.client.get("/api/native/catalog", headers={"Authorization": ""})).status_code, 401) | |
| self.assertEqual((await self.client.get("/api/native/catalog", headers={"Authorization": f"Bearer {TOKEN_A}"})).status_code, 401) | |
| self.assertEqual((await self.client.get("/api/native/health", headers={"Authorization": ""})).json(), {"status": "healthy"}) | |
| self.assertEqual((await self.client.get("/api/native/health", headers={"Host": "evil.example"})).status_code, 400) | |
| self.assertEqual((await self.client.get("/api/native/health", headers={"Origin": "https://evil.example"})).status_code, 403) | |
| failed = await self.operation("a", "restart") | |
| self.assertEqual(failed["status"], "failed", failed) | |
| readiness = await self.client.get("/api/native/readiness", headers={"Authorization": ""}) | |
| self.assertEqual(readiness.status_code, 503) | |
| self.assertEqual(readiness.json(), {"status": "degraded"}) | |
| self.assertIn("profiles", (await self.client.get("/api/native/readiness")).json()) | |
| for profile_id in ("a", "b"): | |
| await self.install(profile_id, local_bundle("mcp-export")) | |
| await self.install(profile_id, fixture()) | |
| pending = (await self.client.get(f"/api/native/profiles/{profile_id}")).json() | |
| self.assertTrue(pending["restart_required"]) | |
| restarted = await self.operation(profile_id, "restart") | |
| self.assertEqual(restarted["status"], "succeeded", restarted) | |
| self.assertEqual((await self.call())["content"][0]["text"], "v1") | |
| self.assertEqual((await self.client.get("/api/native/readiness")).status_code, 200) | |
| self.assertEqual((await self.client.get("/mcp/b", headers={"Authorization": f"Bearer {TOKEN_A}"})).status_code, 401) | |
| self.assertEqual((await self.client.get("/mcp/a", headers={"Authorization": f"Bearer {ADMIN}"})).status_code, 401) | |
| await self.install("a", local_bundle("text-tools")) | |
| configured_state = (await self.client.get("/api/native/profiles/a")).json() | |
| configurable = next(plugin for plugin in configured_state["plugins"] if plugin["moduleName"] == "@message/text-tools") | |
| changed = await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": "API配置:"}}) | |
| self.assertEqual(changed["status"], "succeeded", changed) | |
| self.assertTrue((await self.client.get("/api/native/profiles/a")).json()["restart_required"]) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| rendered = await self.post("/profiles/a/call", {"name": "message_text_uppercase", "arguments": {"text": "hello"}}) | |
| self.assertEqual(rendered["content"][0]["text"], "API配置:HELLO") | |
| rejected_config = await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": 5}}) | |
| self.assertEqual(rejected_config["status"], "failed", rejected_config) | |
| config_after = (await self.client.get("/api/native/profiles/a")).json() | |
| self.assertEqual(next(plugin["config"]["prefix"] for plugin in config_after["plugins"] if plugin["moduleName"] == "@message/text-tools"), "API配置:") | |
| unauthorized = await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": {"$env": "SERVICE_B_TOKEN"}}}) | |
| self.assertEqual(unauthorized["status"], "failed") | |
| with patch.dict(os.environ, {"SERVICE_A_TOKEN": "fixture-env-secret-alpha", "SERVICE_B_TOKEN": "fixture-env-secret-beta", "SERVICE_MISSING_TOKEN": ""}): | |
| missing = await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": {"$env": "SERVICE_MISSING_TOKEN"}}}) | |
| self.assertEqual(missing["status"], "succeeded", missing) | |
| refused = await self.operation("a", "restart") | |
| self.assertEqual(refused["status"], "failed") | |
| self.assertIn("SERVICE_MISSING_TOKEN", refused["error"]) | |
| reference = await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": {"$env": "SERVICE_A_TOKEN"}}}) | |
| self.assertEqual(reference["status"], "succeeded", reference) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| redacted = await self.post("/profiles/a/call", {"name": "message_text_uppercase", "arguments": {"text": "hello"}}) | |
| self.assertEqual(redacted["content"][0]["text"], "[REDACTED]HELLO") | |
| state_text = (await self.client.get("/api/native/profiles/a")).text | |
| self.assertNotIn("fixture-env-secret-alpha", state_text) | |
| self.assertNotIn("fixture-env-secret-beta", state_text) | |
| self.assertIn("SERVICE_A_TOKEN", state_text) | |
| with patch.dict(os.environ, {"SERVICE_A_TOKEN": ADMIN}): | |
| reused = await self.operation("a", "restart") | |
| self.assertEqual(reused["status"], "failed") | |
| self.assertNotIn(ADMIN, json.dumps(reused)) | |
| await self.operation("a", "configure", {"entry_id": configurable["entry_id"], "config": {"prefix": "API配置:"}}) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| # 两个真实 SDK 会话同时存在,DELETE 一个之后另一个和新会话仍可用。 | |
| async with streamablehttp_client(self.url + "/mcp/a", httpx_client_factory=loopback_http_client, headers={"Authorization": f"Bearer {TOKEN_A}"}) as (r1, w1, session1): | |
| async with ClientSession(r1, w1) as first: | |
| await first.initialize() | |
| async with streamablehttp_client(self.url + "/mcp/a", httpx_client_factory=loopback_http_client, headers={"Authorization": f"Bearer {TOKEN_A}"}) as (r2, w2, session2): | |
| async with ClientSession(r2, w2) as second: | |
| await second.initialize() | |
| self.assertNotEqual(session1(), session2()) | |
| self.assertEqual((await second.call_tool("api_fixture", {})).content[0].text, "v1") | |
| # 会话 ID 不能替代访问令牌,且不能跨 profile 重用。 | |
| self.assertEqual((await self.client.get("/mcp/a", headers={"Authorization": "", "Mcp-Session-Id": session1()})).status_code, 401) | |
| self.assertEqual((await self.client.get("/mcp/b", headers={"Authorization": f"Bearer {TOKEN_B}", "Mcp-Session-Id": session1(), "Accept": "text/event-stream"})).status_code, 404) | |
| self.assertTrue((await first.list_tools()).tools) | |
| disabled = await self.operation("a", "disable", {"name": "@message/api-fixture"}, headers={"Idempotency-Key": "disable-once"}) | |
| repeated = await self.post("/profiles/a/disable", {"name": "@message/api-fixture"}, headers={"Idempotency-Key": "disable-once"}) | |
| self.assertEqual(repeated["operation_id"], disabled["id"]) | |
| conflict = await self.client.post("/api/native/profiles/a/enable", json={"name": "@message/api-fixture"}, headers={"Idempotency-Key": "disable-once"}) | |
| self.assertEqual(conflict.status_code, 409) | |
| self.assertEqual((await self.call())["content"][0]["text"], "v1") | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| self.assertTrue((await self.call())["isError"]) | |
| self.assertEqual((await self.call("b"))["content"][0]["text"], "v1") | |
| await self.operation("a", "enable", {"name": "@message/api-fixture"}) | |
| upgraded_data = fixture("1.0.1", "v2") | |
| upgrade = await self.install("a", upgraded_data) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| self.assertEqual((await self.call())["content"][0]["text"], "v2") | |
| download = await self.client.get(f"/api/native/catalog/{upgrade['id']}/download") | |
| self.assertEqual(download.status_code, 200) | |
| self.assertEqual(download.content, upgraded_data) | |
| conflict = await self.client.post("/api/native/catalog", files={"file": ("conflict.tgz", fixture("1.0.1", "altered"))}) | |
| self.assertEqual(conflict.status_code, 409) | |
| pids = [worker.process.pid for worker in self.app.state.supervisor.workers.values()] | |
| await self.stop_server() | |
| for pid in pids: | |
| with self.assertRaises(ProcessLookupError): | |
| os.kill(pid, 0) | |
| await self.start_server() | |
| await self.client.aclose() | |
| self.client = httpx.AsyncClient(trust_env=False, base_url=self.url, headers={"Authorization": f"Bearer {ADMIN}"}, timeout=90) | |
| self.assertEqual((await self.call())["content"][0]["text"], "v2") | |
| self.assertEqual((await self.call("b"))["content"][0]["text"], "v1") | |
| self.assertEqual((await self.client.get(f"/api/native/operations/{disabled['id']}")).json()["status"], "succeeded") | |
| removed = await self.operation("a", "remove", {"name": "@message/api-fixture"}) | |
| self.assertEqual(removed["status"], "succeeded", removed) | |
| state = (await self.client.get("/api/native/profiles/a")).json() | |
| self.assertEqual(state["status"], "stopped") | |
| self.assertFalse(any(bundle["name"] == "@message/api-fixture" for bundle in state["bundles"])) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| self.assertTrue((await self.call())["isError"]) | |
| async def test_split_worker_diagnostics_never_persist_secrets(self): | |
| secret = "fixture-cross-chunk-environment-secret-2026" | |
| await self.post("/profiles", {"id": "a"}) | |
| await self.install("a", local_bundle("mcp-export")) | |
| captured = io.StringIO() | |
| logger = logging.getLogger("uvicorn.error") | |
| handler = logging.StreamHandler(captured) | |
| logger.addHandler(handler) | |
| try: | |
| with patch.dict(os.environ, {"SERVICE_A_TOKEN": secret}): | |
| await self.install("a", diagnostic_fixture("startup")) | |
| failed = await self.operation("a", "restart") | |
| self.assertEqual(failed["status"], "failed", failed) | |
| self.assertIn("[REDACTED]", failed["error"]) | |
| await self.operation("a", "disable", {"name": "@message/diagnostic-startup"}) | |
| await self.install("a", diagnostic_fixture("late")) | |
| restarted = await self.operation("a", "restart") | |
| self.assertEqual(restarted["status"], "succeeded", restarted) | |
| for _ in range(300): | |
| state = (await self.client.get("/api/native/profiles/a")).json() | |
| if state["status"] == "error": | |
| break | |
| await asyncio.sleep(.05) | |
| self.assertEqual(state["status"], "error") | |
| self.assertIn("[REDACTED]", state["last_error"]) | |
| # 覆盖 Uvicorn 异常日志的独立出口,防止安全响应之外泄漏 traceback。 | |
| try: | |
| raise RuntimeError("fixture exception " + secret) | |
| except RuntimeError: | |
| logger.exception("fixture diagnostic %s", ADMIN) | |
| # 环境值离开作用域后,旧 generation 的诊断仍应保持脱敏。 | |
| snapshots = [(await self.client.get("/api/native/profiles/a")).text, (await self.client.get("/api/native/operations")).text, (await self.client.get("/api/native/readiness")).text, captured.getvalue()] | |
| self.assertIn("[REDACTED]", captured.getvalue()) | |
| for forbidden in (secret, ADMIN, TOKEN_A, TOKEN_B): | |
| for snapshot in snapshots: | |
| self.assertNotIn(forbidden, snapshot) | |
| for database in self.root.glob("control.sqlite3*"): | |
| self.assertNotIn(forbidden.encode(), database.read_bytes()) | |
| finally: | |
| logger.removeHandler(handler) | |
| async def test_api_sigkill_recovers_only_owned_worker(self): | |
| await self.stop_server() | |
| children = [] | |
| log_path = self.root / "api-crash.log" | |
| log = log_path.open("wb") | |
| async def start_child(): | |
| with socket.socket() as probe: | |
| probe.bind(("127.0.0.1", 0)) | |
| port = probe.getsockname()[1] | |
| env = Supervisor(self.settings, None).env() | |
| env.update(MESSAGE_AUTH_MODE="legacy_token", PYTHONPATH=str(ROOT / "apps/market-api"), MESSAGE_DATA_DIR=str(self.root), MESSAGE_ADMIN_TOKEN=ADMIN, MESSAGE_MCP_TOKENS_JSON=json.dumps({"a": TOKEN_A, "b": TOKEN_B}), MESSAGE_ALLOWED_HOSTS="127.0.0.1", MESSAGE_ALLOWED_ORIGINS="http://127.0.0.1") | |
| child = await asyncio.create_subprocess_exec(sys.executable, "-m", "uvicorn", "app.cloud_main:app", "--host", "127.0.0.1", "--port", str(port), "--log-level", "error", "--timeout-graceful-shutdown", "2", cwd=ROOT, env=env, stdout=log, stderr=log) | |
| children.append(child) | |
| await self.client.aclose() | |
| self.client = httpx.AsyncClient(base_url=f"http://127.0.0.1:{port}", headers={"Authorization": f"Bearer {ADMIN}"}, timeout=90, trust_env=False) | |
| for _ in range(1000): | |
| self.assertIsNone(child.returncode, log_path.read_text()) | |
| try: | |
| if (await self.client.get("/api/native/health")).status_code == 200: | |
| return child | |
| except httpx.ConnectError: | |
| pass | |
| await asyncio.sleep(.05) | |
| self.fail("强杀恢复测试 API 启动超时:" + log_path.read_text()) | |
| try: | |
| first = await start_child() | |
| await self.post("/profiles", {"id": "a"}) | |
| await self.install("a", local_bundle("mcp-export")) | |
| await self.install("a", fixture()) | |
| self.assertEqual((await self.operation("a", "restart"))["status"], "succeeded") | |
| before = (await self.client.get("/api/native/profiles/a")).json() | |
| marker = self.root / "profiles/a/.message-worker.json" | |
| old_pid = json.loads(marker.read_text())["pid"] | |
| first.kill() | |
| await first.wait() | |
| # 强杀控制面不应让冷恢复把旧 worker 报作新 generation。 | |
| await start_child() | |
| after = (await self.client.get("/api/native/profiles/a")).json() | |
| self.assertEqual(after["status"], "running", after) | |
| self.assertGreater(after["generation"], before["generation"]) | |
| self.assertNotEqual(json.loads(marker.read_text())["pid"], old_pid) | |
| self.assertEqual((await self.call())["content"][0]["text"], "v1") | |
| if Path(f"/proc/{old_pid}/stat").exists(): | |
| fields = Path(f"/proc/{old_pid}/stat").read_text().split(")", 1)[1].split() | |
| self.assertEqual(fields[0], "Z", "旧 worker 必须退出;孤儿僵尸由容器 init 回收") | |
| finally: | |
| for child in children: | |
| if child.returncode is None: | |
| child.terminate() | |
| try: | |
| await asyncio.wait_for(child.wait(), 30) | |
| except asyncio.TimeoutError: | |
| child.kill() | |
| await child.wait() | |
| log.close() | |
| async def test_recovery_refuses_unverified_reused_pid(self): | |
| await self.post("/profiles", {"id": "a"}) | |
| # 记录故意指向本验收进程;命令并非 Node worker,绝不能收到信号。 | |
| identity = self.app.state.supervisor.process_identity(os.getpid()) | |
| self.app.state.store.own_process(dict(pid=os.getpid(), profile_id="a", command="http", **identity)) | |
| await self.stop_server() | |
| await self.start_server() | |
| await self.client.aclose() | |
| self.client = httpx.AsyncClient(base_url=self.url, headers={"Authorization": f"Bearer {ADMIN}"}, timeout=90, trust_env=False) | |
| state = (await self.client.get("/api/native/profiles/a")).json() | |
| self.assertEqual(state["status"], "error") | |
| self.assertIn("无法验证", state["last_error"]) | |
| self.assertEqual((await self.client.get("/api/native/readiness")).status_code, 503) | |
| failed = await self.operation("a", "restart") | |
| self.assertEqual(failed["status"], "failed") | |
| self.assertFalse(self.app.state.supervisor.workers) | |
| os.kill(os.getpid(), 0) | |
| self.app.state.store.forget_process(os.getpid()) | |
| async def test_running_install_cancel_and_integrity_recovery(self): | |
| await self.post("/profiles", {"id": "a"}) | |
| data = local_bundle("mcp-export") | |
| artifact = await self.upload(data) | |
| queued = await self.post("/profiles/a/install", {"artifact_id": artifact["id"]}) | |
| run_file = self.root / "profiles/a/.plugin-manager/run.json" | |
| for _ in range(1000): | |
| if run_file.exists(): | |
| break | |
| await asyncio.sleep(.01) | |
| self.assertTrue(run_file.exists(), "应观察到真实 pnpm 安装后再取消") | |
| await self.post(f"/operations/{queued['operation_id']}/cancel") | |
| cancelled = await self.wait_operation(queued["operation_id"]) | |
| self.assertEqual(cancelled["status"], "cancelled", cancelled) | |
| self.assertFalse(run_file.exists()) | |
| profile = (await self.client.get("/api/native/profiles/a")).json() | |
| self.assertEqual(profile["desired_revision"], 0) | |
| self.assertFalse(profile["bundles"]) | |
| stored = self.root / "catalog" / (artifact["id"] + ".tgz") | |
| stored.write_bytes(b"tampered fixture") | |
| self.assertEqual((await self.client.get(f"/api/native/catalog/{artifact['id']}/download")).status_code, 409) | |
| failed = await self.operation("a", "install", {"artifact_id": artifact["id"]}) | |
| self.assertEqual(failed["status"], "failed") | |
| self.assertIn("SHA256", failed["error"]) | |
| restored = await self.upload(data) | |
| self.assertEqual(restored["id"], artifact["id"]) | |
| self.assertEqual(stored.read_bytes(), data) | |
| async def test_queued_cancel_single_instance_and_interrupted_recovery(self): | |
| await self.post("/profiles", {"id": "a"}) | |
| for _ in range(2): | |
| empty_key = await self.client.post("/api/native/profiles/a/restart", headers={"Idempotency-Key": ""}) | |
| self.assertEqual(empty_key.status_code, 409) | |
| self.assertFalse(self.app.state.store.operations("a")) | |
| lock = self.app.state.supervisor.lock("a") | |
| await lock.acquire() | |
| try: | |
| queued = await self.post("/profiles/a/restart") | |
| cancelled = await self.post(f"/operations/{queued['operation_id']}/cancel") | |
| self.assertEqual(cancelled["status"], "cancelled") | |
| self.assertEqual((await self.client.post(f"/api/native/operations/{queued['operation_id']}/cancel")).status_code, 409) | |
| finally: | |
| lock.release() | |
| other = create_app(self.settings) | |
| with self.assertRaisesRegex(RuntimeError, "一个 worker"): | |
| async with other.router.lifespan_context(other): | |
| pass | |
| while self.app.state.operations.tasks: | |
| await asyncio.sleep(.01) | |
| value = self.app.state.store.operation(queued["operation_id"]) | |
| value.update(status="running", cancel_requested=False) | |
| self.app.state.store.save_operation(value) | |
| await self.stop_server() | |
| await self.start_server() | |
| async with httpx.AsyncClient(trust_env=False, base_url=self.url, headers={"Authorization": f"Bearer {ADMIN}"}) as client: | |
| recovered = (await client.get(f"/api/native/operations/{value['id']}")).json() | |
| self.assertEqual(recovered["status"], "failed") | |
| self.assertIn("interrupted", recovered["error"]) | |
| if __name__ == "__main__": | |
| unittest.main() | |