# coding=utf-8 """ @Time :2026/6/30 @Author :tao.chen End-to-end HTTP tests for files_mcp.server.app. Uses FastAPI TestClient directly (no MCP transport) — same pattern as tests/integration/test_mcp_routes.py. """ from fastapi.testclient import TestClient from files_mcp.core import path_guard from files_mcp.server import app def _client(tmp_path) -> TestClient: import os os.environ["FILES_MCP_ROOT"] = str(tmp_path) path_guard.set_root_for_testing(tmp_path.resolve()) return TestClient(app) class TestCrud: def test_create_then_read(self, tmp_path): with _client(tmp_path) as c: r = c.post("/create_file", json={"path": "a.txt", "content": "hello"}) assert r.status_code == 200 assert r.json() == {"path": str(tmp_path / "a.txt"), "status": "CREATED", "size": 5} r = c.post("/read_file", json={"path": "a.txt"}) assert r.status_code == 200 assert r.json()["content"] == "hello" def test_update_existing(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "old"}) r = c.post("/update_file", json={"path": "a.txt", "content": "new"}) assert r.status_code == 200 assert r.json()["status"] == "UPDATED" r = c.post("/read_file", json={"path": "a.txt"}) assert r.json()["content"] == "new" def test_delete(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "x"}) r = c.post("/delete_file", json={"path": "a.txt"}) assert r.status_code == 200 assert r.json()["status"] == "DELETED" r = c.post("/read_file", json={"path": "a.txt"}) assert r.status_code == 404 class TestErrors: def test_create_existing_returns_409(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "x"}) r = c.post("/create_file", json={"path": "a.txt", "content": "y"}) assert r.status_code == 409 def test_update_missing_returns_404(self, tmp_path): with _client(tmp_path) as c: r = c.post("/update_file", json={"path": "nope.txt", "content": "x"}) assert r.status_code == 404 def test_escape_returns_400(self, tmp_path): with _client(tmp_path) as c: r = c.post("/read_file", json={"path": "/etc/passwd"}) assert r.status_code == 400 assert "escapes sandbox" in r.json()["detail"] def test_too_large_returns_400(self, tmp_path): with _client(tmp_path) as c: r = c.post( "/create_file", json={"path": "big.txt", "content": "x" * (1024 * 1024 + 1)}, ) assert r.status_code == 400 assert "too large" in r.json()["detail"] def test_non_utf8_returns_400(self, tmp_path): with _client(tmp_path) as c: (tmp_path / "bin").write_bytes(b"\xff\xfe\x00") r = c.post("/read_file", json={"path": "bin"}) assert r.status_code == 400 assert "utf-8" in r.json()["detail"] class TestIntrospection: def test_list_dir(self, tmp_path): with _client(tmp_path) as c: (tmp_path / "a.txt").write_text("a", encoding="utf-8") (tmp_path / "b").mkdir() r = c.post("/list_dir", json={"path": ".", "recursive": False, "max_depth": 1}) assert r.status_code == 200 names = sorted(e["name"] for e in r.json()["entries"]) assert names == ["a.txt", "b"] def test_stat(self, tmp_path): with _client(tmp_path) as c: (tmp_path / "f.txt").write_text("hi", encoding="utf-8") r = c.post("/stat", json={"path": "f.txt"}) assert r.status_code == 200 assert r.json()["type"] == "file" assert r.json()["size"] == 2 def test_search(self, tmp_path): with _client(tmp_path) as c: (tmp_path / "a.py").write_text("a", encoding="utf-8") (tmp_path / "b.txt").write_text("b", encoding="utf-8") r = c.post("/search", json={"path": ".", "pattern": "*.py", "recursive": False}) assert r.status_code == 200 assert len(r.json()["matches"]) == 1 class TestMoveCopy: def test_move(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "x"}) r = c.post("/move", json={"src": "a.txt", "dst": "b.txt"}) assert r.status_code == 200 assert r.json()["status"] == "MOVED" r = c.post("/read_file", json={"path": "b.txt"}) assert r.json()["content"] == "x" def test_move_existing_dst_409(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "x"}) c.post("/create_file", json={"path": "b.txt", "content": "y"}) r = c.post("/move", json={"src": "a.txt", "dst": "b.txt"}) assert r.status_code == 409 def test_copy(self, tmp_path): with _client(tmp_path) as c: c.post("/create_file", json={"path": "a.txt", "content": "x"}) r = c.post("/copy", json={"src": "a.txt", "dst": "b.txt"}) assert r.status_code == 200 assert r.json()["status"] == "COPIED" # source unchanged r = c.post("/read_file", json={"path": "a.txt"}) assert r.json()["content"] == "x" r = c.post("/read_file", json={"path": "b.txt"}) assert r.json()["content"] == "x"