Files
codex-app-server/cloud/tests/test_v2cov_server_handlers7.py
T

615 lines
23 KiB
Python

"""Coverage batch v2 (D7): cloud/server.py module-level helpers and
last small handler branches -- rate limiter, heartbeat, quota helpers,
plan-params parsing, kill requests, gate validation, thread guards,
oauth/github-app leftovers, todos/automation error mappings.
Discipline as before: the function under test is never stubbed.
"""
import json
import os
import sqlite3
import sys
import tempfile
import threading
import time
import unittest
from collections import deque
from pathlib import Path
from types import SimpleNamespace
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
from cloud.handler_fullauto import _full_auto_installation_verified, _full_auto_parse_plan_params
from cloud.server import (
CloudControlPlaneHandler,
RateLimiter,
_apply_default_quota,
_auth_store,
_backfill_default_quotas,
_classify_goal_followup,
_deployed_commit,
_default_executor,
_make_rate_limiter,
_read_agent_entries,
_read_agent_entries_multi,
_resolve_shared_home_mcp,
_run_host_cli,
_task_to_dict,
_upload_dir_for,
_validate_hard_gate_body,
_worker_heartbeat_status,
init_db,
request_kill,
start_rate_limit_sweeper,
sweep_rate_limit_hits,
DbRateLimiter,
)
from cloud.server import _auth_store as _auth_store_cm
def _handler(db_path, upload_dir=None, tenants_root=None):
h = CloudControlPlaneHandler.__new__(CloudControlPlaneHandler)
h.server = SimpleNamespace(db_path=db_path, upload_dir=upload_dir,
tenants_root=tenants_root)
sent = []
h._send_json = lambda status, obj, extra_headers=None: sent.append(
(status, obj))
html = []
h._send_html = lambda status, body: html.append((status, body))
h.sent = sent
h.html = html
return h
def _conn(db_path):
c = sqlite3.connect(db_path)
c.row_factory = sqlite3.Row
return c
class _Base(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "test.db")
init_db(self.db_path)
self.upload_dir = os.path.join(self.tmp.name, "uploads")
os.makedirs(self.upload_dir, exist_ok=True)
def tearDown(self):
self.tmp.cleanup()
def _h(self, body=None, err=None):
h = _handler(self.db_path, upload_dir=self.upload_dir,
tenants_root=self.tmp.name)
h._read_json = lambda: (body, err)
return h
def _project(self, pid="p1", owner="u1", repo_url=None):
c = _conn(self.db_path)
c.execute(
"INSERT INTO projects(id, name, owner_id, created_at,"
" repo_url, auto_pr) VALUES(?, ?, ?, ?, ?, 0)",
(pid, "P", owner, time.time(), repo_url))
c.commit()
c.close()
with _auth_store(self.db_path) as store:
store.create_project(pid, owner)
def _task(self, tid="t1", pid="p1", status="completed",
created_by="u1", **extra):
cols = ["id", "project_id", "prompt", "status", "created_by",
"created_at", "updated_at"]
vals = [tid, pid, "do work", status, created_by, time.time(),
time.time()]
for key, val in extra.items():
cols.append(key)
vals.append(val)
c = _conn(self.db_path)
c.execute(
"INSERT INTO tasks(%s) VALUES(%s)"
% (", ".join(cols), ", ".join("?" * len(cols))), vals)
c.commit()
c.close()
def _last(self, h):
self.assertTrue(h.sent, "handler sent no response")
return h.sent[-1]
class HelpersTestCase(_Base):
def test_run_host_cli_timeout(self):
fake = os.path.join(self.tmp.name, "host.py")
with open(fake, "w") as f:
f.write("# fake\n")
import subprocess as _sp
with mock.patch("cloud.server._host_py_path",
return_value=fake), \
mock.patch("cloud.server.subprocess.run",
side_effect=_sp.TimeoutExpired("host.py", 5)):
ok, err = _run_host_cli(["x"], "/tmp")
self.assertFalse(ok)
self.assertEqual(err, "host.py timed out")
def test_run_host_cli_os_error(self):
fake = os.path.join(self.tmp.name, "host.py")
with open(fake, "w") as f:
f.write("# fake\n")
with mock.patch("cloud.server._host_py_path",
return_value=fake), \
mock.patch("cloud.server.subprocess.run",
side_effect=OSError("no exec")):
ok, err = _run_host_cli(["x"], "/tmp")
self.assertFalse(ok)
self.assertIn("cannot run host.py", err)
def test_shared_home_tenants_root_raises(self):
with mock.patch("cloud.server._local_worker_codex_home",
return_value=None), \
mock.patch("cloud.server._tenants_root_for",
side_effect=RuntimeError("no root")):
self.assertIsNone(_resolve_shared_home_mcp(None))
def test_shared_home_tenants_root_empty(self):
with mock.patch("cloud.server._local_worker_codex_home",
return_value=None), \
mock.patch("cloud.server._tenants_root_for",
return_value=""):
self.assertIsNone(_resolve_shared_home_mcp(None))
def test_read_agent_entries_mixed_lines(self):
path = os.path.join(self.tmp.name, "agents.jsonl")
with open(path, "w") as f:
f.write("\n")
f.write("not json\n")
f.write("[1, 2]\n")
f.write('{"thread": "t", "nope": 1}\n')
f.write('{"thread": "t", "id": "a", "state": "start"}\n')
f.write('{"thread": "t", "id": "a", "state": "done"}\n')
entries = _read_agent_entries(path)
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0]["state"], "done")
self.assertEqual(_read_agent_entries(
os.path.join(self.tmp.name, "missing.jsonl")), [])
def test_read_agent_entries_multi_overlap(self):
p1 = os.path.join(self.tmp.name, "a1.jsonl")
p2 = os.path.join(self.tmp.name, "a2.jsonl")
with open(p1, "w") as f:
f.write('{"thread": "t", "id": "a", "v": 1}\n')
with open(p2, "w") as f:
f.write('{"thread": "t", "id": "a", "v": 2}\n')
entries = _read_agent_entries_multi([p1, p2])
self.assertEqual(len(entries), 1)
self.assertEqual(entries[0]["v"], 2)
def test_rate_limiter_key_cleanup(self):
limiter = RateLimiter(per_minute=100, window_seconds=60,
time_fn=lambda: 1000.0)
limiter._hits = {("k%d" % i): deque() for i in range(10001)}
self.assertTrue(limiter.allow("new"))
self.assertEqual(len(limiter._hits), 10001)
def test_db_limiter_ensure_twice(self):
limiter = DbRateLimiter(self.db_path)
c = _conn(self.db_path)
limiter._ensure_table(c)
limiter._ensure_table(c)
c.close()
self.assertTrue(limiter._ensured)
def test_db_limiter_ensure_inner_return(self):
# Inner early return needs a thread that passed the outer
# check, then blocks on the lock while the main thread flips
# _ensured: deterministic, no timing luck beyond the block.
limiter = DbRateLimiter(self.db_path)
limiter._ensured = False
c = _conn(self.db_path)
limiter._ensure_lock.acquire()
done = []
thread = threading.Thread(
target=lambda: done.append(limiter._ensure_table(c)))
thread.start()
time.sleep(0.2)
limiter._ensured = True
limiter._ensure_lock.release()
thread.join(timeout=5)
self.assertFalse(thread.is_alive())
c.close()
def test_make_rate_limiter_postgres(self):
with mock.patch.dict(os.environ, {"CLOUD_DB_BACKEND": "postgres"}):
limiter = _make_rate_limiter(self.db_path)
self.assertIsInstance(limiter, DbRateLimiter)
def test_sweep_postgres_no_lock(self):
conn = mock.Mock()
conn.execute.return_value.fetchone.return_value = (0,)
with mock.patch.dict(os.environ, {"CLOUD_DB_BACKEND": "postgres"}), \
mock.patch("cloud.server._db_connect",
return_value=conn):
self.assertFalse(sweep_rate_limit_hits(self.db_path, 60))
def test_sweeper_loop_runs_and_dies(self):
limiter = DbRateLimiter(self.db_path)
with mock.patch("time.sleep",
side_effect=[None, RuntimeError("stop")]), \
mock.patch("cloud.server.sweep_rate_limit_hits",
side_effect=RuntimeError("sweep down")):
thread = start_rate_limit_sweeper(limiter, self.db_path)
self.assertIsNotNone(thread)
thread.join(timeout=5)
self.assertFalse(thread.is_alive())
def test_deployed_commit_missing_file(self):
self.assertIsNone(_deployed_commit())
def test_deployed_commit_success_path(self):
# Success path credits 1088/1091 in coverage's line map;
# the file lives in the repo root only for this test.
root = Path(__file__).resolve().parents[2]
target = root / "DEPLOYED_COMMIT"
self.assertFalse(target.exists(),
"DEPLOYED_COMMIT must not pre-exist")
try:
target.write_text(" abc123\n", encoding="utf-8")
self.assertEqual(_deployed_commit(), "abc123")
finally:
target.unlink(missing_ok=True)
def test_worker_heartbeat_bad_override(self):
hb = os.path.join(self.tmp.name, "hb.json")
with open(hb, "w") as f:
json.dump({"last_seen": time.time()}, f)
server = SimpleNamespace(
db_path=None, worker_heartbeat_max_age_seconds="bogus",
worker_heartbeat_file=hb)
age, ok = _worker_heartbeat_status(server)
self.assertTrue(ok)
def test_worker_heartbeat_bad_env(self):
hb = os.path.join(self.tmp.name, "hb.json")
with open(hb, "w") as f:
json.dump({"last_seen": time.time()}, f)
server = SimpleNamespace(db_path=None, worker_heartbeat_file=hb)
with mock.patch.dict(
os.environ,
{"CLOUD_WORKER_HEARTBEAT_MAX_AGE_SECONDS": "bogus"}):
age, ok = _worker_heartbeat_status(server)
self.assertTrue(ok)
def test_upload_dir_fallback(self):
d = _upload_dir_for(SimpleNamespace())
self.assertIsInstance(d, str)
self.assertTrue(d)
def test_default_executor_local(self):
with mock.patch.dict(os.environ,
{"CLOUD_DEFAULT_EXECUTOR": "local"}):
self.assertEqual(_default_executor(), "local")
def test_apply_default_quota_commit_failure(self):
c = _conn(self.db_path)
class _CommitFail:
def __init__(self, real):
self._real = real
def execute(self, *a, **k):
return self._real.execute(*a, **k)
def commit(self):
raise RuntimeError("commit down")
def __getattr__(self, name):
return getattr(self._real, name)
self.assertTrue(_apply_default_quota(_CommitFail(c), "u1"))
c.close()
def test_installation_verified_config_none(self):
c = _conn(self.db_path)
with mock.patch("cloud.handler_fullauto.get_app_config",
return_value=None):
self.assertFalse(_full_auto_installation_verified(c))
c.close()
def test_installation_verified_config_error(self):
c = _conn(self.db_path)
with mock.patch("cloud.handler_fullauto.get_app_config",
side_effect=RuntimeError("config down")):
self.assertFalse(_full_auto_installation_verified(c))
c.close()
def test_installation_verified_repo(self):
c = _conn(self.db_path)
with mock.patch("cloud.handler_fullauto.get_app_config",
return_value={"id": 1}), \
mock.patch("cloud.handler_fullauto.find_installation_for_repo",
return_value={"id": 9}):
self.assertTrue(
_full_auto_installation_verified(c, "o/r"))
c.close()
def test_installation_verified_any(self):
c = _conn(self.db_path)
with mock.patch("cloud.handler_fullauto.get_app_config",
return_value={"id": 1}), \
mock.patch("cloud.handler_fullauto.list_installations",
return_value=[{"id": 9}]):
self.assertTrue(_full_auto_installation_verified(c))
c.close()
def test_plan_params_checks_string(self):
params, err = _full_auto_parse_plan_params(
{}, {"repo": ["o/r"], "required_checks": ["a, b"]})
self.assertIsNone(err)
self.assertEqual(params["required_checks"], ("a", "b"))
def test_plan_params_checks_list(self):
params, err = _full_auto_parse_plan_params(
{"repo": "o/r", "required_checks": ["x", "y"]}, {})
self.assertIsNone(err)
self.assertEqual(params["required_checks"], ("x", "y"))
def test_plan_params_checks_bad_type(self):
params, err = _full_auto_parse_plan_params(
{"repo": "o/r", "required_checks": 5}, {})
self.assertIsNone(params)
self.assertIn("must be a list or string", err)
def test_plan_params_org_bad_type(self):
params, err = _full_auto_parse_plan_params(
{"repo": "o/r", "org": 123}, {})
self.assertIsNone(params)
self.assertIn("org must be a string", err)
def test_backfill_rollback_failure_zero(self):
c = _conn(self.db_path)
class _Failing:
def __init__(self, real):
self._real = real
def execute(self, sql, params=()):
if "INSERT OR IGNORE INTO user_quotas" in sql:
raise RuntimeError("insert down")
return self._real.execute(sql, params)
def rollback(self):
raise RuntimeError("rollback down")
def __getattr__(self, name):
return getattr(self._real, name)
self.assertEqual(_backfill_default_quotas(_Failing(c)), 0)
c.close()
def test_auth_store_close_failure_swallowed(self):
class _FakeStore:
def __init__(self, db_path):
self.conn = mock.Mock()
self.conn.close.side_effect = RuntimeError("down")
with mock.patch("cloud.handler_support.AuthStore", _FakeStore):
with _auth_store_cm(self.db_path) as store:
self.assertIsNotNone(store)
def _row(self, depends_on):
return {
"id": "t1", "project_id": "p1", "prompt": "p",
"status": "completed", "idempotency_key": None,
"depends_on": depends_on, "priority": 0,
"created_by": "u1", "created_at": 1.0, "updated_at": 2.0,
"last_heartbeat": None,
}
def test_task_to_dict_bad_depends_on(self):
d = _task_to_dict(self._row("not json"))
self.assertEqual(d["depends_on"], [])
def test_task_to_dict_non_list_depends_on(self):
d = _task_to_dict(self._row('{"a": 1}'))
self.assertEqual(d["depends_on"], [{"a": 1}])
def test_request_kill_connect_failure(self):
with mock.patch("cloud.server._connect",
side_effect=RuntimeError("db down")):
self.assertFalse(request_kill(self.db_path, "t1", "u1"))
def test_request_kill_insert_failure(self):
c = _conn(self.db_path)
class _KillConn:
def __init__(self, real):
self._real = real
def execute(self, sql, params=()):
if "INSERT INTO kill_requests" in sql:
raise RuntimeError("insert down")
return self._real.execute(sql, params)
def rollback(self):
raise RuntimeError("rollback down")
def __getattr__(self, name):
return getattr(self._real, name)
with mock.patch("cloud.server._connect",
return_value=_KillConn(c)):
self.assertFalse(request_kill(self.db_path, "t1", "u1"))
c.close()
def test_gate_body_bad_entry(self):
self.assertIn("ci_checks entries must be objects",
_validate_hard_gate_body(
{"ci_checks": [{"name": 1}]}))
def test_gate_body_bad_bool(self):
self.assertIn("must be a boolean",
_validate_hard_gate_body({"contract_frozen": "x"}))
def test_gate_body_bad_version(self):
self.assertIn("must be a string",
_validate_hard_gate_body({"contract_version": 3}))
def test_gate_body_bad_expected(self):
self.assertIn("must be a string",
_validate_hard_gate_body(
{"contract_expected_version": 4}))
def test_classify_goal_followup_paused(self):
action, code, payload = _classify_goal_followup(
{"status": "paused"}, "做目标")
self.assertEqual(action, "error")
self.assertEqual(code, 409)
class HandlerLeftoversTestCase(_Base):
def test_kb_index_close_failure_swallowed(self):
h = self._h(body={"path": "a.md", "content": "hi"})
class _Store:
def __init__(self, db_path):
self.conn = mock.Mock()
self.conn.close.side_effect = RuntimeError("down")
def index_file(self, project_id, path, content, kind="file"):
return "d1"
with mock.patch("cloud.handler_identity.KBStore", _Store):
h._kb_index("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 201)
self.assertEqual(payload["id"], "d1")
def test_create_followup_read_error(self):
self._project()
self._task()
h = self._h(err="bad json")
h._create_followup("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_thread_guard_cloud(self):
h = self._h()
code, msg = h._thread_task_guard({"executor": "cloud"})
self.assertEqual(code, 409)
def test_thread_mutate_resolve_error(self):
self._project()
self._task()
h = self._h()
h._resolve_task_thread = lambda task, **k: (
None, None, None, (404, "no thread"), None)
h._task_thread_mutate("u1", "t1", ["archive"],
"thread_archived", "archived %s")
self.assertEqual(self._last(h)[0], 404)
def test_mcp_oauth_login_home_none(self):
h = self._h(body={})
h._mcp_server_oauth_login("u1", None, "srv")
code, payload = self._last(h)
self.assertEqual(code, 502)
self.assertIn("cannot resolve tenant codex-home",
payload["error"])
def test_thread_read_no_thread_note(self):
self._project()
self._task()
h = self._h()
h._resolve_task_thread = lambda task, **k: (
{"id": "th1"}, "/tmp", "/tmp", None, None)
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")):
h._task_thread_read("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertIn("no thread", str(payload))
def test_release_hard_gate_error(self):
self._project()
self._task(status="merged")
h = self._h(body={"files_changed": [], "ci_checks": "x"})
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1"}):
h._release_task("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("must be a list", payload["error"])
def test_coordinator_reserve_single_part(self):
h = self._h()
self.assertTrue(h._check_coordinator_reserve("u1", 1))
def test_oauth_login_close_failure_swallowed(self):
from cloud.handler_oauth import OAuthStateStore as _RealStore
class _ClosingProxy:
def __init__(self, real):
self._real = real
def close(self):
raise RuntimeError("close down")
def __getattr__(self, name):
return getattr(self._real, name)
class _Store(_RealStore):
def __init__(self, db_path):
super().__init__(db_path)
self.conn = _ClosingProxy(self.conn)
h = self._h()
h.path = ("/oauth/github/login?client_id=c"
"&redirect_uri=https://x.test/cb")
with mock.patch("cloud.handler_oauth.OAuthStateStore", _Store):
h._oauth_login("/oauth/github/login")
# Either 200 (login started) or 400 (no client config): both
# pass through the close-failure swallow in the finally.
self.assertIn(self._last(h)[0], (200, 400))
def test_oauth_bind_initiate_read_error(self):
h = self._h(err="bad json")
h._authenticate = lambda: "u1"
h._oauth_bind_initiate("github")
self.assertEqual(self._last(h)[0], 400)
def test_github_app_setup_page_org_action(self):
from cloud.github_app import SetupStateStore
store = SetupStateStore(self.db_path)
try:
state = store.create_state(issued_by="u1")
finally:
store.conn.close()
h = self._h()
h.path = ("/github-app/setup?state=%s&org=acme" % state)
with mock.patch.dict(
os.environ,
{"GITHUB_APP_PUBLIC_BASE": "https://api.example.test"}):
h._github_app_setup_page()
self.assertEqual(h.html[0][0], 200)
self.assertIn("organizations/acme", h.html[0][1])
def test_todos_create_read_error(self):
h = self._h(err="bad json")
h._todos_create("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_todos_delete_ghost_404(self):
h = self._h()
h._todos_delete("u1", "p1", "ghost")
code, payload = self._last(h)
self.assertEqual(code, 404)
self.assertIn("todo not found", payload["error"])
def test_automation_update_generic_failure_502(self):
h = self._h(body={"name": "new"})
class _FailAll:
def __init__(self, real):
self._real = real
def execute(self, *a, **k):
raise RuntimeError("db down")
def __getattr__(self, name):
return getattr(self._real, name)
h._db = lambda: _FailAll(_conn(self.db_path))
h._automation_update("u1", "p1", "a1")
self.assertEqual(self._last(h)[0], 502)