Files
codex-app-server/deploy/tests/test_coverage85_verify_multiuser.py
host 4cd9b5eaa4 coverage85-step3: 运行器统一加根路径,tests/ E402 清零
新增 run_tests.py:三套件统一入口,只加仓库根(deploy 套件
再加 deploy/ 目录在根之前),各套件走独立子进程;verify 作
业改走运行器,coverage 作业因子进程测不到覆盖率保持原
discover 命令。tests/ 13 个文件的 sys.path 前置 import 全部
改回正常 import(含 test_host_trace 的 try/except 回落);
deploy 的 deploy 目录插入与 verify-multiuser 的 spec 加载按
陷阱 2 如实保留(仅删 verify-multiuser 冗余的根插入)。
.coveragerc omit 加 */run_tests.py,使新运行器文件不计入
生产覆盖率。门槛未动(仍 75%),未补新测试。
2026-11-03 02:48:54 +00:00

174 lines
6.1 KiB
Python
Raw Permalink 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]
_SPEC = importlib.util.spec_from_file_location(
"verify_multiuser",
os.path.join(REPO_ROOT, "deploy", "verify-multiuser.py"))
assert _SPEC is not None and _SPEC.loader is not None
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()