Files
codex-app-server/deploy/tests/test_coverage85_verify_multiuser.py
T
host 576fd50ee3 coverage85-step2a: MCP/deploy/verify-multiuser 剩余缺口真实行为测试(+43 项)
- playwright/imagegen/websearch 三个 MCP:handle_request 全工具分发、
  通知/错误分支、main() 进程内 stdio 循环(pipe/StringIO + 假浏
  览器/假依赖)、LineReader 分块与 EOF、真实 RemoteBridge 构造、
  flat-copy 回落导入;三文件孤立覆盖率 98%(余 __main__ 与
  通知型 handler-error else 分支,见回执)。
- deploy.py:check_clean_tree 脏/暂存/放行、archive 失败、remote
  助手、transfer 全路失败与 sha 不符、git 源冲突与干跑、tar
  路径干跑/构建/部署成功与失败;孤立 92%(余行由既有 deploy
  测试覆盖,__main__ 除外)。
- verify-multiuser main():真实本地控制面 13/13 通过且清理归零、
  死端口与 stub 项目失败早退、清理触发器异常报 leftover。
生产代码零改动。
2026-11-03 02:48:54 +00:00

174 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""覆盖率 78%->85% 第 2 步:deploy/verify-multiuser.py 的 main()。
main() 是对真实控制面的多用户验证:本文件用本地临时 SQLite +
本地临时端口的真实控制面(test_server.py 同款 create_server)跑
完整流程,绝不连生产、不建生产任务;失败分支用本地 stub HTTP
服务与死端口驱动。临时库里的 multiuser-test-* 行跑完必须清零。
"""
import http.server
import importlib.util
import io
import json
import os
import sqlite3
import sys
import tempfile
import threading
import unittest
from contextlib import redirect_stdout
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
_SPEC = importlib.util.spec_from_file_location(
"verify_multiuser",
os.path.join(REPO_ROOT, "deploy", "verify-multiuser.py"))
vm = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(vm)
from cloud.server import create_server, init_db # noqa: E402
class StubHandler(http.server.BaseHTTPRequestHandler):
mode = "wrong-id"
def _send(self, code, body):
data = json.dumps(body).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def do_POST(self):
length = int(self.headers.get("Content-Length") or 0)
self.rfile.read(length)
if self.mode == "wrong-id":
self._send(201, {"id": "wrong"})
else:
self._send(500, {"error": "boom"})
def do_GET(self):
self._send(404, {"error": "not found"})
def log_message(self, *args):
pass
def _start_stub(mode):
StubHandler.mode = mode
srv = http.server.HTTPServer(("127.0.0.1", 0), StubHandler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
return srv
class VerifyMultiuserMainTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self._old_argv = sys.argv
def tearDown(self):
sys.argv = self._old_argv
self.tmp.cleanup()
def _run_main(self, base):
vm.RESULTS.clear()
sys.argv = ["verify-multiuser.py", "--base", base,
"--db", self.db_path]
out = io.StringIO()
with redirect_stdout(out):
rc = vm.main()
return rc, out.getvalue(), list(vm.RESULTS)
def _leftover_counts(self):
conn = sqlite3.connect(self.db_path)
try:
counts = {}
for table, col in (("users", "username"),
("projects", "id"),
("tasks", "project_id")):
counts[table] = conn.execute(
"SELECT COUNT(*) FROM %s WHERE %s LIKE ?"
% (table, col), ("multiuser-test-%",)).fetchone()[0]
return counts
finally:
conn.close()
def test_happy_path_against_real_local_server(self):
server = create_server(self.db_path, "127.0.0.1", 0)
port = server.server_address[1]
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
rc, out, results = self._run_main(
"http://127.0.0.1:%d" % port)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
self.assertEqual(rc, 0, out)
self.assertEqual(len(results), 13, results)
self.assertTrue(all(ok for _, ok in results), results)
self.assertIn("13/13", out)
self.assertEqual(self._leftover_counts(),
{"users": 0, "projects": 0, "tasks": 0})
def test_dead_base_fails_early(self):
init_db(self.db_path)
rc, out, results = self._run_main("http://127.0.0.1:1")
self.assertEqual(rc, 1)
self.assertEqual(len(results), 2)
self.assertFalse(any(ok for _, ok in results))
self.assertIn("项目创建失败", out)
self.assertEqual(self._leftover_counts(),
{"users": 0, "projects": 0, "tasks": 0})
def test_cleanup_error_reports_leftover(self):
server = create_server(self.db_path, "127.0.0.1", 0)
port = server.server_address[1]
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
# 删用户时触发器直接 ABORT:清理抛错 -> leftover -> 返回 1,
# 但 13 项检查本身全部通过(区分“检查失败”与“清理失败”)。
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"CREATE TRIGGER no_user_delete BEFORE DELETE ON users "
"BEGIN SELECT RAISE(ABORT, 'trigger blocks delete'); END")
conn.commit()
finally:
conn.close()
rc, out, results = self._run_main(
"http://127.0.0.1:%d" % port)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
self.assertEqual(rc, 1)
self.assertTrue(all(ok for _, ok in results), results)
self.assertIn("清理未完成", out)
self.assertIn("cleanup-error", out)
def test_stub_project_responses_fail_early(self):
init_db(self.db_path)
for mode, want in [("wrong-id", [True, True]),
("error-500", [False, False])]:
with self.subTest(mode=mode):
stub = _start_stub(mode)
try:
rc, out, results = self._run_main(
"http://127.0.0.1:%d" % stub.server_address[1])
finally:
stub.shutdown()
stub.server_close()
self.assertEqual(rc, 1)
self.assertEqual([ok for _, ok in results], want)
self.assertIn("项目创建失败", out)
if __name__ == "__main__":
unittest.main()