test: code-to10 v2 coverage additions (14 files, production code unchanged)

(cherry picked from commit 473338c35d59bfd50929542e7f08c0a4a22c5987)
This commit is contained in:
host committed 2026-11-04 21:03:03 +00:00
1 parent 2032043b60
commit 3a72ed3847
14 files changed
+8249

No files matched your search

+150
View File
@@ -0,0 +1,150 @@
"""Coverage batch v2 (E2): cloud/marker_tools.py pure helpers --
timezone fallback, first-run parsing, int coercion, spawn-default
parsing, heartbeat/progress formatting, todo/task lines.
Safety buffer on top of E1; all direct calls.
"""
import sys
import time
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
from cloud.marker_tools import (
_as_int,
_heartbeat_age_text,
_model_from_match,
_progress_lines,
_shanghai_tz,
format_snapshot,
format_task,
format_todo,
parse_first_run_at,
parse_spawn_defaults,
row_get,
)
class FakeMatch:
def __init__(self, token):
self._token = token
def group(self, n):
return self._token
def end(self, n):
return len(self._token)
class MarkerPureTestCase(unittest.TestCase):
def test_shanghai_tz_zoneinfo_raises(self):
with mock.patch("cloud.marker_tools.ZoneInfo",
side_effect=Exception("no tz")):
tz = _shanghai_tz()
self.assertEqual(tz.utcoffset(None).total_seconds(), 8 * 3600)
def test_shanghai_tz_no_zoneinfo(self):
with mock.patch("cloud.marker_tools.ZoneInfo", None):
tz = _shanghai_tz()
self.assertEqual(tz.utcoffset(None).total_seconds(), 8 * 3600)
def test_first_run_blank(self):
self.assertIsNone(parse_first_run_at(" "))
def test_first_run_negative(self):
self.assertIsNone(parse_first_run_at(-5))
def test_first_run_zero(self):
self.assertIsNone(parse_first_run_at(0))
def test_first_run_valid(self):
self.assertEqual(parse_first_run_at(1700000000), 1700000000.0)
self.assertIsNotNone(parse_first_run_at("2026-01-01T00:00:00Z"))
def test_row_get_fallback(self):
self.assertIsNone(row_get(None, "x"))
self.assertIsNone(row_get({}, "missing"))
def test_as_int_bool_and_float(self):
self.assertEqual(_as_int(True, 7), 7)
self.assertEqual(_as_int(3.5, 7), 7)
self.assertEqual(_as_int(3.0, 7), 3)
self.assertEqual(_as_int(4, 7), 4)
def test_model_from_match_empty_token(self):
self.assertEqual(_model_from_match("x", FakeMatch("."), 1),
(None, None))
def test_model_from_match_effort_word(self):
self.assertEqual(_model_from_match("x", FakeMatch("max"), 1),
(None, "max"))
def test_spawn_defaults_empty(self):
self.assertEqual(parse_spawn_defaults(""), {})
self.assertEqual(parse_spawn_defaults(None), {})
def test_spawn_defaults_zh(self):
out = parse_spawn_defaults("子任务用 gpt-6-luna max")
self.assertEqual(out.get("model"), "gpt-6-luna")
def test_heartbeat_none(self):
self.assertEqual(_heartbeat_age_text({}), "")
def test_heartbeat_bad(self):
self.assertEqual(
_heartbeat_age_text({"last_heartbeat": "bad"}), "")
def test_heartbeat_future(self):
now = time.time()
self.assertEqual(
_heartbeat_age_text({"last_heartbeat": now + 100}, now=now),
"0 秒前")
def test_heartbeat_seconds(self):
now = time.time()
self.assertEqual(
_heartbeat_age_text({"last_heartbeat": now - 30}, now=now),
"30 秒前")
def test_progress_lines_full(self):
now = time.time()
lines = _progress_lines(
{"executor": "docker", "actual_model": "m1",
"worktree_host": "h1", "last_heartbeat": now - 5,
"turn_id": "t9"}, now=now)
self.assertEqual(len(lines), 5)
def test_snapshot_terminal_empty(self):
text = format_snapshot(
{"id": "t1", "status": "completed", "result_text": "",
"error_text": ""}, 100)
self.assertIn("结果:(空)", text)
def test_snapshot_timed_out(self):
text = format_snapshot(
{"id": "t1", "status": "running", "result_text": "",
"error_text": ""}, 100, timed_out=True)
self.assertIn("等待超时", text)
def test_snapshot_cancelled(self):
text = format_snapshot(
{"id": "t1", "status": "running", "result_text": "",
"error_text": ""}, 100, cancelled=True)
self.assertIn("被取消", text)
def test_todo_bad_due_and_task(self):
line = format_todo({"status": "open", "title": "t", "id": "d1",
"priority": "high", "due_at": "bad",
"task_id": "t1"})
self.assertIn("task_id=t1", line)
self.assertNotIn("due=", line)
def test_task_bad_created(self):
line = format_task({"status": "completed", "id": "t1",
"created_by": "u1", "prompt": "do it",
"created_at": "bad"})
self.assertIn("do it", line)
self.assertNotIn("created=", line)
+407
View File
@@ -0,0 +1,407 @@
"""Coverage batch v2: cloud/audit_scorecard.py branch sweep.
The baseline left 55 lines uncovered, almost all of them the
second/third branches of the seven _score_* scorers (caps, unknown
returns, untested kinds) plus the threshold-source fallbacks. These
tests drive every branch with real evidence dicts and assert the
pinned scores, statuses and overall rule (average is never the pass
criterion). Threshold values used here are the module's own test
defaults; nothing here presents them as production-approved.
"""
import unittest
from cloud.audit_scorecard import (
DEFAULT_THRESHOLDS,
build_scorecard,
example_scorecard_current_state,
_result,
_round1,
_score_higher,
_score_lower,
_t,
_thresholds_source,
)
def _delivery(**over):
ev = {
"first_pass_rate": 0.97, "defect_rate": 0.01, "rework_rate": 0.02,
"first_pass_known": True, "defect_known": True,
"rework_known": True, "attempts_present": True,
"evidence_kind": "real",
}
ev.update(over)
return ev
def _gate(**over):
ev = {
"hard_gate_enforced": True, "gate_items_passed": 100,
"gate_items_total": 100, "merge_blocked_verified": True,
"loop_end_to_end_verified": True,
"sampling_misjudgment_rate": 0.0, "evidence_kind": "real",
}
ev.update(over)
return ev
def _arch(**over):
ev = {
"contract_frozen": True, "arch_violations": 0,
"directory_violations": 0, "import_violations": 0,
"stack_changed_without_approval": False,
}
ev.update(over)
return ev
def _test(**over):
ev = {
"prd_coverage_rate": 1.0, "cross_matrix_coverage_rate": 1.0,
"coverage_pct": 0.95, "cross_matrix_kind": "real",
"real_pg": True, "real_service": True,
"agent_six_complete": True, "lint_items": 0, "mypy_items": 0,
"bandit_high": 0,
}
ev.update(over)
return ev
def _security(**over):
ev = {
"ledger_verify_ok": True, "ledger_coverage_rate": 1.0,
"shell_whitelist_pass_rate": 1.0, "branch_protection": "protected",
"anchor_check_recent": True,
}
ev.update(over)
return ev
def _cost(**over):
ev = {
"cost_records_complete": True, "unpriced_as_zero": False,
"per_creator_present": True, "quota_cutoff_verified": True,
}
ev.update(over)
return ev
def _ops(**over):
ev = {
"ci_green_rate": 1.0, "deploy_success_rate": 1.0,
"rollback_drill_done": True, "ha_status": "second_host",
"parallel_scale_proven": 40,
}
ev.update(over)
return ev
def _all_good():
return {
"delivery_quality": _delivery(),
"gate_audit": _gate(),
"arch_contract": _arch(),
"test_coverage": _test(),
"security_ledger": _security(),
"cost_quota": _cost(),
"ops_scale": _ops(),
}
class HelpersTestCase(unittest.TestCase):
def test_thresholds_source_branches(self):
self.assertEqual(_thresholds_source(None), "test_default_unapproved")
self.assertEqual(_thresholds_source({}), "test_default_unapproved")
approved = {"a": {"value": 1, "source": "user_approved"}}
self.assertEqual(_thresholds_source(approved), "user_approved")
# Non-dict entry: falls into the else branch, and a lone
# non-approved source falls through to the final default.
self.assertEqual(_thresholds_source({"a": 5}), "test_default_unapproved")
mixed = {"a": {"value": 1, "source": "user_approved"},
"b": {"value": 2, "source": "test_default_unapproved"}}
self.assertEqual(_thresholds_source(mixed), "test_default_unapproved")
def test_t_branches(self):
self.assertEqual(_t(None, "coverage_nine"),
DEFAULT_THRESHOLDS["coverage_nine"]["value"])
self.assertEqual(_t({"coverage_nine": {"value": 0.77}},
"coverage_nine"), 0.77)
self.assertEqual(_t({"coverage_nine": 0.66}, "coverage_nine"), 0.66)
self.assertEqual(_t({"other": 1}, "coverage_nine"),
DEFAULT_THRESHOLDS["coverage_nine"]["value"])
def test_score_higher_segments(self):
self.assertIsNone(_score_higher(None, 0.9, 0.95, 0.5))
self.assertEqual(_score_higher(1.0, 0.9, 0.95, 0.5), 10.0)
self.assertAlmostEqual(_score_higher(0.92, 0.9, 0.95, 0.5), 9.4)
self.assertAlmostEqual(_score_higher(0.7, 0.9, 0.95, 0.5), 7.0)
self.assertAlmostEqual(_score_higher(0.2, 0.9, 0.95, 0.5), 2.0)
self.assertEqual(_score_higher(-1.0, 0.9, 0.95, 0.0), 0.0)
# Degenerate bands still return a number instead of dividing
# by zero; the floor-band edge (v == floor) scores exactly 5.
self.assertEqual(_score_higher(0.9, 0.9, 0.9, 0.5), 10.0)
self.assertEqual(_score_higher(0.5, 0.9, 0.95, 0.5), 5.0)
def test_score_lower_segments(self):
self.assertIsNone(_score_lower(None, 0.02, 0.05, 0.20))
self.assertEqual(_score_lower(0.01, 0.02, 0.05, 0.20), 10.0)
self.assertAlmostEqual(_score_lower(0.03, 0.02, 0.05, 0.20),
9.0 + (0.05 - 0.03) / 0.03)
self.assertAlmostEqual(_score_lower(0.10, 0.02, 0.05, 0.20),
5.0 + 4.0 * (0.20 - 0.10) / 0.15)
self.assertAlmostEqual(_score_lower(0.40, 0.02, 0.05, 0.20), 2.5)
self.assertEqual(_score_lower(0.02, 0.05, 0.05, 0.20), 10.0)
self.assertEqual(_score_lower(0.05, 0.02, 0.05, 0.20), 9.0)
def test_result_statuses_and_round1(self):
self.assertEqual(_result("x", None, [], ["m"], [])["status"], "unknown")
self.assertEqual(_result("x", 9.0, [], [], [])["status"], "pass")
self.assertEqual(_result("x", 4.0, [], [], [])["status"], "fail")
self.assertEqual(_round1(9.04), 9.0)
class DeliveryTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"delivery_quality": _delivery(**over)},
{})["dimensions"][0]
def test_happy(self):
dim = self._score()
self.assertEqual(dim["score"], 10.0)
self.assertEqual(dim["status"], "pass")
def test_missing_and_untested(self):
dim = self._score(first_pass_rate=None, defect_known=False,
attempts_present=False)
self.assertIsNone(dim["score"])
self.assertIn("first_pass_rate", dim["missing_items"])
self.assertIn("defect_known_not_known", dim["missing_items"])
self.assertIn("attempts_present_false_or_missing",
dim["missing_items"])
dim = self._score(evidence_kind="fixture")
self.assertIsNone(dim["score"])
self.assertTrue(any("fixture" in u for u in dim["untested_items"]))
dim = self._score(evidence_kind="estimated")
self.assertIsNone(dim["score"])
def test_mid_scores(self):
dim = self._score(first_pass_rate=0.7, defect_rate=0.10,
rework_rate=0.30)
self.assertLess(dim["score"], 9.0)
self.assertEqual(dim["status"], "fail")
class GateTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"gate_audit": _gate(**over)},
{})["dimensions"][1]
def test_happy(self):
self.assertEqual(self._score()["score"], 10.0)
def test_unknown_and_conditional(self):
dim = self._score(hard_gate_enforced=None)
self.assertIsNone(dim["score"])
self.assertIn("hard_gate_enforced_unknown", dim["missing_items"])
dim = self._score(gate_items_passed=None)
self.assertIsNone(dim["score"])
dim = self._score(audit_verdict="conditional")
self.assertIsNone(dim["score"])
self.assertTrue(any("conditional" in u
for u in dim["untested_items"]))
dim = self._score(evidence_kind="dry_run")
self.assertIsNone(dim["score"])
def test_caps(self):
self.assertEqual(self._score(hard_gate_enforced=False)["score"], 4.0)
self.assertEqual(self._score(merge_blocked_verified=False)["score"], 4.0)
self.assertEqual(self._score(loop_end_to_end_verified=False)["score"], 4.0)
def test_misjudgment_unknown_and_scored(self):
dim = self._score(sampling_misjudgment_rate=None)
self.assertIsNone(dim["score"])
self.assertIn("sampling_misjudgment_rate_unknown",
dim["missing_items"])
dim = self._score(sampling_misjudgment_rate=0.10)
self.assertLess(dim["score"], 10.0)
dim = self._score(gate_items_total=0, gate_items_passed=0,
sampling_misjudgment_rate=0.0)
self.assertEqual(dim["score"], 0.0)
class ArchTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"arch_contract": _arch(**over)},
{})["dimensions"][2]
def test_happy_and_unknown(self):
self.assertEqual(self._score()["score"], 10.0)
dim = self._score(arch_violations=None, contract_frozen=None)
self.assertIsNone(dim["score"])
self.assertIn("arch_violations_unknown", dim["missing_items"])
self.assertIn("contract_frozen_unknown", dim["missing_items"])
def test_caps(self):
self.assertEqual(self._score(contract_frozen=False)["score"], 4.0)
self.assertEqual(
self._score(stack_changed_without_approval=True)["score"], 4.0)
self.assertEqual(self._score(arch_violations=2)["score"], 5.0)
self.assertEqual(self._score(directory_violations=1)["score"], 5.0)
class TestDimTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"test_coverage": _test(**over)},
{})["dimensions"][3]
def test_happy(self):
self.assertEqual(self._score()["score"], 10.0)
def test_unknown_and_untested(self):
dim = self._score(prd_coverage_rate=None)
self.assertIsNone(dim["score"])
dim = self._score(cross_matrix_kind="viewport_substitute",
real_pg=True, real_service=True)
self.assertIsNone(dim["score"])
self.assertTrue(any("viewport_substitute" in u
for u in dim["untested_items"]))
self.assertTrue(any("cross_matrix_not_real" in u
for u in dim["untested_items"]))
dim = self._score(cross_matrix_kind="code_review_only")
self.assertIsNone(dim["score"])
dim = self._score(real_pg=False, real_service=True)
self.assertIsNone(dim["score"])
dim = self._score(real_service=False, real_pg=True)
self.assertIsNone(dim["score"])
def test_agent_six_cap_and_static(self):
self.assertEqual(self._score(agent_six_complete=False)["score"], 4.0)
dim = self._score(lint_items=None)
self.assertIsNone(dim["score"])
self.assertIn("lint_items_missing", dim["missing_items"])
dim = self._score(lint_items=3)
self.assertEqual(dim["score"], 8.0)
dim = self._score(bandit_high=1)
self.assertEqual(dim["score"], 8.0)
class SecurityTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"security_ledger": _security(**over)},
{})["dimensions"][4]
def test_happy_and_unknown(self):
self.assertEqual(self._score()["score"], 10.0)
dim = self._score(ledger_verify_ok=None, branch_protection="unknown")
self.assertIsNone(dim["score"])
self.assertIn("ledger_verify_ok_unknown", dim["missing_items"])
self.assertIn("branch_protection_unknown", dim["missing_items"])
dim = self._score(ledger_coverage_rate=None, branch_protection=None)
self.assertIsNone(dim["score"])
def test_caps(self):
self.assertEqual(self._score(branch_protection="unprotected")["score"], 4.0)
self.assertEqual(self._score(ledger_verify_ok=False)["score"], 4.0)
self.assertEqual(self._score(anchor_check_recent=False)["score"], 8.0)
def test_no_whitelist_rate(self):
dim = self._score(shell_whitelist_pass_rate=None)
self.assertEqual(dim["score"], 10.0)
class CostTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"cost_quota": _cost(**over)},
{})["dimensions"][5]
def test_happy_and_unknown(self):
self.assertEqual(self._score()["score"], 10.0)
dim = self._score(cost_records_complete=False)
self.assertIsNone(dim["score"])
dim = self._score(unpriced_as_zero=None)
self.assertIsNone(dim["score"])
def test_caps(self):
self.assertEqual(self._score(unpriced_as_zero=True)["score"], 0.0)
self.assertEqual(self._score(quota_cutoff_verified=False)["score"], 4.0)
self.assertEqual(self._score(per_creator_present=False)["score"], 8.0)
class OpsTestCase(unittest.TestCase):
def _score(self, **over):
return build_scorecard({"ops_scale": _ops(**over)},
{})["dimensions"][6]
def test_happy_and_unknown(self):
self.assertEqual(self._score()["score"], 10.0)
dim = self._score(ci_green_rate=None, rollback_drill_done=None,
ha_status=None)
self.assertIsNone(dim["score"])
self.assertTrue(any(m.startswith("ci_green_rate_unknown")
for m in dim["missing_items"]))
self.assertIn("rollback_drill_done_unknown", dim["missing_items"])
self.assertIn("ha_status_unknown", dim["missing_items"])
def test_caps_and_scale(self):
self.assertEqual(self._score(rollback_drill_done=False)["score"], 4.0)
self.assertEqual(
self._score(ha_status="single_host_unaccepted")["score"], 5.0)
dim = self._score(parallel_scale_proven=None)
self.assertIsNone(dim["score"])
self.assertIn("parallel_scale_proven_unknown", dim["missing_items"])
dim = self._score(parallel_scale_proven=None,
parallel_scale_accepted_alternative=True)
self.assertEqual(dim["score"], 10.0)
dim = self._score(parallel_scale_proven=18)
self.assertEqual(dim["score"], 4.5)
dim = self._score(deploy_success_rate=None)
self.assertEqual(dim["score"], 10.0)
dim = self._score(ha_status="single_host_accepted",
parallel_scale_proven=40)
self.assertEqual(dim["score"], 10.0)
class OverallTestCase(unittest.TestCase):
def test_all_pass(self):
card = build_scorecard(_all_good())
self.assertEqual(card["overall"], "pass")
self.assertTrue(card["overall_pass"])
self.assertEqual(card["average_score"], 10.0)
self.assertTrue(card["average_is_not_pass_criterion"])
self.assertEqual(card["thresholds_source"], "test_default_unapproved")
self.assertEqual(set(card["thresholds"].keys()),
set(DEFAULT_THRESHOLDS.keys()))
def test_average_is_not_pass_criterion(self):
# Six dims at 10, one at 4: average 9.1 but overall not pass.
evidence = _all_good()
evidence["cost_quota"] = _cost(quota_cutoff_verified=False)
card = build_scorecard(evidence)
self.assertGreaterEqual(card["average_score"], 9.0)
self.assertEqual(card["overall"], "not_pass")
self.assertFalse(card["overall_pass"])
def test_empty_evidence(self):
card = build_scorecard({})
self.assertEqual(card["overall"], "not_pass")
self.assertIsNone(card["average_score"])
self.assertTrue(all(d["score"] is None for d in card["dimensions"]))
self.assertGreater(len(card["missing_items"]), 0)
card_none = build_scorecard(None)
self.assertEqual(card_none["overall"], "not_pass")
def test_user_approved_thresholds_source(self):
approved = {k: {"value": v["value"], "source": "user_approved"}
for k, v in DEFAULT_THRESHOLDS.items()}
card = build_scorecard(_all_good(), approved)
self.assertEqual(card["thresholds_source"], "user_approved")
def test_example_current_state_is_not_pass(self):
card = example_scorecard_current_state()
self.assertEqual(card["overall"], "not_pass")
self.assertTrue(card["missing_items"] or card["untested_items"])
if __name__ == "__main__":
unittest.main()
+553
View File
@@ -0,0 +1,553 @@
"""Coverage batch v2: cloud/server.py handler branches.
Covers the baseline gaps in _full_auto_branch_protection_ok,
_full_auto_github_request, _admin_cost_by_creator,
_workers_heartbeat, _admin_backup and _task_trace_stream.
Stubbing discipline: the method under test is never stubbed. Its
sinks and predicate collaborators are: _send_json (captured output),
_read_json (request body), _is_global_admin via the real
CODEX_GLOBAL_ADMINS env, _user_exists via a real users row,
_cost_by_creator_payload (fixed payload / ValueError for the month
branch), _registry_heartbeat (ValueError case only), backup_postgres
(a real temp file, pg_dump itself not run), _fetch_task /
_trace_snapshot / _auth_store for the SSE stream (the stream loop
and emit-failure logic under test stays real).
"""
import io
import json
import os
import sqlite3
import sys
import tempfile
import time
import unittest
import urllib.error
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))
import cloud.server as server_mod
from cloud.server import (
CloudControlPlaneHandler,
FullAutoError,
_full_auto_branch_protection_ok,
_full_auto_github_request,
init_db,
)
def _handler(db_path):
h = CloudControlPlaneHandler.__new__(CloudControlPlaneHandler)
h.server = SimpleNamespace(db_path=db_path)
sent = []
h._send_json = lambda status, obj, extra_headers=None: sent.append(
(status, obj))
h.sent = sent
return h
def _task_row(status="running", task_id="T1"):
return {
"id": task_id, "project_id": "p1", "prompt": "do work",
"status": status, "idempotency_key": None, "depends_on": "[]",
"priority": 0, "created_by": "tester", "created_at": 0.0,
"updated_at": 0.0, "last_heartbeat": None, "result_text": None,
"result_text_full": None, "error_text": None,
"actual_model": None, "started_at": None, "finished_at": None,
}
class FullAutoHelpersTestCase(unittest.TestCase):
def _valid_protection(self, checks=("verify",)):
return {
"required_status_checks": {"strict": True,
"contexts": list(checks)},
"enforce_admins": True,
"required_pull_request_reviews": {
"required_approving_review_count": 1},
"allow_force_pushes": False,
"allow_deletions": False,
}
def test_branch_protection_ok(self):
self.assertTrue(_full_auto_branch_protection_ok(
self._valid_protection(), ["verify"]))
self.assertTrue(_full_auto_branch_protection_ok(
self._valid_protection(("verify", "lint")),
["verify"]))
def test_branch_protection_failures(self):
self.assertFalse(_full_auto_branch_protection_ok(
"not-a-dict", ["verify"]))
self.assertFalse(_full_auto_branch_protection_ok(
self._valid_protection(), []))
bad = self._valid_protection()
bad["required_status_checks"] = {"strict": False,
"contexts": ["verify"]}
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["required_status_checks"] = ["verify"]
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["required_status_checks"] = {"strict": True,
"contexts": ["other"]}
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["enforce_admins"] = False
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["required_pull_request_reviews"] = None
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["allow_force_pushes"] = True
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
bad = self._valid_protection()
bad["allow_deletions"] = True
self.assertFalse(_full_auto_branch_protection_ok(bad, ["verify"]))
def test_github_request_fake_client(self):
def fake(method, url, headers, body):
return 201, {"ok": True}
self.assertEqual(_full_auto_github_request(
fake, "POST", "https://example.invalid/x", {}, None),
(201, {"ok": True}))
with self.assertRaises(FullAutoError):
_full_auto_github_request(
lambda m, u, h, b: {"not": "a-tuple"},
"GET", "https://example.invalid/x", {}, None)
def test_github_request_urllib_paths(self):
class _Resp:
def __init__(self, raw, status=200):
self._raw = raw
self.status = status
def read(self):
return self._raw
def __enter__(self):
return self
def __exit__(self, *args):
return False
with mock.patch("urllib.request.urlopen",
return_value=_Resp(b'{"a": 1}', 200)):
self.assertEqual(_full_auto_github_request(
None, "GET", "https://example.invalid/x", {}, None),
(200, {"a": 1}))
err = urllib.error.HTTPError(
"https://example.invalid/x", 404, "Not Found", {},
io.BytesIO(b'{"error": "nope"}'))
with mock.patch("urllib.request.urlopen", side_effect=err):
self.assertEqual(_full_auto_github_request(
None, "GET", "https://example.invalid/x", {}, None),
(404, {"error": "nope"}))
with mock.patch("urllib.request.urlopen",
return_value=_Resp(b'not json', 200)):
self.assertEqual(_full_auto_github_request(
None, "GET", "https://example.invalid/x", {}, None),
(200, {}))
with mock.patch("urllib.request.urlopen",
return_value=_Resp(b'[1, 2]', 200)):
self.assertEqual(_full_auto_github_request(
None, "GET", "https://example.invalid/x", {}, None),
(200, {}))
class AdminCostTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"CREATE TABLE IF NOT EXISTS users(id TEXT PRIMARY KEY,"
" username TEXT UNIQUE, api_key_hash TEXT,"
" created_at REAL)")
conn.execute(
"INSERT OR IGNORE INTO users(id, username, api_key_hash,"
" created_at) VALUES('u1', 'u1', '', 0)")
conn.commit()
finally:
conn.close()
def tearDown(self):
self.tmp.cleanup()
def _handler(self):
h = _handler(self.db_path)
audits = []
h._audit_admin = lambda u, a, d: audits.append((u, a, d))
h.audits = audits
return h
def test_forbidden_for_non_admin(self):
h = self._handler()
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"},
clear=False):
h._admin_cost_by_creator("nobody", {"user_id": ["u1"]})
self.assertEqual(h.sent[0][0], 403)
self.assertTrue(any(a == "admin_quota_denied" for _u, a, _d in h.audits))
def test_target_validation(self):
h = self._handler()
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_cost_by_creator("admin1", {})
self.assertEqual(h.sent[0][0], 400)
h.sent.clear()
h._admin_cost_by_creator("admin1", {"user_id": ["ghost"]})
self.assertEqual(h.sent[0][0], 404)
def test_month_value_error_and_success(self):
h = self._handler()
h._cost_by_creator_payload = mock.Mock(
side_effect=ValueError("bad month"))
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_cost_by_creator(
"admin1", {"user_id": ["u1"], "month": ["2026-13"]})
self.assertEqual(h.sent[0][0], 400)
self.assertIn("YYYY-MM", h.sent[0][1]["error"])
h.sent.clear()
h._cost_by_creator_payload = mock.Mock(
return_value={"user_id": "u1", "month": "2026-11"})
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_cost_by_creator(
"admin1", {"user_id": ["u1"], "month": ["2026-11"]})
self.assertEqual(h.sent[0][0], 200)
self.assertEqual(h.sent[0][1]["user_id"], "u1")
class WorkersHeartbeatTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
def tearDown(self):
self.tmp.cleanup()
def _call(self, body=None, err=None):
h = _handler(self.db_path)
h._read_json = lambda: (body, err)
h._workers_heartbeat("u1")
return h.sent
def test_read_error(self):
sent = self._call(body=None, err="invalid json")
self.assertEqual(sent[0][0], 400)
def test_validation_errors(self):
self.assertEqual(self._call({})[0][1]["error"],
"worker_id required")
self.assertEqual(self._call({"worker_id": "w1",
"capacity": "many"})[0][1]["error"],
"capacity must be an integer")
self.assertEqual(self._call({"worker_id": "w1",
"running": "many"})[0][1]["error"],
"running must be an integer")
self.assertEqual(self._call({"worker_id": "w1",
"running": -1})[0][1]["error"],
"running must be >= 0")
self.assertEqual(self._call({"worker_id": "w1",
"host": 123})[0][1]["error"],
"host must be a string")
self.assertEqual(self._call({"worker_id": "w1",
"pid": "12"})[0][1]["error"],
"pid must be an integer")
self.assertEqual(self._call({"worker_id": "w1",
"pid": 0})[0][1]["error"],
"pid must be > 0")
self.assertEqual(self._call({"worker_id": "w1",
"address": 5})[0][1]["error"],
"address must be a string")
def test_registry_value_error(self):
h = _handler(self.db_path)
h._read_json = lambda: ({"worker_id": "w1"}, None)
with mock.patch("cloud.server._registry_heartbeat",
side_effect=ValueError("worker_id required")):
h._workers_heartbeat("u1")
self.assertEqual(h.sent[0][0], 400)
self.assertIn("worker_id required", h.sent[0][1]["error"])
def test_success_registers(self):
sent = self._call({"worker_id": "w1", "capacity": 2,
"running": 0, "host": "h1", "pid": 123,
"address": "10.0.0.1"})
self.assertEqual(sent[0][0], 200)
self.assertTrue(sent[0][1]["alive"])
self.assertEqual(sent[0][1]["worker_id"], "w1")
class AdminBackupTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
self.out_root = os.path.join(self.tmp.name, "backups")
os.makedirs(self.out_root)
def tearDown(self):
self.tmp.cleanup()
def _handler(self):
h = _handler(self.db_path)
h._audit_admin = lambda u, a, d: None
return h
def test_read_error_and_out_dir_type(self):
h = self._handler()
h._read_json = lambda: (None, "invalid json")
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 400)
h.sent.clear()
h._read_json = lambda: ({"out_dir": 123}, None)
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 400)
self.assertIn("out_dir must be a string", h.sent[0][1]["error"])
def test_forbidden_and_outside_root(self):
h = self._handler()
h._read_json = lambda: ({"out_dir": self.out_root}, None)
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "admin1"}):
h._admin_backup("nobody")
self.assertEqual(h.sent[0][0], 403)
h.sent.clear()
h._read_json = lambda: ({"out_dir": "/etc"}, None)
with mock.patch.dict(os.environ,
{"CODEX_GLOBAL_ADMINS": "admin1",
"CLOUD_BACKUP_ROOT": self.out_root}):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 400)
def test_sqlite_backup_with_fallback_root(self):
# No out_dir and no CLOUD_BACKUP_ROOT: the CLOUD_BACKUP_DIR
# fallback root supplies roots[0] as the candidate.
h = self._handler()
h._read_json = lambda: ({}, None)
env = {"CODEX_GLOBAL_ADMINS": "admin1",
"CLOUD_BACKUP_DIR": self.out_root}
with mock.patch.dict(os.environ, env):
os.environ.pop("CLOUD_BACKUP_ROOT", None)
os.environ.pop("CLOUD_DB_BACKEND", None)
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 200, h.sent)
self.assertTrue(h.sent[0][1]["path"].startswith(self.out_root))
def test_postgres_backup_branch_with_fake_dump(self):
dest = os.path.join(self.out_root, "pg.dump")
Path(dest).write_bytes(b"fake-dump")
h = self._handler()
# _db() resolves the backend from env and refuses PG without
# a driver; the backup branch under test is the selection +
# success response, so the count query runs on the real
# SQLite file through a plain connection here.
h._db = lambda: sqlite3.connect(self.db_path)
h._read_json = lambda: ({"out_dir": self.out_root}, None)
env = {"CODEX_GLOBAL_ADMINS": "admin1",
"CLOUD_BACKUP_ROOT": self.out_root,
"CLOUD_DB_BACKEND": "postgres"}
import cloud.backup as backup_mod
with mock.patch.dict(os.environ, env):
with mock.patch.object(backup_mod, "backup_postgres",
return_value=dest):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 200, h.sent)
self.assertEqual(h.sent[0][1]["backend"], "postgres")
self.assertEqual(h.sent[0][1]["path"], dest)
def test_chmod_failure_swallowed_and_backup_failure_500(self):
h = self._handler()
h._read_json = lambda: ({"out_dir": self.out_root}, None)
env = {"CODEX_GLOBAL_ADMINS": "admin1",
"CLOUD_BACKUP_ROOT": self.out_root}
with mock.patch.dict(os.environ, env):
with mock.patch("os.chmod", side_effect=OSError("chmod down")):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 200, h.sent)
h.sent.clear()
import cloud.backup as backup_mod
with mock.patch.dict(os.environ, env):
with mock.patch.object(backup_mod, "backup_db",
side_effect=RuntimeError("dump down")):
h._admin_backup("admin1")
self.assertEqual(h.sent[0][0], 500)
self.assertIn("backup failed", h.sent[0][1]["error"])
class _AuthCtx:
def __enter__(self):
return SimpleNamespace(check_permission=lambda *args: True)
def __exit__(self, *args):
return False
class _Writer:
def __init__(self, fail_after=None):
self.chunks = []
self.calls = 0
self.fail_after = fail_after
def write(self, data):
self.calls += 1
if self.fail_after is not None and self.calls > self.fail_after:
raise BrokenPipeError("pipe closed")
self.chunks.append(data.decode("utf-8")
if isinstance(data, bytes) else data)
def flush(self):
pass
def _trace_handler(db_path, fetch, entries, writer=None,
headers_raise=False):
h = _handler(db_path)
h._fetch_task = fetch
h._trace_snapshot = lambda task: (entries, "")
h.send_response = lambda *a: (_ for _ in ()).throw(
BrokenPipeError("gone")) if headers_raise else None
h.send_header = lambda *a: None
h.end_headers = lambda: None
h.wfile = writer if writer is not None else _Writer()
return h
class TraceStreamTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
def tearDown(self):
self.tmp.cleanup()
def _run(self, handler):
with mock.patch("cloud.server._auth_store",
lambda db_path: _AuthCtx()):
handler._task_trace_stream("u1", "T1")
return "".join(handler.wfile.chunks)
def test_headers_broken_pipe_returns(self):
h = _trace_handler(self.db_path, lambda tid: _task_row(),
[{"id": "e1"}], headers_raise=True)
out = self._run(h)
self.assertEqual(out, "")
def test_snapshot_emit_failure_returns(self):
writer = _Writer(fail_after=0)
h = _trace_handler(self.db_path, lambda tid: _task_row(),
[{"id": "e1"}], writer=writer)
out = self._run(h)
self.assertEqual(out, "")
self.assertEqual(writer.calls, 1)
def test_poll_timeout(self):
h = _trace_handler(self.db_path, lambda tid: _task_row(),
[{"id": "e1"}])
with mock.patch.object(server_mod, "TRACE_STREAM_MAX_SECONDS",
0.05), \
mock.patch.object(server_mod, "TRACE_STREAM_POLL_SECONDS",
0.01):
out = self._run(h)
self.assertIn('"type": "snapshot"', out)
self.assertIn('"reason": "timeout"', out)
def test_poll_deleted(self):
calls = [0]
def _fetch(tid):
calls[0] += 1
return _task_row() if calls[0] == 1 else None
h = _trace_handler(self.db_path, _fetch, [{"id": "e1"}])
with mock.patch.object(server_mod, "TRACE_STREAM_MAX_SECONDS",
60), \
mock.patch.object(server_mod, "TRACE_STREAM_POLL_SECONDS",
0.01):
out = self._run(h)
self.assertIn('"status": "deleted"', out)
def test_poll_new_entry_then_terminal(self):
calls = [0]
def _fetch(tid):
calls[0] += 1
if calls[0] == 1:
return _task_row(status="running")
return _task_row(status="completed")
def _snapshot(task):
if task["status"] == "completed":
return [{"id": "e1"}, {"id": "e2"}], ""
return [{"id": "e1"}], ""
h = _trace_handler(self.db_path, _fetch, [{"id": "e1"}])
h._trace_snapshot = _snapshot
with mock.patch.object(server_mod, "TRACE_STREAM_MAX_SECONDS",
60), \
mock.patch.object(server_mod, "TRACE_STREAM_POLL_SECONDS",
0.01):
out = self._run(h)
self.assertIn('"type": "entry"', out)
self.assertIn('"status": "completed"', out)
def test_poll_emit_failure_returns(self):
writer = _Writer(fail_after=1)
calls = [0]
def _fetch(tid):
calls[0] += 1
if calls[0] == 1:
return _task_row(status="running")
return _task_row(status="completed")
def _snapshot(task):
if task["status"] == "completed":
return [{"id": "e1"}, {"id": "e2"}], ""
return [{"id": "e1"}], ""
h = _trace_handler(self.db_path, _fetch, [{"id": "e1"}],
writer=writer)
h._trace_snapshot = _snapshot
with mock.patch.object(server_mod, "TRACE_STREAM_MAX_SECONDS",
60), \
mock.patch.object(server_mod, "TRACE_STREAM_POLL_SECONDS",
0.01):
out = self._run(h)
# Snapshot write ok, loop entry write fails -> stream ends
# without a done event.
self.assertIn('"type": "snapshot"', out)
self.assertNotIn('"type": "done"', out)
def test_poll_fetch_broken_pipe_returns(self):
calls = [0]
def _fetch(tid):
calls[0] += 1
if calls[0] == 1:
return _task_row(status="running")
raise BrokenPipeError("gone")
h = _trace_handler(self.db_path, _fetch, [{"id": "e1"}])
with mock.patch.object(server_mod, "TRACE_STREAM_MAX_SECONDS",
60), \
mock.patch.object(server_mod, "TRACE_STREAM_POLL_SECONDS",
0.01):
out = self._run(h)
self.assertIn('"type": "snapshot"', out)
self.assertNotIn('"reason": "timeout"', out)
if __name__ == "__main__":
unittest.main()
+915
View File
@@ -0,0 +1,915 @@
"""Coverage batch v2 (D2): cloud/server.py handler validation branches.
Covers baseline gaps in _create_project, _create_environment,
_create_upload, _automation_create, _automation_bind,
_automation_heartbeat, _merge_task, _loop_run, _audit_loop_confirm,
_full_auto_execute and _create_task (gateway / attachment /
idempotency).
Stubbing discipline: the method under test is never stubbed. Its
sinks and predicate collaborators are: _send_json (captured output),
_read_json (request body), _db (a proxy connection that delegates
every statement except the one under test, for the TOCTOU
IntegrityError simulations -- the same pattern as the worker retry
rowcount-0 test), _validate_sub2api_key / _pushback_block /
_loop_verdict / _loop_confirm (collaborator error injection for the
handler's own error mapping), and _loop_ledger_store /
_loop_create_subtask (close/subtask failure injection). All other
logic -- validation, permission checks via the real AuthStore,
quota gates, driver result shaping -- runs for real.
"""
import json
import os
import sqlite3
import sys
import tempfile
import time
import unittest
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))
import cloud.server as server_mod
from cloud.server import CloudControlPlaneHandler, _auth_store, init_db
from cloud.sub2api import Sub2apiError as _Sub2apiError
def _handler(db_path, upload_dir=None):
h = CloudControlPlaneHandler.__new__(CloudControlPlaneHandler)
h.server = SimpleNamespace(db_path=db_path, upload_dir=upload_dir)
sent = []
h._send_json = lambda status, obj, extra_headers=None: sent.append(
(status, obj))
h.sent = sent
return h
def _conn(db_path):
c = sqlite3.connect(db_path)
c.row_factory = sqlite3.Row
return c
class _ProxyConn:
"""Delegate everything except the statements a test targets."""
def __init__(self, real, fail_insert_prefix=None,
fail_insert_exc=None, first_select_none=None,
close_exc=None):
self._real = real
self._fail_insert_prefix = fail_insert_prefix
self._fail_insert_exc = fail_insert_exc
self._first_select_none = first_select_none
self._seen_select = 0
self._close_exc = close_exc
def execute(self, sql, params=()):
text = sql.strip()
if (self._fail_insert_prefix
and text.startswith(self._fail_insert_prefix)):
raise self._fail_insert_exc
if (self._first_select_none
and self._first_select_none in text
and text.startswith("SELECT")):
self._seen_select += 1
if self._seen_select == 1:
class _Empty:
def fetchone(self):
return None
def fetchall(self):
return []
return _Empty()
return self._real.execute(sql, params)
def close(self):
if self._close_exc is not None:
raise self._close_exc
return self._real.close()
def __getattr__(self, name):
return getattr(self._real, name)
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)
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 _member(self, pid, uid, role):
c = _conn(self.db_path)
c.execute(
"INSERT OR REPLACE INTO project_members(project_id,"
" user_id, role) VALUES(?, ?, ?)", (pid, uid, role))
c.commit()
c.close()
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 ProjectEnvUploadTestCase(_Base):
def test_create_project_read_error(self):
h = self._h(err="bad json")
h._create_project("u1")
self.assertEqual(self._last(h)[0], 400)
def test_create_project_name_required(self):
h = self._h(body={})
h._create_project("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("name required", payload["error"])
def test_create_project_id_must_be_string(self):
h = self._h(body={"name": "P", "id": 42})
h._create_project("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("id must be a string", payload["error"])
def test_create_project_auto_pr_bool(self):
# Bool branch (True -> 1): the 3351 line.
h = self._h(body={"name": "P", "auto_pr": True})
h._create_project("u1")
code, payload = self._last(h)
self.assertEqual(code, 201)
self.assertEqual(payload["auto_pr"], 1)
def test_create_project_existing_409(self):
self._project()
h = self._h(body={"name": "P2", "id": "p1"})
h._create_project("u1")
self.assertEqual(self._last(h)[0], 409)
def test_create_project_integrity_error_409(self):
# TOCTOU simulation: SELECT finds nothing, INSERT races and
# loses; the handler must map it to the same 409. Each _db()
# call gets a fresh proxy so closing one connection cannot
# poison the next.
h = self._h(body={"name": "P", "id": "p9"})
h._db = lambda: _ProxyConn(
_conn(self.db_path),
fail_insert_prefix="INSERT INTO projects",
fail_insert_exc=sqlite3.IntegrityError("race"))
h._create_project("u1")
self.assertEqual(self._last(h)[0], 409)
def test_create_environment_read_error(self):
h = self._h(err="bad json")
h._create_environment("u1")
self.assertEqual(self._last(h)[0], 400)
def test_create_environment_project_id_required(self):
h = self._h(body={})
h._create_environment("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("project_id required", payload["error"])
def test_create_environment_name_too_long(self):
self._project(repo_url="https://example.com/r.git")
h = self._h(body={"project_id": "p1", "name": "x" * 101})
h._create_environment("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("name too long", payload["error"])
def test_create_environment_integrity_error_409(self):
self._project(repo_url="https://example.com/r.git")
h = self._h(body={"project_id": "p1", "name": "env1"})
h._db = lambda: _ProxyConn(
_conn(self.db_path),
fail_insert_prefix="INSERT INTO environments",
fail_insert_exc=sqlite3.IntegrityError("race"))
h._create_environment("u1")
self.assertEqual(self._last(h)[0], 409)
def _multipart(self, parts):
boundary = "BOUNDARY"
out = b""
for headers, data in parts:
out += ("--%s\r\n" % boundary).encode()
for key, val in headers:
out += ("%s: %s\r\n" % (key, val)).encode()
out += b"\r\n" + data + b"\r\n"
out += ("--%s--\r\n" % boundary).encode()
return out, "multipart/form-data; boundary=" + boundary
def test_create_upload_content_length_value_error(self):
h = self._h()
h.headers = {"Content-Type": "multipart/form-data; boundary=X",
"Content-Length": "not-a-number"}
h._read_body = lambda: b""
h._create_upload("u1")
code, payload = self._last(h)
# Length falls back to 0, then the empty body fails parsing.
self.assertEqual(code, 400)
def test_create_upload_too_large(self):
from cloud.uploads import MAX_RAW_BYTES
h = self._h()
h.headers = {"Content-Type": "multipart/form-data; boundary=X",
"Content-Length": str(MAX_RAW_BYTES + 1)}
h._read_body = lambda: b"x"
h._create_upload("u1")
self.assertEqual(self._last(h)[0], 413)
def test_create_upload_parse_error(self):
raw, ctype = self._multipart([])
h = self._h()
h.headers = {"Content-Type": ctype,
"Content-Length": str(len(b"garbage"))}
h._read_body = lambda: b"garbage"
h._create_upload("u1")
self.assertEqual(self._last(h)[0], 400)
def test_create_upload_no_files(self):
# Zero parts: fields and files are both empty, so the paths
# check passes vacuously and the no-files branch fires.
raw, ctype = self._multipart([])
h = self._h()
h.headers = {"Content-Type": ctype,
"Content-Length": str(len(raw))}
h._read_body = lambda: raw
h._create_upload("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("no files", payload["error"])
def test_create_upload_paths_mismatch(self):
raw, ctype = self._multipart([
([("Content-Disposition",
'form-data; name="f1"; filename="a.txt"'),
("Content-Type", "text/plain")], b"hello"),
([("Content-Disposition",
'form-data; name="f2"; filename="b.txt"'),
("Content-Type", "text/plain")], b"world"),
([("Content-Disposition", 'form-data; name="paths"')],
b'["a.txt"]')])
h = self._h()
h.headers = {"Content-Type": ctype,
"Content-Length": str(len(raw))}
h._read_body = lambda: raw
h._create_upload("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("数量不一致", payload["error"])
class AutomationTestCase(_Base):
def _auto(self, name="auto1", project_id="p1", prompt="do it"):
from cloud.automations import create_automation
c = _conn(self.db_path)
auto = create_automation(c, project_id, name, prompt, "u1")
c.close()
return auto
def _bind(self, aid, tid, mode="heartbeat"):
from cloud.lifecycle import bind_automation
c = _conn(self.db_path)
binding = bind_automation(c, aid, "p1", tid, mode)
c.close()
return binding
def test_automation_create_read_error(self):
h = self._h(err="bad json")
h._automation_create("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_automation_create_interval_none_defaults(self):
self._project()
h = self._h(body={"name": "a1", "prompt": "p",
"interval_seconds": None})
h._automation_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 201)
self.assertEqual(payload["interval_seconds"], 86400)
def test_automation_create_bad_first_run(self):
self._project()
h = self._h(body={"name": "a1", "prompt": "p",
"first_run_at": "not-a-time"})
h._automation_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("first_run_at", payload["error"])
def test_automation_create_run_once_must_be_bool(self):
self._project()
h = self._h(body={"name": "a1", "prompt": "p",
"run_once": "yes"})
h._automation_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("run_once", payload["error"])
def test_automation_create_generic_error_502(self):
self._project()
h = self._h(body={"name": "a1", "prompt": "p"})
with mock.patch("cloud.server._auto_create",
side_effect=RuntimeError("db boom")):
h._automation_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 502)
self.assertIn("db boom", payload["error"])
def test_automation_bind_read_error(self):
h = self._h(err="bad json")
h._automation_bind("u1", "p1", "a1")
self.assertEqual(self._last(h)[0], 400)
def test_automation_bind_missing_404(self):
h = self._h(body={"task_id": "t1"})
h._automation_bind("u1", "p1", "nope")
self.assertEqual(self._last(h)[0], 404)
def test_automation_bind_cancelled_409(self):
auto = self._auto()
c = _conn(self.db_path)
c.execute("UPDATE automations SET status='cancelled' WHERE id=?",
(auto["id"],))
c.commit()
c.close()
self._task()
h = self._h(body={"task_id": "t1"})
h._automation_bind("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 409)
def test_automation_bind_target_missing_404(self):
auto = self._auto()
h = self._h(body={"task_id": "ghost"})
h._automation_bind("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 404)
def test_automation_bind_target_cross_project_404(self):
auto = self._auto()
self._project(pid="p2", owner="u2")
self._task(tid="t2", pid="p2", created_by="u2")
h = self._h(body={"task_id": "t2"})
h._automation_bind("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 404)
def test_automation_bind_bad_mode_400(self):
auto = self._auto()
self._task()
h = self._h(body={"task_id": "t1", "mode": "bogus"})
h._automation_bind("u1", "p1", auto["id"])
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("heartbeat or cron", payload["error"])
def test_automation_heartbeat_read_error(self):
h = self._h(err="bad json")
h._automation_heartbeat("u1", "p1", "a1")
self.assertEqual(self._last(h)[0], 400)
def test_automation_heartbeat_missing_404(self):
h = self._h(body={})
h._automation_heartbeat("u1", "p1", "nope")
self.assertEqual(self._last(h)[0], 404)
def test_automation_heartbeat_no_binding_409(self):
auto = self._auto()
h = self._h(body={})
h._automation_heartbeat("u1", "p1", auto["id"])
code, payload = self._last(h)
self.assertEqual(code, 409)
self.assertIn("no heartbeat binding", payload["error"])
def test_automation_heartbeat_cron_binding_409(self):
auto = self._auto()
self._task()
self._bind(auto["id"], "t1", mode="cron")
h = self._h(body={})
h._automation_heartbeat("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 409)
def test_automation_heartbeat_bound_task_missing_404(self):
auto = self._auto()
self._task()
self._bind(auto["id"], "t1")
c = _conn(self.db_path)
c.execute("DELETE FROM tasks WHERE id='t1'")
c.commit()
c.close()
h = self._h(body={})
h._automation_heartbeat("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 404)
def test_automation_heartbeat_bound_task_cross_project_404(self):
auto = self._auto()
self._task()
self._bind(auto["id"], "t1")
c = _conn(self.db_path)
c.execute("UPDATE tasks SET project_id='p2' WHERE id='t1'")
c.commit()
c.close()
h = self._h(body={})
h._automation_heartbeat("u1", "p1", auto["id"])
self.assertEqual(self._last(h)[0], 404)
def test_automation_heartbeat_quota_reject_returns(self):
# The bare return after the quota gate: quota lookup failure
# makes the gate answer 503, and heartbeat must return
# without delivering anything.
auto = self._auto()
self._task()
self._bind(auto["id"], "t1")
h = self._h(body={})
h._quota_status = mock.Mock(side_effect=RuntimeError("down"))
h._automation_heartbeat("u1", "p1", auto["id"])
self.assertEqual(len(h.sent), 1)
self.assertEqual(h.sent[0][0], 503)
def test_automation_heartbeat_message_must_be_string(self):
auto = self._auto()
self._task()
self._bind(auto["id"], "t1")
h = self._h(body={"message": 123})
h._automation_heartbeat("u1", "p1", auto["id"])
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("message required", payload["error"])
def test_automation_heartbeat_message_too_long(self):
auto = self._auto()
self._task()
self._bind(auto["id"], "t1")
h = self._h(body={"message": "x" * 20001})
h._automation_heartbeat("u1", "p1", auto["id"])
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("message too long", payload["error"])
class MergeLoopTestCase(_Base):
def _state(self):
return SimpleNamespace(
module_id="m", contract_version="v1", pushback_round=0,
failure_count=0, audit_fail_count=0,
acceptance_fail_count=0, rebuild_no=0, targeted_used=False,
status="open", pending_confirmation=False, retained_tests=[],
retained_passed_items=[], previous_artifacts=[], history=[])
def _loop_result(self, subtask=None):
return {
"next_state": self._state(), "action_kind": "verdict",
"reason": "r", "merge_blocked": False,
"release_blocked": False, "requires_reaudit": False,
"escalation_grants_user_acceptance": False,
"notification": None, "subtask": subtask}
def _confirm_result(self, subtask=None):
result = self._loop_result(subtask)
result.pop("requires_reaudit")
result.pop("escalation_grants_user_acceptance")
return result
def _closing_db(self):
# First _db() call serves _fetch_task (real); later calls
# serve the loop connection whose close() raises, so the
# finally-block except-pass lines run.
calls = {"n": 0}
def _db():
calls["n"] += 1
if calls["n"] == 1:
return _conn(self.db_path)
return _ProxyConn(_conn(self.db_path),
close_exc=RuntimeError("close boom"))
return _db
def _closing_store(self):
class _Store:
def close(self):
raise RuntimeError("store close boom")
return _Store()
def test_merge_read_error(self):
self._project()
self._task(status="completed")
h = self._h(err="bad json")
h._merge_task("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_merge_files_changed_must_be_list(self):
self._project()
self._task(status="completed")
h = self._h(body={"files_changed": "x"})
h._merge_task("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("files_changed must be a list", payload["error"])
def test_merge_pushback_unavailable_409(self):
self._project()
self._task(status="completed")
h = self._h(body={})
with mock.patch.dict(os.environ,
{"CLOUD_MERGE_GATE_MODE": "off"}), \
mock.patch("cloud.server._pushback_block",
side_effect=RuntimeError("chain down")):
h._merge_task("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 409)
self.assertEqual(payload["error"],
"audit pushback state unavailable")
self.assertTrue(payload["audit_pushback"]["merge_blocked"])
def test_merge_pushback_unavailable_audit_fails_too(self):
# The audit best-effort except-pass: even when the audit
# write itself raises, the handler still answers the same 409.
self._project()
self._task(status="completed")
h = self._h(body={})
real_auth = server_mod._auth_store
calls = {"n": 0}
def _auth_store(db_path):
calls["n"] += 1
if calls["n"] == 1:
return real_auth(db_path)
raise RuntimeError("audit down")
with mock.patch.dict(os.environ,
{"CLOUD_MERGE_GATE_MODE": "off"}), \
mock.patch("cloud.server._pushback_block",
side_effect=RuntimeError("chain down")), \
mock.patch("cloud.server._auth_store",
side_effect=_auth_store):
h._merge_task("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 409)
self.assertEqual(payload["error"],
"audit pushback state unavailable")
def test_loop_run_task_missing_404(self):
h = self._h()
h._loop_run("u1", "ghost", {}, "verdict")
self.assertEqual(self._last(h)[0], 404)
def test_loop_run_forbidden_403(self):
self._project()
self._task()
h = self._h()
h._loop_run("u9", "t1", {}, "verdict")
self.assertEqual(self._last(h)[0], 403)
def test_loop_run_verdict_body_400(self):
self._project()
self._task()
h = self._h()
h._loop_run("u1", "t1", {}, "verdict")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("verdict dict", payload["error"])
def test_loop_run_fix_body_400(self):
self._project()
self._task()
h = self._h()
h._loop_run("u1", "t1", {}, "fix")
self.assertEqual(self._last(h)[0], 400)
def test_loop_run_correction_body_400(self):
self._project()
self._task()
h = self._h()
h._loop_run("u1", "t1", {}, "correction")
self.assertEqual(self._last(h)[0], 400)
def test_loop_run_unknown_kind_400(self):
self._project()
self._task()
h = self._h()
h._loop_run("u1", "t1", {}, "bogus")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("unknown loop kind", payload["error"])
def test_loop_run_driver_value_error_400(self):
self._project()
self._task()
h = self._h()
with mock.patch("cloud.server._loop_verdict",
side_effect=ValueError("driver nope")):
h._loop_run("u1", "t1", {"verdict": {"verdict": "fail"}},
"verdict")
self.assertEqual(self._last(h)[0], 400)
def test_loop_run_driver_exception_503(self):
self._project()
self._task()
h = self._h()
with mock.patch("cloud.server._loop_verdict",
side_effect=RuntimeError("ledger down")):
h._loop_run("u1", "t1", {"verdict": {"verdict": "fail"}},
"verdict")
code, payload = self._last(h)
self.assertEqual(code, 503)
self.assertEqual(payload["error"], "audit_loop_unavailable")
def test_loop_run_close_failures_swallowed_200(self):
self._project()
self._task()
h = self._h()
h._db = self._closing_db()
h._loop_ledger_store = lambda: self._closing_store()
with mock.patch("cloud.server._loop_verdict",
return_value=self._loop_result()):
h._loop_run("u1", "t1", {"verdict": {"verdict": "fail"}},
"verdict")
self.assertEqual(self._last(h)[0], 200)
def test_loop_run_subtask_error_503(self):
self._project()
self._task()
h = self._h()
h._loop_create_subtask = mock.Mock(
side_effect=RuntimeError("insert boom"))
with mock.patch("cloud.server._loop_verdict",
return_value=self._loop_result(
subtask={"kind": "rebuild"})):
h._loop_run("u1", "t1", {"verdict": {"verdict": "fail"}},
"verdict")
code, payload = self._last(h)
self.assertEqual(code, 503)
self.assertIn("subtask_error", payload)
self.assertIn("subtask_note", payload)
def test_confirm_task_missing_404(self):
h = self._h(body={})
h._audit_loop_confirm("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_confirm_forbidden_403(self):
self._project()
self._task()
h = self._h(body={})
h._audit_loop_confirm("u9", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_confirm_read_error(self):
self._project()
self._task()
h = self._h(err="bad json")
h._audit_loop_confirm("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_confirm_driver_value_error_400(self):
self._project()
self._task()
h = self._h(body={"user_confirmed": True, "confirmed_by": "u1"})
with mock.patch("cloud.server._loop_confirm",
side_effect=ValueError("driver nope")):
h._audit_loop_confirm("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_confirm_driver_exception_503(self):
self._project()
self._task()
h = self._h(body={"user_confirmed": True, "confirmed_by": "u1"})
with mock.patch("cloud.server._loop_confirm",
side_effect=RuntimeError("ledger down")):
h._audit_loop_confirm("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 503)
self.assertEqual(payload["error"], "audit_loop_unavailable")
def test_confirm_close_failures_swallowed_200(self):
self._project()
self._task()
h = self._h(body={"user_confirmed": True, "confirmed_by": "u1"})
h._db = self._closing_db()
h._loop_ledger_store = lambda: self._closing_store()
with mock.patch("cloud.server._loop_confirm",
return_value=self._confirm_result()):
h._audit_loop_confirm("u1", "t1")
self.assertEqual(self._last(h)[0], 200)
def test_confirm_subtask_error_503(self):
self._project()
self._task()
h = self._h(body={"user_confirmed": True, "confirmed_by": "u1"})
h._loop_create_subtask = mock.Mock(
side_effect=RuntimeError("insert boom"))
with mock.patch("cloud.server._loop_confirm",
return_value=self._confirm_result(
subtask={"kind": "rebuild"})):
h._audit_loop_confirm("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 503)
self.assertIn("subtask_error", payload)
class FullAutoCreateTaskTestCase(_Base):
def _full_auto_h(self, body, err=None, admin=False):
h = self._h(body, err)
h._audit_admin = lambda *args: None
return h
def test_full_auto_read_error(self):
h = self._full_auto_h(None, err="bad json")
h._full_auto_execute("u1")
self.assertEqual(self._last(h)[0], 400)
def test_full_auto_plan_error(self):
h = self._full_auto_h({})
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("repo must be", payload["error"])
def test_full_auto_not_admin_denied(self):
h = self._full_auto_h({"repo": "o/r"})
with mock.patch.dict(os.environ, {}, clear=False):
os.environ.pop("CODEX_GLOBAL_ADMINS", None)
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 403)
self.assertEqual(payload["reason"], "not_global_admin")
def test_full_auto_action_must_be_string(self):
h = self._full_auto_h({"repo": "o/r", "action": 123})
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1"}):
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("action must be a string", payload["error"])
def test_full_auto_disabled_denied(self):
h = self._full_auto_h({"repo": "o/r"})
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1"}):
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 403)
self.assertEqual(payload["reason"], "full_auto_disabled")
def _install(self, repo="o/r"):
from cloud.github_app import get_app_config
c = _conn(self.db_path)
get_app_config(c)
c.execute(
"INSERT OR REPLACE INTO github_app_config(id, app_id, slug,"
" client_id, webhook_secret, pem, html_url, created_at)"
" VALUES(1, '1', 'app', 'cid', 'sec', NULL, NULL, ?)",
(time.time(),))
from cloud.github_app import _ensure_installations_table
_ensure_installations_table(c)
c.execute(
"INSERT INTO github_app_installations(installation_id,"
" repo_full_name, created_at) VALUES(?, ?, ?)",
(123, repo, time.time()))
c.commit()
c.close()
def test_full_auto_token_missing_denied(self):
self._install()
h = self._full_auto_h({"repo": "o/r"})
h.server.full_auto_token_resolver = lambda db_path, repo: None
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1",
"FULL_AUTO_ENABLED": "1"}):
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 403)
self.assertEqual(payload["reason"], "token_missing")
def test_full_auto_protection_request_error_denied(self):
self._install()
h = self._full_auto_h({"repo": "o/r"})
h.server.full_auto_token_resolver = lambda db_path, repo: "tok"
from cloud.full_auto import FullAutoError
h.server.full_auto_http_client = mock.Mock(
side_effect=FullAutoError("net down", reason="net"))
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1",
"FULL_AUTO_ENABLED": "1"}):
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 403)
self.assertEqual(payload["reason"],
"branch_protection_unverified")
def test_full_auto_protection_not_200_denied(self):
self._install()
h = self._full_auto_h({"repo": "o/r"})
h.server.full_auto_token_resolver = lambda db_path, repo: "tok"
h.server.full_auto_http_client = lambda m, u, hd, b: (404, {})
with mock.patch.dict(os.environ, {"CODEX_GLOBAL_ADMINS": "u1",
"FULL_AUTO_ENABLED": "1"}):
h._full_auto_execute("u1")
code, payload = self._last(h)
self.assertEqual(code, 403)
self.assertEqual(payload["reason"],
"branch_protection_unverified")
def test_create_task_gateway_sub2api_error_502(self):
self._project()
h = self._h(body={"project_id": "p1", "prompt": "hello"})
with mock.patch.dict(os.environ, {"CLOUD_GATEWAY": "sub2api"}), \
mock.patch("cloud.server._validate_sub2api_key",
side_effect=_Sub2apiError("key dead")):
h._create_task("u1")
code, payload = self._last(h)
self.assertEqual(code, 502)
self.assertIn("key dead", payload["error"])
def test_create_task_gateway_generic_error_502(self):
self._project()
h = self._h(body={"project_id": "p1", "prompt": "hello"})
with mock.patch.dict(os.environ, {"CLOUD_GATEWAY": "sub2api"}), \
mock.patch("cloud.server._validate_sub2api_key",
side_effect=RuntimeError("net down")):
h._create_task("u1")
self.assertEqual(self._last(h)[0], 502)
def _upload_row(self, uid="up1"):
c = _conn(self.db_path)
c.execute(
"INSERT INTO uploads(id, created_by, created_at, status)"
" VALUES(?, ?, ?, 'pending')",
(uid, "u1", time.time()))
c.commit()
c.close()
def test_create_task_attachment_dir_missing_400(self):
self._project()
self._upload_row()
h = self._h(body={"project_id": "p1", "prompt": "hello",
"upload_id": "up1"})
h._create_task("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("暂存目录缺失", payload["error"])
def test_create_task_attachment_empty_400(self):
self._project()
self._upload_row()
os.makedirs(os.path.join(self.upload_dir, "up1"), exist_ok=True)
h = self._h(body={"project_id": "p1", "prompt": "hello",
"upload_id": "up1"})
h._create_task("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("附件包为空", payload["error"])
def test_create_task_idempotency_race_returns_existing(self):
self._project()
self._task(tid="t-old", idempotency_key="k1")
h = self._h(body={"project_id": "p1", "prompt": "hello",
"idempotency_key": "k1"})
h._db = lambda: _ProxyConn(
_conn(self.db_path),
fail_insert_prefix="INSERT INTO tasks",
fail_insert_exc=sqlite3.IntegrityError("race"),
first_select_none="idempotency_key = ?")
h._create_task("u1")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["id"], "t-old")
if __name__ == "__main__":
unittest.main()
File diff suppressed because it is too large. Load diff
+496
View File
@@ -0,0 +1,496 @@
"""Coverage batch v2 (D4): cloud/server.py small handler clusters.
Same stubbing discipline as D2/D3: the method under test is never
stubbed; _send_json / _read_json are captured input, store classes
are faked only where the test target is the close-swallow itself
(the fake still performs the business call), and collaborator
error injection covers only the handler's own error mapping.
"""
import os
import sqlite3
import sys
import tempfile
import time
import unittest
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.server import (
CloudControlPlaneHandler,
_auth_store,
init_db,
)
def _handler(db_path, upload_dir=None):
h = CloudControlPlaneHandler.__new__(CloudControlPlaneHandler)
h.server = SimpleNamespace(db_path=db_path, upload_dir=upload_dir)
sent = []
h._send_json = lambda status, obj, extra_headers=None: sent.append(
(status, obj))
h.sent = sent
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)
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 _member(self, pid, uid, role):
c = _conn(self.db_path)
c.execute(
"INSERT OR REPLACE INTO project_members(project_id,"
" user_id, role) VALUES(?, ?, ?)", (pid, uid, role))
c.commit()
c.close()
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 _env(self, eid="e1", pid="p1"):
c = _conn(self.db_path)
c.execute(
"INSERT INTO environments(id, project_id, name, branch,"
" executor, status, error_text, repo_url, created_by,"
" created_at, updated_at) VALUES(?, ?, ?, ?, ?, ?, ?, ?,"
" ?, ?, ?)",
(eid, pid, "env", "main", "local", "ready", None,
"https://example.com/r.git", "u1", time.time(),
time.time()))
c.commit()
c.close()
def _last(self, h):
self.assertTrue(h.sent, "handler sent no response")
return h.sent[-1]
class _ClosingConn:
def __init__(self):
self.conn = mock.Mock()
self.conn.close.side_effect = RuntimeError("close down")
class _ClosingStore:
def __init__(self):
self.conn = _ClosingConn().conn
def close(self):
raise RuntimeError("close down")
class EnvProjectMemberTestCase(_Base):
def test_get_environment_check_error(self):
self._project()
self._env()
h = self._h()
h._get_environment("u9", "e1")
code, payload = self._last(h)
self.assertEqual(code, 403)
def test_archive_environment_missing_404(self):
h = self._h()
h._archive_environment("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_archive_environment_check_error(self):
self._project()
self._env()
h = self._h()
h._archive_environment("u9", "e1")
self.assertEqual(self._last(h)[0], 403)
def test_update_project_read_error(self):
self._project()
h = self._h(err="bad json")
h._update_project("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_resolve_invite_user_id_must_be_string(self):
h = self._h()
target, status, err = h._resolve_invite_target({"user_id": 42})
self.assertEqual(status, 400)
self.assertIsNone(target)
def test_resolve_invite_username_must_be_string(self):
h = self._h()
target, status, err = h._resolve_invite_target({"username": 42})
self.assertEqual(status, 400)
self.assertIsNone(target)
def test_member_invite_project_missing_404(self):
h = self._h(body={})
h._project_member_invite("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_member_invite_read_error(self):
self._project()
h = self._h(err="bad json")
h._project_member_invite("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_member_patch_project_missing_404(self):
h = self._h(body={})
h._project_member_patch("u1", "ghost", "u2")
self.assertEqual(self._last(h)[0], 404)
def test_member_patch_read_error(self):
self._project()
h = self._h(err="bad json")
h._project_member_patch("u1", "p1", "u2")
self.assertEqual(self._last(h)[0], 400)
def test_member_patch_unknown_role_400(self):
self._project()
h = self._h(body={"role": "superuser"})
h._project_member_patch("u1", "p1", "u2")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("unknown role", payload["error"])
def test_push_sub_delete_wrong_method_404(self):
h = self._h()
h._project_push_subscription_delete("u1", "GET", "p1",
["p1", "push",
"subscriptions", "s1"])
self.assertEqual(self._last(h)[0], 404)
def _sub(self, sid="s1", pid="p1", uid="u2"):
from cloud.push import init_push_db
init_push_db(self.db_path)
c = _conn(self.db_path)
c.execute(
"INSERT INTO push_subscriptions(id, project_id, user_id,"
" channel, target, preference, created_at)"
" VALUES(?, ?, ?, ?, ?, ?, ?)",
(sid, pid, uid, "webhook", "https://example.com/hook",
"all", time.time()))
c.commit()
c.close()
def test_push_sub_delete_missing_404(self):
self._project()
h = self._h()
h._project_push_subscription_delete("u1", "DELETE", "p1",
["p1", "push",
"subscriptions", "ghost"])
self.assertEqual(self._last(h)[0], 404)
def test_push_sub_delete_forbidden_403(self):
self._project(owner="owner1")
self._member("p1", "u1", "contributor")
self._sub()
h = self._h()
h._project_push_subscription_delete("u1", "DELETE", "p1",
["p1", "push",
"subscriptions", "s1"])
self.assertEqual(self._last(h)[0], 403)
class MemoryIdentityTestCase(_Base):
def test_memory_create_read_error(self):
h = self._h(err="bad json")
h._memory_create("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_memory_create_goal_required(self):
h = self._h(body={"conclusion": "done"})
h._memory_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("goal required", payload["error"])
def test_memory_create_close_failure_swallowed(self):
class _Store:
def __init__(self, db_path):
self.conn = _ClosingConn().conn
def add_summary(self, *a, **k):
return 99
h = self._h(body={"goal": "g", "conclusion": "c"})
with mock.patch("cloud.server.MemoryStore", _Store):
h._memory_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 201)
self.assertEqual(payload["id"], 99)
def test_memory_list_close_failure_swallowed(self):
class _Store:
def __init__(self, db_path):
self.conn = _ClosingConn().conn
def list_summaries(self, project_id):
return []
def build_prompt_context(self, project_id, top_k=5):
return ""
h = self._h()
with mock.patch("cloud.server.MemoryStore", _Store):
h._memory_list("p1", {})
self.assertEqual(self._last(h)[0], 200)
def test_memory_supersede_close_failure_swallowed(self):
from cloud.server import MemoryStore
store = MemoryStore(self.db_path)
try:
mid = store.add_summary("p1", "", "g", "c")
finally:
store.conn.close()
class _Store:
def __init__(self, db_path):
self.conn = _ClosingConn().conn
def deprecate(self, mid):
pass
h = self._h()
with mock.patch("cloud.server.MemoryStore", _Store):
h._memory_supersede("u1", "p1", str(mid))
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["status"], "deprecated")
def test_identity_close_swallow(self):
h = self._h()
h._identity_close(_ClosingStore())
h._auto_memory_close(_ClosingStore())
def test_identity_profile_upsert_read_error(self):
h = self._h(err="bad json")
h._identity_profile_upsert("u1")
self.assertEqual(self._last(h)[0], 400)
def test_identity_profile_upsert_bad_preferences_400(self):
h = self._h(body={"preferences": "not-an-object"})
h._identity_profile_upsert("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("must be an object", payload["error"])
def test_identity_user_memory_create_read_error(self):
h = self._h(err="bad json")
h._identity_user_memory_create("u1")
self.assertEqual(self._last(h)[0], 400)
def test_identity_user_memory_create_bad_kind_400(self):
h = self._h(body={"kind": "bogus", "content": "x"})
h._identity_user_memory_create("u1")
self.assertEqual(self._last(h)[0], 400)
def test_identity_user_memory_supersede_read_error_body_404(self):
# Read error does not abort supersede: body defaults to {}
# (4294) and the missing-memory check still answers 404.
h = self._h(err="bad json")
h._identity_user_memory_supersede("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_identity_user_memory_supersede_key_error_404(self):
# Row check passes, then the supersede itself loses a race
# (KeyError) -- the handler maps it to the same 404.
class _Store:
def __init__(self, db_path):
self.conn = mock.Mock()
def get_memory(self, mem_id):
return {"scope": "user", "owner_user_id": "u1"}
def supersede_memory(self, *a, **k):
raise KeyError("gone")
h = self._h(body={})
with mock.patch("cloud.server.IdentityStore", _Store):
h._identity_user_memory_supersede("u1", "m1")
self.assertEqual(self._last(h)[0], 404)
def test_identity_user_memory_delete_key_error_404(self):
class _Store:
def __init__(self, db_path):
self.conn = mock.Mock()
def get_memory(self, mem_id):
return {"scope": "user", "owner_user_id": "u1"}
def delete_memory(self, mem_id, user_id):
raise KeyError("gone")
h = self._h()
with mock.patch("cloud.server.IdentityStore", _Store):
h._identity_user_memory_delete("u1", "m1")
self.assertEqual(self._last(h)[0], 404)
def test_identity_user_memory_supersede_bad_kind_400(self):
h = self._h(body={"kind": "fact", "content": "likes tea"})
h._identity_user_memory_create("u1")
code, payload = self._last(h)
self.assertEqual(code, 201)
mem_id = payload["id"]
h2 = self._h(body={"replacement_kind": "bogus",
"replacement_content": "x"})
h2._identity_user_memory_supersede("u1", mem_id)
self.assertEqual(self._last(h2)[0], 400)
def test_identity_user_memory_delete_missing_404(self):
h = self._h()
h._identity_user_memory_delete("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_identity_user_memory_delete_forbidden_404(self):
h = self._h(body={"kind": "fact", "content": "likes tea"})
h._identity_user_memory_create("u1")
mem_id = self._last(h)[1]["id"]
h2 = self._h()
h2._identity_user_memory_delete("u2", mem_id)
self.assertEqual(self._last(h2)[0], 404)
def test_identity_context_with_project(self):
self._project()
h = self._h()
h._identity_context("u1", {"project_id": ["p1"]})
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["project_id"], "p1")
def test_identity_context_close_failure_swallowed(self):
self._project()
class _Store:
def __init__(self, db_path):
self.conn = _ClosingConn().conn
def build_prompt_context(self, project_id):
return ""
h = self._h()
with mock.patch("cloud.server.MemoryStore", _Store):
h._identity_context("u1", {"project_id": ["p1"]})
self.assertEqual(self._last(h)[0], 200)
def test_auto_memory_settings_read_error(self):
h = self._h(err="bad json")
h._auto_memory_settings_upsert("u1")
self.assertEqual(self._last(h)[0], 400)
def test_auto_memory_settings_body_must_be_object(self):
h = self._h(body=["not", "an", "object"])
h._auto_memory_settings_upsert("u1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("must be an object", payload["error"])
def test_auto_memory_log_bad_limit(self):
h = self._h()
h._auto_memory_log("u1", {"limit": ["abc"]})
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("limit must be an integer", payload["error"])
def test_task_auto_memory_flags_read_error(self):
h = self._h(err="bad json")
h._task_auto_memory_flags("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_task_auto_memory_flags_body_must_be_object(self):
h = self._h(body=["x"])
h._task_auto_memory_flags("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_task_auto_memory_flags_check_error(self):
self._project()
self._task()
h = self._h(body={})
h._task_auto_memory_flags("u9", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_project_identity_memory_create_read_error(self):
h = self._h(err="bad json")
h._project_identity_memory_create("u1", "p1")
self.assertEqual(self._last(h)[0], 400)
def test_project_identity_memories_list_bad_top_k(self):
h = self._h()
h._project_identity_memories_list("p1", {"top_k": ["abc"]})
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("top_k must be an integer", payload["error"])
def test_project_identity_memory_supersede_none_body_404(self):
h = self._h(body=None)
h._project_identity_memory_supersede("u1", "p1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_project_identity_memory_supersede_read_error_body_404(self):
# Same read-error defaulting as the user-memory variant.
h = self._h(err="bad json")
h._project_identity_memory_supersede("u1", "p1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_project_identity_memory_supersede_key_error_404(self):
class _Store:
def __init__(self, db_path):
self.conn = mock.Mock()
def get_memory(self, mem_id):
return {"scope": "project", "project_id": "p1"}
def supersede_memory(self, *a, **k):
raise PermissionError("not yours")
h = self._h(body={})
with mock.patch("cloud.server.IdentityStore", _Store):
h._project_identity_memory_supersede("u1", "p1", "m1")
self.assertEqual(self._last(h)[0], 404)
def test_project_identity_memory_supersede_bad_kind_400(self):
self._project()
h = self._h(body={"kind": "fact", "content": "likes tea"})
h._project_identity_memory_create("u1", "p1")
code, payload = self._last(h)
self.assertEqual(code, 201)
mem_id = payload["id"]
h2 = self._h(body={"replacement_kind": "bogus",
"replacement_content": "x"})
h2._project_identity_memory_supersede("u1", "p1", mem_id)
self.assertEqual(self._last(h2)[0], 400)
+648
View File
@@ -0,0 +1,648 @@
"""Coverage batch v2 (D4b): cloud/server.py small handler clusters,
second half -- costs/relay, task lists, followups, threads, files,
browser helpers, peer messages, reviews, skills/apps/MCP lists.
Same stubbing discipline as D2-D4: the method under test is never
stubbed; host-CLI collaborators (_run_host_cli, _extract_*) are
stubbed only to drive the handler's own error mapping.
"""
import os
import socket
import sqlite3
import sys
import tempfile
import time
import unittest
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.server import (
CloudControlPlaneHandler,
_auth_store,
init_db,
)
from cloud.sub2api import Sub2apiError as _Sub2apiError
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))
h.sent = sent
return h
def _conn(db_path):
c = sqlite3.connect(db_path)
c.row_factory = sqlite3.Row
return c
class _ProxyConn:
def __init__(self, real, fail_select_substr=None, fail_exc=None):
self._real = real
self._fail_select_substr = fail_select_substr
self._fail_exc = fail_exc
def execute(self, sql, params=()):
text = sql.strip()
if (self._fail_select_substr and text.startswith("SELECT")
and self._fail_select_substr in text):
raise self._fail_exc
return self._real.execute(sql, params)
def __getattr__(self, name):
return getattr(self._real, name)
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 _member(self, pid, uid, role):
c = _conn(self.db_path)
c.execute(
"INSERT OR REPLACE INTO project_members(project_id,"
" user_id, role) VALUES(?, ?, ?)", (pid, uid, role))
c.commit()
c.close()
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 _ClosingConn:
def __init__(self):
self.conn = mock.Mock()
self.conn.close.side_effect = RuntimeError("close down")
class CostRelayTestCase(_Base):
def test_kb_search_q_required(self):
h = self._h()
h._kb_search("p1", {})
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("q required", payload["error"])
def test_kb_search_bad_top_k(self):
h = self._h()
h._kb_search("p1", {"q": ["x"], "top_k": ["abc"]})
self.assertEqual(self._last(h)[0], 400)
def test_kb_search_close_failure_swallowed(self):
class _Store:
def __init__(self, db_path):
self.conn = _ClosingConn().conn
def search(self, project_id, q, top_k=5):
return []
h = self._h()
with mock.patch("cloud.server.KBStore", _Store):
h._kb_search("p1", {"q": ["x"]})
self.assertEqual(self._last(h)[0], 200)
def test_project_cost_bad_days(self):
h = self._h()
h._project_cost("p1", {"days": ["abc"]})
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("days must be an integer", payload["error"])
def test_project_cost_close_failure_swallowed(self):
class _Store:
def __init__(self, db_path):
self._conn = mock.Mock()
self._conn.close.side_effect = RuntimeError("down")
def dual_column_report_by_project(self, project_id):
return {}
h = self._h()
with mock.patch("cloud.server.CostStore", _Store):
h._project_cost("p1", {})
self.assertEqual(self._last(h)[0], 200)
def test_task_cost_close_failure_swallowed(self):
self._project()
self._task()
class _Store:
def __init__(self, db_path):
self._conn = mock.Mock()
self._conn.close.side_effect = RuntimeError("down")
def dual_column_report_by_task(self, task_id):
return {}
h = self._h()
with mock.patch("cloud.server.CostStore", _Store):
h._task_cost("u1", "t1")
self.assertEqual(self._last(h)[0], 200)
def test_relay_models_sub2api_generic_error_502(self):
h = self._h()
with mock.patch.dict(os.environ, {"CLOUD_GATEWAY": "sub2api"}), \
mock.patch("cloud.server._validate_sub2api_key",
side_effect=RuntimeError("net down")):
h._relay_models("u1")
self.assertEqual(self._last(h)[0], 502)
def test_relay_models_sub2api_list_error_502(self):
h = self._h()
with mock.patch.dict(os.environ, {"CLOUD_GATEWAY": "sub2api"}), \
mock.patch("cloud.server._validate_sub2api_key",
return_value=("ok", "key", "sub", "kid")), \
mock.patch("cloud.server._list_sub2api_models",
side_effect=RuntimeError("gw down")):
h._relay_models("u1")
self.assertEqual(self._last(h)[0], 502)
def test_models_overview_no_memberships_200(self):
self._project()
h = self._h()
h._models_overview("u9")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["model_stats"], [])
def test_models_overview_close_failure_swallowed(self):
self._project()
conn = mock.Mock()
conn.execute.return_value.fetchall.return_value = []
conn.close.side_effect = RuntimeError("down")
class _Store:
def __init__(self, db_path):
self._conn = conn
h = self._h()
with mock.patch("cloud.server.CostStore", _Store):
h._models_overview("u1")
self.assertEqual(self._last(h)[0], 200)
class ListsFollowupsTestCase(_Base):
def test_list_tasks_project_required(self):
h = self._h()
h._list_tasks("u1", {})
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("project_id required", payload["error"])
def test_list_tasks_executor_filter_skips(self):
self._project()
self._task()
h = self._h()
h._list_tasks("u1", {"project_id": ["p1"],
"executor": ["docker"]})
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["tasks"], [])
def test_goal_convert_queued_none(self):
self._project()
self._task(status="completed")
h = self._h()
self.assertIsNone(h._goal_convert_queued("t1", "做目标"))
def test_goal_revive_terminal_none(self):
self._project()
self._task(status="completed", resume_message="old")
h = self._h()
self.assertIsNone(h._goal_revive_terminal("t1", "做目标"))
def test_task_agents_remote_host_200(self):
self._project()
self._task(worktree_host="other-host")
h = self._h()
h._task_agents("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["agents"], [])
def test_create_followup_convert_race_409(self):
self._project()
self._task(status="queued")
h = self._h(body={"body": "/goal 做目标"})
h._goal_convert_queued = lambda tid, target: None
h._create_followup("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 409)
self.assertIn("状态已变化", payload["error"])
def test_create_followup_revive_race_409(self):
self._project()
self._task(status="completed")
h = self._h(body={"body": "/goal 做目标"})
h._goal_revive_terminal = lambda tid, target: None
h._create_followup("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 409)
self.assertIn("状态已变化", payload["error"])
def test_append_direct_trace_remote_host_returns(self):
h = self._h()
h._append_direct_trace({"id": "t1", "project_id": "p1",
"worktree_host": "other-host"},
"fu1", "hi")
def test_append_direct_trace_tenant_dir(self):
tenant_dir = os.path.join(self.tmp.name, "p1", "work",
"host-traces")
os.makedirs(tenant_dir, exist_ok=True)
open(os.path.join(tenant_dir, "t1.jsonl"), "w").close()
h = self._h()
h._append_direct_trace({"id": "t1", "project_id": "p1"},
"fu1", "hello tenant")
with open(os.path.join(tenant_dir, "t1.jsonl")) as f:
content = f.read()
self.assertIn("followup-direct", content)
def test_append_direct_trace_write_failure_swallowed(self):
h = self._h()
with mock.patch("os.makedirs",
side_effect=OSError("ro fs")):
h._append_direct_trace({"id": "t1", "project_id": "p1"},
"fu1", "hi")
def test_list_followups_missing_404(self):
h = self._h()
h._list_followups("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_list_followups_forbidden_403(self):
self._project()
self._task()
h = self._h()
h._list_followups("u9", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_task_timeline_forbidden_403(self):
self._project()
self._task()
h = self._h()
h._task_timeline("u9", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_task_files_200_cap(self):
self._project()
self._task()
worktree = os.path.join(self.tmp.name, "p1", "work",
"host-worktrees", "t1")
os.makedirs(worktree, exist_ok=True)
for i in range(201):
with open(os.path.join(worktree, "f%03d.txt" % i), "w") as f:
f.write("x")
h = self._h()
h._task_files("u1", "t1", {})
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(len(payload["entries"]), 200)
def test_browser_worktree_for_cloud_409(self):
self._project()
self._task(executor="cloud")
h = self._h()
worktree, err = h._browser_worktree_for("u1", "t1")
self.assertIsNone(worktree)
self.assertEqual(err[0], 409)
def test_browser_body_dict_none_and_bad(self):
h = self._h()
self.assertIsNone(h._browser_body_dict(None))
self.assertIsNone(h._browser_body_dict(b"not json"))
self.assertEqual(h._browser_body_dict({"a": 1}), {"a": 1})
def test_browser_input_worktree_error(self):
h = self._h()
h._browser_worktree_for = lambda *a: (None, (404, "nope"))
h._browser_input("u1", "t1", {})
self.assertEqual(self._last(h)[0], 404)
def test_browser_input_state_error(self):
h = self._h()
h._browser_worktree_for = lambda *a: (self.tmp.name, None)
with mock.patch("cloud.playwright_remote.read_state",
return_value=(None, (502, "no session"))):
h._browser_input("u1", "t1", {})
self.assertEqual(self._last(h)[0], 502)
def test_browser_input_write_os_error_503(self):
h = self._h()
h._browser_worktree_for = lambda *a: (self.tmp.name, None)
with mock.patch("cloud.playwright_remote.read_state",
return_value=({"url": ""}, None)), \
mock.patch("cloud.playwright_remote.effective_holder",
return_value="user"), \
mock.patch("cloud.playwright_remote.append_input",
side_effect=OSError("unreachable")):
h._browser_input("u1", "t1", {"type": "type", "text": "hi"})
code, payload = self._last(h)
self.assertEqual(code, 503)
self.assertIn("browser input unreachable", payload["error"])
def test_chat_history_forbidden_403(self):
self._project()
self._task()
h = self._h()
h._task_chat_history("u9", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_chat_history_legacy_query_failure_none(self):
self._project()
self._task()
h = self._h()
h._trace_snapshot = lambda task: ([], None)
h._db = lambda: _ProxyConn(
_conn(self.db_path),
fail_select_substr="FROM audit_log",
fail_exc=RuntimeError("audit down"))
h._task_chat_history("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertIsNone(payload["legacy_continue_count"])
def test_peer_message_read_error(self):
h = self._h(err="bad json")
h._create_peer_message("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_peer_message_sender_forbidden_403(self):
self._project()
self._project(pid="p2", owner="u2")
self._task(tid="t1", pid="p1")
self._task(tid="t2", pid="p2", created_by="u2")
h = self._h(body={"from_task_id": "t2", "body": "hi"})
h._create_peer_message("u1", "t1")
self.assertEqual(self._last(h)[0], 403)
def test_peer_message_body_too_long(self):
self._project()
self._task(tid="t1", pid="p1")
self._task(tid="t2", pid="p1")
h = self._h(body={"from_task_id": "t2", "body": "x" * 20001})
h._create_peer_message("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("body too long", payload["error"])
def test_list_peer_messages_missing_404(self):
h = self._h()
h._list_peer_messages("u1", "ghost")
self.assertEqual(self._last(h)[0], 404)
def test_create_review_read_error(self):
h = self._h(err="bad json")
h._create_review("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_update_review_findings_read_error(self):
h = self._h(err="bad json")
h._update_review_findings("u1", "t1", "r1")
self.assertEqual(self._last(h)[0], 400)
def test_update_review_findings_missing_404(self):
h = self._h(body={})
h._update_review_findings("u1", "ghost", "r1")
self.assertEqual(self._last(h)[0], 404)
class ThreadListsTestCase(_Base):
def test_thread_guard_docker_409(self):
h = self._h()
code, msg = h._thread_task_guard({"executor": "docker"})
self.assertEqual(code, 409)
def test_thread_guard_remote_host_409(self):
h = self._h()
code, msg = h._thread_task_guard(
{"executor": None, "worktree_host": "other-host"})
self.assertEqual(code, 409)
def test_list_threads_no_tenant_home_502(self):
h = self._h()
threads, home, cwd, err, provider = h._list_task_threads(
{"id": "t1", "project_id": None})
self.assertEqual(err[0], 502)
def test_list_threads_extract_none_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")), \
mock.patch("cloud.server._extract_thread_list",
return_value=None):
threads, home, cwd, err, provider = h._list_task_threads(
{"id": "t1", "project_id": "p1"})
self.assertEqual(err[0], 502)
self.assertIn("no thread list", err[1])
def test_task_thread_guard_409(self):
self._project()
self._task(executor="docker")
h = self._h()
h._task_thread("u1", "t1", {})
self.assertEqual(self._last(h)[0], 409)
def test_task_thread_cli_failure_502(self):
self._project()
self._task()
h = self._h()
h._resolve_task_thread = lambda task: (
{"id": "th1"}, "/tmp", "/tmp", None, None)
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._task_thread("u1", "t1", {})
code, payload = self._last(h)
self.assertEqual(code, 502)
self.assertIn("host down", payload["error"])
def test_task_thread_no_thread_502(self):
self._project()
self._task()
h = self._h()
h._resolve_task_thread = lambda task: (
{"id": "th1"}, "/tmp", "/tmp", None, None)
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")):
h._task_thread("u1", "t1", {})
code, payload = self._last(h)
self.assertEqual(code, 502)
self.assertIn("no thread", payload["error"])
def test_task_thread_mutate_task_error(self):
h = self._h()
h._task_thread_mutate("u1", "ghost", ["archive"],
"thread_archived", "detail")
self.assertEqual(self._last(h)[0], 404)
def test_task_thread_rename_read_error(self):
self._project()
self._task()
h = self._h(err="bad json")
h._task_thread_rename("u1", "t1")
self.assertEqual(self._last(h)[0], 400)
def test_task_thread_rename_name_too_long(self):
self._project()
self._task()
h = self._h(body={"name": "x" * 201})
h._task_thread_rename("u1", "t1")
code, payload = self._last(h)
self.assertEqual(code, 400)
self.assertIn("name too long", payload["error"])
def test_project_host_home_none(self):
h = self._h()
self.assertEqual(h._project_host_home(None), (None, None))
def _list_case(self, method):
h = self._h()
method(h, None)
return h
def test_skills_list_home_none_502(self):
h = self._h()
h._skills_list(None)
self.assertEqual(self._last(h)[0], 502)
def test_skills_list_cli_failure_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._skills_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_skills_list_no_list_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")):
h._skills_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_apps_list_home_none_502(self):
h = self._h()
h._apps_list(None)
self.assertEqual(self._last(h)[0], 502)
def test_apps_list_cli_failure_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._apps_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_apps_list_no_list_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")):
h._apps_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_mcp_list_home_none_502(self):
h = self._h()
h._mcp_servers_list(None)
self.assertEqual(self._last(h)[0], 502)
def test_mcp_list_cli_failure_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._mcp_servers_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_mcp_list_no_list_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(True, "garbage")):
h._mcp_servers_list("p1")
self.assertEqual(self._last(h)[0], 502)
def test_mcp_list_next_cursor(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(True, {"servers": [],
"nextCursor": "n1"})):
h._mcp_servers_list("p1")
code, payload = self._last(h)
self.assertEqual(code, 200)
self.assertEqual(payload["nextCursor"], "n1")
def test_mcp_reload_home_none_502(self):
h = self._h()
h._mcp_servers_reload("u1", None)
self.assertEqual(self._last(h)[0], 502)
def test_mcp_reload_cli_failure_502(self):
h = self._h()
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._mcp_servers_reload("u1", "p1")
self.assertEqual(self._last(h)[0], 502)
def test_mcp_oauth_login_read_error(self):
h = self._h(err="bad json")
h._mcp_server_oauth_login("u1", "p1", "srv")
self.assertEqual(self._last(h)[0], 400)
def test_mcp_oauth_login_timeout_argv(self):
h = self._h(body={"timeoutSecs": 30})
with mock.patch("cloud.server._run_host_cli",
return_value=(True, {"authorizationUrl": "u",
"loginId": "l"})) as cli:
h._mcp_server_oauth_login("u1", "p1", "srv")
argv = cli.call_args[0][0]
self.assertIn("--timeout-secs", argv)
self.assertEqual(self._last(h)[0], 200)
def test_mcp_oauth_login_cli_failure_502(self):
h = self._h(body={})
with mock.patch("cloud.server._run_host_cli",
return_value=(False, "host down")):
h._mcp_server_oauth_login("u1", "p1", "srv")
self.assertEqual(self._last(h)[0], 502)
File diff suppressed because it is too large. Load diff
+615
View File
@@ -0,0 +1,615 @@
"""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.server import (
CloudControlPlaneHandler,
RateLimiter,
_apply_default_quota,
_auth_store,
_backfill_default_quotas,
_classify_goal_followup,
_deployed_commit,
_default_executor,
_full_auto_installation_verified,
_full_auto_parse_plan_params,
_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.server.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.server.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.server.get_app_config",
return_value={"id": 1}), \
mock.patch("cloud.server.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.server.get_app_config",
return_value={"id": 1}), \
mock.patch("cloud.server.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.server.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.server.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.server 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.server.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)
+204
View File
@@ -0,0 +1,204 @@
"""Coverage batch v2 (D8): GitHub webhook /fix-review branches --
project with no owner, task-INSERT IntegrityError duplicate read,
and IntegrityError re-raise.
The webhook handler runs end to end with a real HMAC signature
(env secret), a real delivery claim and a real fix-review claim;
only the task-table INSERT is faulted, via a counting _db proxy,
to drive the handler's own IntegrityError mapping.
"""
import hashlib
import hmac
import io
import json
import os
import sqlite3
import sys
import tempfile
import time
import unittest
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.server import (
CloudControlPlaneHandler,
init_db,
)
SECRET = "test-secret"
def _handler(db_path):
h = CloudControlPlaneHandler.__new__(CloudControlPlaneHandler)
h.server = SimpleNamespace(db_path=db_path, upload_dir=None,
tenants_root=None)
sent = []
h._send_json = lambda status, obj, extra_headers=None: sent.append(
(status, obj))
h.sent = sent
return h
def _conn(db_path):
c = sqlite3.connect(db_path)
c.row_factory = sqlite3.Row
return c
class _RowCursor:
def __init__(self, row):
self._row = row
def fetchone(self):
return self._row
def fetchall(self):
return [self._row] if self._row else []
class _WebhookConn:
"""Delegates everything except the task INSERT (raises
IntegrityError) and the idempotency re-read (faked row)."""
def __init__(self, real, existing_row):
self._real = real
self._existing_row = existing_row
def execute(self, sql, params=()):
text = sql.strip()
if text.startswith("INSERT INTO tasks"):
raise sqlite3.IntegrityError("duplicate delivery")
if "SELECT id FROM tasks WHERE project_id" in text:
return _RowCursor(self._existing_row)
return self._real.execute(sql, params)
def __getattr__(self, name):
return getattr(self._real, name)
class WebhookFixReviewTestCase(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)
from cloud.cost import CostStore
CostStore(self.db_path)
def tearDown(self):
self.tmp.cleanup()
def _project_row(self, owner):
c = _conn(self.db_path)
c.execute(
"INSERT INTO projects(id, name, owner_id, created_at,"
" repo_url, auto_pr) VALUES(?, ?, ?, ?, ?, 0)",
("p1", "o/r", owner, time.time(), None))
c.commit()
c.close()
def _payload(self):
return {
"action": "created",
"comment": {
"id": 99,
"body": "/fix-review please fix this",
"user": {"login": "alice",
"author_association": "MEMBER"},
},
"issue": {"number": 7, "user": {"login": "bob"}},
"repository": {"full_name": "o/r"},
}
def _h(self, raw, delivery="d1"):
h = _handler(self.db_path)
sig = "sha256=" + hmac.new(
SECRET.encode(), raw, hashlib.sha256).hexdigest()
h.rfile = io.BytesIO(raw)
h.headers = {
"Content-Length": str(len(raw)),
"X-Hub-Signature-256": sig,
"X-GitHub-Event": "issue_comment",
"X-GitHub-Delivery": delivery,
}
return h
def test_project_no_owner(self):
self._project_row(None)
raw = json.dumps(self._payload()).encode()
h = self._h(raw)
with mock.patch.dict(os.environ,
{"GITHUB_WEBHOOK_SECRET": SECRET}):
h._handle_github_webhook()
code, payload = h.sent[-1]
self.assertEqual(code, 200)
self.assertIn("no owner for quota attribution",
payload["error"])
def _proxy_db(self, existing_row):
calls = {"n": 0}
def _db():
calls["n"] += 1
if calls["n"] == 2:
return _WebhookConn(_conn(self.db_path), existing_row)
return _conn(self.db_path)
return _db
def test_insert_conflict_reads_duplicate(self):
self._project_row("u1")
raw = json.dumps(self._payload()).encode()
h = self._h(raw)
h._db = self._proxy_db(("existing-task",))
with mock.patch.dict(os.environ,
{"GITHUB_WEBHOOK_SECRET": SECRET}):
h._handle_github_webhook()
code, payload = h.sent[-1]
self.assertEqual(code, 200)
self.assertTrue(payload["duplicate"])
self.assertEqual(payload["task_id"], "existing-task")
def test_insert_conflict_reraises(self):
self._project_row("u1")
raw = json.dumps(self._payload()).encode()
h = self._h(raw)
h._db = self._proxy_db(None)
with mock.patch.dict(os.environ,
{"GITHUB_WEBHOOK_SECRET": SECRET}):
with self.assertRaises(sqlite3.IntegrityError):
h._handle_github_webhook()
class MainHarnessTestCase(unittest.TestCase):
def _fake_server(self):
server = SimpleNamespace(
rate_limiter=object(), server_address=("127.0.0.1", 18765))
def _serve_forever():
raise KeyboardInterrupt
server.serve_forever = _serve_forever
return server
def test_main_shared_home_synced(self):
from cloud.server import main
with mock.patch("cloud.server.create_server",
return_value=self._fake_server()), \
mock.patch("cloud.server._resolve_shared_home_mcp",
return_value="/tmp/x"), \
mock.patch("cloud.server._sync_shared_home_mcp",
return_value={"copied": ["f"],
"refreshed": []}):
main(["--port", "18765"])
def test_main_shared_home_failure_swallowed(self):
from cloud.server import main
with mock.patch("cloud.server.create_server",
return_value=self._fake_server()), \
mock.patch("cloud.server._resolve_shared_home_mcp",
side_effect=RuntimeError("resolve down")):
main(["--port", "18765"])
+636
View File
@@ -0,0 +1,636 @@
"""Coverage batch v2: check_transplant.py in-process tests.
Why this file exists: the existing test_skill_transplant_prep.py
drives the script through subprocess, which coverage cannot see --
so the tool measured 11.4% (31/271) despite being behaviour-tested.
These tests import the module in-process and call the same code
paths directly, with real assertions on drafts and error strings
(the subprocess tests stay untouched and still pass).
Fixtures are the same construction as test_skill_transplant_prep
(built from the script's own fragment constants, not copies of them),
plus small variants that trip each loud-failure branch.
"""
import contextlib
import importlib.util
import io
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
SKILL_DIR = REPO_ROOT / "cloud" / "skills" / "software-engineering"
SCRIPT = SKILL_DIR / "tools" / "check_transplant.py"
def _load_script():
spec = importlib.util.spec_from_file_location(
"check_transplant_v2cov", str(SCRIPT))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
CT = _load_script()
def _source_fixture():
m = CT
parts = [
"---\nname: software-engineering\n---\n",
"## 角色线程矩阵\n",
"| 角色 | 线程数 | 负责目录 | 产出 |",
"| QA / 测试 | 1 | `tests/` | 测试用例 |",
"| 独立审计 | 1 | 只写审计记录 | 审计结论 |",
"| 验收 / 发布 | 协调员兼任 | `docs/release/` | 验收清单 |",
"## 失败、续跑与成本\n",
"- 成本控制:分阶段本身就是成本控制。",
"## 独立审计与自动推回\n",
"本节是审计加固第 3 步的技能机制,与代码 `cloud/audit_pushback.py`",
"(推回决策纯函数)双轨落地,只写文档不算落地。只补审计机制,",
"不改上面已有规则的语义:G5 缺陷回路、合并拆分、G6 用户验收",
"的原意都不变。" + m.AUDIT_WARNING,
"### 独立审计角色:不写代码,只独立重跑、抽查证据、单独签字\n",
"- 审计线程不写业务代码、不改待办、不给合并决定、不执行合并。",
"### 自动推回:审计不过关,系统自动推回,不等人工点头\n",
"- 推回上限与升级:推回修复最多三轮" + m.PUSHBACK_LIMIT_PAREN + "。",
"- 与已有回路的关系:G5 缺陷回路复用为修复执行通道,推回派生的修复子任务",
" 就按 G5 方式执行,只补“自动触发 + 合并发布阻塞”这两块缺失;",
m.G7_FULLAUTO_BLOCK,
"## 推倒重做:超过三次后的升级分支(不是并行回路)\n",
"- 与上一节自动推回的关系再写一遍以免混淆:自动推回(1–3 轮修补)",
" 在前、推倒重做(超过 3 次)在后,是同一链路的前后两段;",
" 与 G5 缺陷回路的关系是复用修复执行通道(重做任务本身仍按",
" 阶段派生、仍走 G5 回归复核)" + m.REBUILD_TAIL,
"## 三条收紧:分流、保留资产、审计的审计\n",
"- 收紧一:阈值不能死板,第三次失败先判根因。",
m.TIGHTEN3_NOTIFY,
"## QA 全链路:测试环节跑通各个和所有业务链路\n",
"本节是“独立审计与 QA”部分的补充验收标准,与自动推回、推倒重做、",
"三条收紧并列,不覆盖已有规则。" + m.QA_INTRO,
"### 每个客户端交付前自测:三项缺一不算自测完\n",
"- 单元测试 + 对照契约的集成测试 + 手工冒烟清单,三项缺一不算自测完。",
"## 诚实边界:原生客户端\n",
"- 在只有 Linux 主机的环境里,真原生 iOS 需要 Xcode。",
"## 校验(协调员每过一个门禁自查一次)\n",
"- 分阶段走了吗?G3.5 没过就开了 G4b / G4c 吗?",
"- 审计与推回走了吗?审计签字人是否独立于执行人、协调员批准人、",
" QA 测试人?安全签字是不是无条件通过?审计 fail 后有没有自动推回",
" 并阻塞合并/发布?模块失败超过 3 次后有没有按推倒重做分支处理",
" (先判根因、只推倒该模块、旧实现归档、避坑清单带进新包),",
" 而不是无限修补或悄悄放行?",
"- QA 全链路走了吗?每端自测三项齐了吗?PRD 验收标准全有对应用例吗?",
" agent 专属六项齐了吗?跨端矩阵覆盖全部已选端了吗,未测端写明原因了吗?",
" 未编译端是不是只写了“代码评审 + Web 核心验证”?QA 矩阵签字与",
" 独立审计签字两个签字位都在且不是同一人吗?推回包里失败链路齐了吗,",
" 重审重跑失败链路了吗?",
"有一项为否,就停在当前门禁,不进入下一阶段。",
"## 附录:角色模板(播种随附版)\n",
"下面是 `templates/roles/` 的随附全文。",
"---",
"## 附录角色:08-shared-core(来源 templates/roles/08-shared-core.md)\n",
"# 角色模板:共享核心\n",
"- 不在共享逻辑里写某端专属代码。",
"---",
"## 附录角色:09-auditor(独立审计,新增)\n",
"# 角色模板:独立审计(1 个线程)\n",
"你是本项目的独立审计线程。",
]
return "\n".join(parts) + "\n"
def _target_fixture(target_id):
markers = " ".join(CT.MARKERS[target_id])
last = ("## 附录角色:10-iteration(来源 templates/roles/10-iteration.md,v0.5 新增)"
if target_id == "v05"
else "## 附录角色:08-shared-core(来源 templates/roles/08-shared-core.md)")
parts = [
"# software-engineering:多客户端软件工程编排\n",
"## 角色线程矩阵\n",
"| 角色 | 线程数 | 负责目录 | 产出 |",
"| QA / 测试 | 1 | `tests/` | 测试用例 |",
"| 验收 / 发布 | 协调员兼任 | `docs/release/` | 验收清单 |",
"## 失败、续跑与成本\n",
"- 成本控制:分阶段本身就是成本控制。",
"## 诚实边界:原生客户端\n",
"- 在只有 Linux 主机的环境里,真原生 iOS 需要 Xcode。",
"## 校验(协调员每过一个门禁自查一次)\n",
"- 分阶段走了吗?G3.5 没过就开了 G4b / G4c 吗?",
"- 标记行:" + markers,
"| G1 | PRD 冻结 | PRD 按 `templates/artifacts/prd-outline.md` 写完,用户故事有验收标准 | 回产品线程改 |",
"问卷模板在 `templates/artifacts/intake-questions.md`。最关键的六问:",
"- 验收清单模板在 `templates/artifacts/acceptance-checklist.md`。",
"按 `templates/artifacts/prd-outline.md` 写一份 PRD,至少包含:",
" 协议模板见 `templates/artifacts/` 下的 agent 任务契约模板。",
"1. 按 `templates/artifacts/acceptance-checklist.md` 生成本项目的验收清单,",
"有一项为否,就停在当前门禁,不进入下一阶段。",
"## 附录:角色模板(播种随附版)\n",
last + "\n",
"# 角色模板:共享核心\n",
"- 不在共享逻辑里写某端专属代码。",
]
return "\n".join(parts) + "\n"
def _main_lines_ending_with_tighten3():
"""Main lines whose last paragraph is the tighten-3 notify text.
Exercises transform_main's v04 else-branch (notify text without a
trailing newline), which the standard fixture cannot reach because
the QA section follows the notify paragraph there. Built as a
lines list directly: transform_main only needs main lines, while
extract_source would also demand matrix/checklist/appendix.
"""
m = CT
return [
"## 独立审计与自动推回",
"前言。" + m.AUDIT_WARNING,
"### 独立审计角色:不写代码,只独立重跑、抽查证据、单独签字",
"### 自动推回:审计不过关,系统自动推回,不等人工点头",
"上限" + m.PUSHBACK_LIMIT_PAREN + "。",
m.G7_FULLAUTO_BLOCK,
"## 推倒重做:超过三次后的升级分支(不是并行回路)",
"关系" + m.REBUILD_TAIL,
"## 三条收紧:分流、保留资产、审计的审计",
m.QA_INTRO,
m.TIGHTEN3_NOTIFY,
]
class ExtractTestCase(unittest.TestCase):
def test_extract_happy(self):
hunks = CT.extract_source(_source_fixture())
self.assertEqual(hunks["matrix_row"], "| 独立审计 | 1 | 只写审计记录 | 审计结论 |")
self.assertEqual(hunks["main"][0], "## 独立审计与自动推回")
self.assertTrue(hunks["audit_item"][0].startswith("- 审计与推回走了吗?"))
self.assertTrue(hunks["audit_item"][-1].endswith("悄悄放行?"))
self.assertTrue(hunks["qa_item"][0].startswith("- QA 全链路走了吗?"))
self.assertTrue(hunks["qa_item"][-1].endswith("重审重跑失败链路了吗?"))
self.assertEqual(hunks["appendix_body"][0], "# 角色模板:独立审计(1 个线程)")
def test_extract_matrix_zero_and_two(self):
base = _source_fixture()
no_matrix = base.replace("| 独立审计 | 1 | 只写审计记录 | 审计结论 |\n", "")
with self.assertRaises(ValueError) as ctx:
CT.extract_source(no_matrix)
self.assertIn("auditor matrix row", str(ctx.exception))
two = base.replace("| 验收 / 发布 |",
"| 独立审计 | 1 | 只写审计记录 | 审计结论 |\n| 验收 / 发布 |")
with self.assertRaises(ValueError):
CT.extract_source(two)
def test_extract_main_after_honesty(self):
base = _source_fixture()
# Move the single honesty heading to before the main heading:
# delete the original, then insert one copy ahead of main, so
# both _find_exact calls still find exactly one hit and only
# the order check can fire.
moved = base.replace("## 诚实边界:原生客户端\n", "", 1)
moved = moved.replace("## 独立审计与自动推回\n",
"## 诚实边界:原生客户端\n## 独立审计与自动推回\n", 1)
with self.assertRaises(ValueError) as ctx:
CT.extract_source(moved)
self.assertIn("does not precede", str(ctx.exception))
def test_extract_missing_headings_and_unterminated_item(self):
base = _source_fixture()
with self.assertRaises(ValueError):
CT.extract_source(base.replace("## 独立审计与自动推回\n", "## 其它节\n", 1))
with self.assertRaises(ValueError):
CT.extract_source(base.replace("## 附录角色:09-auditor(独立审计,新增)\n", "", 1))
with self.assertRaises(ValueError):
CT.extract_source(base.replace("- 审计与推回走了吗?", "- 审计走了吗?", 1))
with self.assertRaises(ValueError):
CT.extract_source(base.replace("- QA 全链路走了吗?", "- QA 走了吗?", 1))
unterminated = base.replace("而不是无限修补或悄悄放行?",
"而不是无限修补。")
with self.assertRaises(ValueError) as ctx:
CT.extract_source(unterminated)
self.assertIn("never ends", str(ctx.exception))
def test_extract_trims_trailing_blank_lines_of_main(self):
# Main slice ending in blank lines: the while-pop loop runs
# and the returned main has no trailing blank.
base = _source_fixture()
variant = base.replace("## 诚实边界:原生客户端\n",
"\n\n## 诚实边界:原生客户端\n", 1)
hunks = CT.extract_source(variant)
self.assertNotEqual(hunks["main"][-1], "")
self.assertEqual(hunks["main"][0], "## 独立审计与自动推回")
def test_find_exact_and_lines_and_fail(self):
self.assertEqual(CT._lines("a\nb"), ["a", "b"])
lines = ["x", "y", "x"]
with self.assertRaises(ValueError):
CT._find_exact(lines, lambda l: l == "z", "missing")
with self.assertRaises(ValueError):
CT._find_exact(lines, lambda l: l == "x", "duplicated")
err = io.StringIO()
with contextlib.redirect_stderr(err):
self.assertEqual(CT.fail("boom"), 1)
self.assertIn("FAIL: boom", err.getvalue())
class TransformTestCase(unittest.TestCase):
def _main(self, source=None):
return CT.extract_source(source or _source_fixture())["main"]
def test_transform_v03(self):
lines, stopped = CT.transform_main(self._main(), "v03")
text = "\n".join(lines)
self.assertEqual(stopped, [])
self.assertNotIn("G7 热修复回路并行", text)
self.assertNotIn("全自动 CI 自动修复", text)
self.assertIn("验收失败导致的推倒要先通知用户", text)
self.assertIn("## QA 全链路", text)
self.assertNotIn("移植警告", text)
self.assertIn("推回上限独立于其它修复回路", text)
self.assertTrue(text.rstrip().endswith("。") or "三项缺一不算自测完" in text)
def test_transform_v04(self):
lines, stopped = CT.transform_main(self._main(), "v04")
text = "\n".join(lines)
self.assertEqual(len(stopped), 1)
self.assertIn("收紧三", stopped[0])
self.assertNotIn("G7 热修复回路并行", text)
self.assertNotIn("验收失败导致的推倒要先通知用户", text)
self.assertIn("全自动 CI 自动修复并行", text)
self.assertIn("## QA 全链路", text)
def test_transform_v05(self):
lines, stopped = CT.transform_main(self._main(), "v05")
text = "\n".join(lines)
self.assertEqual(len(stopped), 2)
self.assertNotIn("## QA 全链路", text)
self.assertNotIn("验收失败导致的推倒要先通知用户", text)
self.assertNotIn("移植警告", text)
self.assertIn("## 三条收紧", text)
def test_transform_v04_tighten3_without_trailing_newline(self):
lines, stopped = CT.transform_main(
_main_lines_ending_with_tighten3(), "v04")
text = "\n".join(lines)
self.assertEqual(len(stopped), 1)
self.assertNotIn("验收失败导致的推倒要先通知用户", text)
self.assertNotIn("G7 热修复回路并行", text)
def test_transform_unknown_target(self):
with self.assertRaises(ValueError):
CT.transform_main(self._main(), "v99")
def test_transform_fragment_count_errors(self):
base = _source_fixture()
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(
base.replace(CT.G7_FULLAUTO_BLOCK, ""))["main"], "v03")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(
base.replace(CT.PUSHBACK_LIMIT_PAREN, ""))["main"], "v03")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(
base.replace(CT.REBUILD_TAIL, ""))["main"], "v03")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(
base.replace(CT.G7_FULLAUTO_BLOCK, ""))["main"], "v04")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(
base.replace(CT.AUDIT_WARNING, ""))["main"], "v03")
def test_transform_v05_qa_missing_and_tighten3_missing(self):
base = _source_fixture()
no_qa = base.replace("## QA 全链路:测试环节跑通各个和所有业务链路\n", "", 1)
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(no_qa)["main"], "v05")
no_tighten = base.replace(CT.TIGHTEN3_NOTIFY, "")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(no_tighten)["main"], "v05")
with self.assertRaises(ValueError):
CT.transform_main(CT.extract_source(no_tighten)["main"], "v04")
class ApplyTestCase(unittest.TestCase):
def test_apply_all_targets_in_process(self):
for target_id in ("v03", "v04", "v05"):
with self.subTest(target_id=target_id):
draft, report = CT.apply_transplant(
_source_fixture(), _target_fixture(target_id), target_id)
self.assertNotIn("templates/artifacts", draft)
self.assertEqual(draft.count("| 独立审计 |"), 1)
self.assertEqual(
CT.check_transplanted(_target_fixture(target_id),
draft, target_id), [])
number = "11" if target_id == "v05" else "09"
self.assertIn("## 附录角色:%s-auditor" % number, draft)
self.assertTrue(any("matrix row" in line for line in report))
if target_id == "v03":
self.assertIn("验收失败导致的推倒要先通知用户", draft)
self.assertNotIn("G7 热修复回路并行", draft)
if target_id == "v04":
self.assertIn("STOPPED", "\n".join(report))
if target_id == "v05":
self.assertNotIn("## QA 全链路", draft)
self.assertNotIn("09-auditor", draft)
def test_apply_error_branches(self):
base_t = _target_fixture("v03")
base_s = _source_fixture()
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, base_t.replace(
"| QA / 测试 |", "| QA |"), "v03")
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, base_t.replace(
"## 诚实边界:原生客户端", "## 诚实分界线", 1), "v03")
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, base_t.replace(
"有一项为否", "有一项不对", 1), "v03")
already = base_t.replace(
"## 附录:角色模板(播种随附版)",
"## 附录角色:09-auditor(来源 x)\n## 附录:角色模板(播种随附版)")
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, already, "v03")
duplicated = base_t.replace(
"问卷模板在 `templates/artifacts/intake-questions.md`。",
"问卷模板在 `templates/artifacts/intake-questions.md`。"
"问卷模板在 `templates/artifacts/intake-questions.md`。")
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, duplicated, "v03")
missing_artifact = base_t.replace(
"问卷模板在 `templates/artifacts/intake-questions.md`。",
"问卷模板已删。")
with self.assertRaises(ValueError):
CT.apply_transplant(base_s, missing_artifact, "v03")
class AnchorsTestCase(unittest.TestCase):
def _anchors(self, source, target, target_id):
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
errors = CT.check_anchors(source, target, target_id)
return errors, buf.getvalue()
def test_anchors_happy_all_targets(self):
for target_id in ("v03", "v04", "v05"):
with self.subTest(target_id=target_id):
errors, out = self._anchors(_source_fixture(),
_target_fixture(target_id),
target_id)
self.assertEqual(errors, [])
self.assertIn("anchors:", out)
def test_anchors_source_unextractable(self):
errors, _out = self._anchors("no hunks here",
_target_fixture("v03"), "v03")
self.assertEqual(len(errors), 1)
self.assertIn("not extractable", errors[0])
def test_anchors_each_error(self):
base_t = _target_fixture("v03")
base_s = _source_fixture()
cases = [
(base_t.replace("| QA / 测试 |", "| QA |"),
"target QA matrix row"),
(base_t.replace("## 诚实边界:原生客户端", "## 诚实分界线", 1),
"target honesty heading"),
(base_t.replace("有一项为否", "有一项不对", 1),
"target checklist end"),
(base_t.replace("## 附录:角色模板(播种随附版)",
"## 附录模板", 1),
"target appendix intro"),
(base_t.replace("## 附录角色:08-shared-core", "## 附录角色:07-x", 1),
"lacks last chapter"),
(base_t.replace("## 附录:角色模板(播种随附版)",
"## 附录角色:09-auditor(来源 x)\n"
"## 附录:角色模板(播种随附版)"),
"already contains"),
(base_t.replace("单端复核", "单端检查", 1),
"preserved marker missing"),
]
for target, needle in cases:
with self.subTest(needle=needle):
errors, _out = self._anchors(base_s, target, "v03")
self.assertTrue(any(needle in e for e in errors),
errors)
class CheckTransplantedTestCase(unittest.TestCase):
def _draft(self, target_id):
draft, _report = CT.apply_transplant(
_source_fixture(), _target_fixture(target_id), target_id)
return draft
def test_check_happy_all_targets(self):
for target_id in ("v03", "v04", "v05"):
with self.subTest(target_id=target_id):
self.assertEqual(
CT.check_transplanted(_target_fixture(target_id),
self._draft(target_id),
target_id), [])
def test_check_marker_and_matrix_errors(self):
draft = self._draft("v03")
target = _target_fixture("v03")
dropped = "\n".join(l for l in draft.split("\n")
if not l.startswith("- 标记行:"))
errors = CT.check_transplanted(target, dropped, "v03")
self.assertTrue(any("preserved marker lost" in e for e in errors))
no_row = draft.replace("| 独立审计 | 1 | 只写审计记录 | 审计结论 |\n", "")
errors = CT.check_transplanted(target, no_row, "v03")
self.assertTrue(any("auditor matrix row" in e for e in errors))
two_rows = draft.replace("| 独立审计 | 1 |",
"| 独立审计 | 1 |\n| 独立审计 | 1 |", 1)
errors = CT.check_transplanted(target, two_rows, "v03")
self.assertTrue(any("auditor matrix row" in e for e in errors))
def test_check_main_header_missing(self):
draft = self._draft("v03")
target = _target_fixture("v03")
for header in CT.MAIN_HEADERS:
with self.subTest(header=header):
mutated = draft.replace(header + "\n", "", 1)
errors = CT.check_transplanted(target, mutated, "v03")
self.assertTrue(any("main header missing" in e
for e in errors), errors)
def test_check_stopped_paragraphs_per_target(self):
# v03: QA must be present, G7/full-auto absent, tighten3 present.
draft = self._draft("v03")
target = _target_fixture("v03")
no_qa = draft.replace("## QA 全链路:测试环节跑通各个和所有业务链路\n", "", 1)
self.assertTrue(any("QA section missing" in e for e in
CT.check_transplanted(target, no_qa, "v03")))
with_g7 = draft + "\nG7 热修复回路并行\n"
self.assertTrue(any("STOPPED G7" in e for e in
CT.check_transplanted(target, with_g7, "v03")))
with_fullauto = draft + "\n全自动 CI 自动修复\n"
self.assertTrue(any("STOPPED full-auto" in e for e in
CT.check_transplanted(target, with_fullauto, "v03")))
no_tighten = draft.replace(CT.TIGHTEN3_NOTIFY, "")
self.assertTrue(any("tighten3 notify missing" in e for e in
CT.check_transplanted(target, no_tighten, "v03")))
# v04: QA present, G7/tighten3 absent.
draft4 = self._draft("v04")
target4 = _target_fixture("v04")
no_qa4 = draft4.replace("## QA 全链路:测试环节跑通各个和所有业务链路\n", "", 1)
self.assertTrue(any("QA section missing" in e for e in
CT.check_transplanted(target4, no_qa4, "v04")))
with_tighten4 = draft4 + "\n" + CT.TIGHTEN3_NOTIFY + "\n"
self.assertTrue(any("STOPPED tighten3" in e for e in
CT.check_transplanted(target4, with_tighten4, "v04")))
with_g7_4 = draft4 + "\nG7 热修复回路并行\n"
self.assertTrue(any("STOPPED G7" in e for e in
CT.check_transplanted(target4, with_g7_4, "v04")))
# v05: QA section/item and tighten3 must all be absent.
draft5 = self._draft("v05")
target5 = _target_fixture("v05")
with_qa5 = draft5 + "\n## QA 全链路:测试环节跑通各个和所有业务链路\n"
self.assertTrue(any("STOPPED QA section" in e for e in
CT.check_transplanted(target5, with_qa5, "v05")))
with_qa_item5 = draft5 + "\n- QA 全链路走了吗?\n"
self.assertTrue(any("STOPPED QA checklist" in e for e in
CT.check_transplanted(target5, with_qa_item5, "v05")))
with_tighten5 = draft5 + "\n" + CT.TIGHTEN3_NOTIFY + "\n"
self.assertTrue(any("STOPPED tighten3" in e for e in
CT.check_transplanted(target5, with_tighten5, "v05")))
def test_check_appendix_stale_artifacts_and_no_overwrite(self):
draft = self._draft("v03")
target = _target_fixture("v03")
no_heading = draft.replace(
"## 附录角色:09-auditor(来源 templates/roles/09-auditor.md)",
"## 附录角色:09-auditor(来源 templates/roles/09-auditor2.md)")
self.assertTrue(any("appendix heading" in e for e in
CT.check_transplanted(target, no_heading, "v03")))
old_form = draft + "\n09-auditor(独立审计,新增)\n"
self.assertTrue(any("old appendix heading" in e for e in
CT.check_transplanted(target, old_form, "v03")))
for stale in ("移植警告", "以生产 v0.2 技能为准", "技能正文 diff 全是加法"):
with self.subTest(stale=stale):
mutated = draft + "\n%s\n" % stale
self.assertTrue(any("stale transplant warning" in e for e in
CT.check_transplanted(target, mutated, "v03")))
with_artifact = draft + "\n见 `templates/artifacts/x.md`。\n"
self.assertTrue(any("templates/artifacts" in e for e in
CT.check_transplanted(target, with_artifact, "v03")))
# No-overwrite: drop one target line that no other check pins.
dropped_honesty = draft.replace("## 诚实边界:原生客户端\n", "", 1)
self.assertTrue(any("no-overwrite" in e for e in
CT.check_transplanted(target, dropped_honesty, "v03")))
lines = draft.split("\n")
idx_a = lines.index("## 失败、续跑与成本")
idx_b = lines.index("## 诚实边界:原生客户端")
lines[idx_a], lines[idx_b] = lines[idx_b], lines[idx_a]
reordered = "\n".join(lines)
self.assertTrue(any("no-overwrite" in e for e in
CT.check_transplanted(target, reordered, "v03")))
class MainCliTestCase(unittest.TestCase):
def _write(self, tmp, source, target, target_id="v03"):
src = Path(tmp) / "source.md"
tgt = Path(tmp) / "target.md"
src.write_text(source, encoding="utf-8")
tgt.write_text(target, encoding="utf-8")
return src, tgt
def _run(self, argv):
out, err = io.StringIO(), io.StringIO()
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
code = CT.main(argv)
return code, out.getvalue(), err.getvalue()
def test_main_anchors_ok_and_failed(self):
with tempfile.TemporaryDirectory() as tmp:
src, tgt = self._write(tmp, _source_fixture(),
_target_fixture("v03"))
code, out, _err = self._run(["anchors", "--source244", str(src),
"--target", str(tgt),
"--target-id", "v03"])
self.assertEqual(code, 0)
self.assertIn("anchors OK", out)
bad_tgt = Path(tmp) / "bad.md"
bad_tgt.write_text(_target_fixture("v03").replace(
"| QA / 测试 |", "| QA |"), encoding="utf-8")
code, _out, err = self._run(["anchors", "--source244", str(src),
"--target", str(bad_tgt),
"--target-id", "v03"])
self.assertEqual(code, 1)
self.assertIn("FAILED", err)
def test_main_apply_out_and_stdout(self):
with tempfile.TemporaryDirectory() as tmp:
src, tgt = self._write(tmp, _source_fixture(),
_target_fixture("v04"))
out_path = Path(tmp) / "draft.md"
code, _out, err = self._run(["apply", "--source244", str(src),
"--target", str(tgt),
"--target-id", "v04",
"--out", str(out_path)])
self.assertEqual(code, 0)
self.assertIn("STOPPED", err)
self.assertIn("## 附录角色:09-auditor", out_path.read_text())
code, out, _err = self._run(["apply", "--source244", str(src),
"--target", str(tgt),
"--target-id", "v04"])
self.assertEqual(code, 0)
self.assertIn("## 附录角色:09-auditor", out)
def test_main_check_transplanted_ok_and_failed(self):
with tempfile.TemporaryDirectory() as tmp:
src, tgt = self._write(tmp, _source_fixture(),
_target_fixture("v05"))
draft, _report = CT.apply_transplant(_source_fixture(),
_target_fixture("v05"), "v05")
draft_path = Path(tmp) / "draft.md"
draft_path.write_text(draft, encoding="utf-8")
code, out, _err = self._run(["check-transplanted",
"--source244", str(src),
"--target", str(tgt),
"--target-id", "v05",
"--transplanted", str(draft_path)])
self.assertEqual(code, 0)
self.assertIn("check-transplanted OK", out)
draft_path.write_text(draft + "\n见 `templates/artifacts/x.md`。\n",
encoding="utf-8")
code, _out, err = self._run(["check-transplanted",
"--source244", str(src),
"--target", str(tgt),
"--target-id", "v05",
"--transplanted", str(draft_path)])
self.assertEqual(code, 1)
self.assertIn("FAILED", err)
def test_main_read_error_and_value_error_are_code_2(self):
with tempfile.TemporaryDirectory() as tmp:
src, tgt = self._write(tmp, _source_fixture(),
_target_fixture("v03"))
code, _out, err = self._run(["anchors", "--source244",
str(Path(tmp) / "missing.md"),
"--target", str(tgt),
"--target-id", "v03"])
self.assertEqual(code, 2)
self.assertIn("cannot read input", err)
bad_src = Path(tmp) / "bad.md"
bad_src.write_text("no hunks here", encoding="utf-8")
code, _out, _err = self._run(["apply", "--source244", str(bad_src),
"--target", str(tgt),
"--target-id", "v03"])
self.assertEqual(code, 2)
def test_main_argparse_errors_exit_2(self):
# argparse writes usage to stderr itself; swallow it so the
# full-suite log stays readable.
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit) as ctx:
CT.main(["apply", "--source244", "s", "--target", "t",
"--target-id", "v99"])
self.assertEqual(ctx.exception.code, 2)
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit) as ctx:
CT.main([])
self.assertEqual(ctx.exception.code, 2)
if __name__ == "__main__":
unittest.main()
+240
View File
@@ -0,0 +1,240 @@
"""Coverage batch v2 (E1): cloud/wait_ops.py pure helpers -- row
access, handoff validation, pr-check parsing, wait polling cores,
diagnostics assembly. All direct calls; no production code touched.
"""
import base64
import json
import sqlite3
import sys
import threading
import unittest
from pathlib import Path
from unittest import mock
REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
from cloud.wait_ops import (
_normalize_json_checks,
_row_dict,
build_pr_diagnostics,
create_handoff,
decode_wait_cursor,
detect_significant_change,
get_handoff,
handoff_to_dict,
interruptible_sleep,
parse_pr_checks,
poll_for_first_change,
poll_task_statuses,
report_handoff,
row_get,
significant_now,
worktree_status,
)
class RowHelpersTestCase(unittest.TestCase):
def test_row_get_fallbacks(self):
self.assertIsNone(row_get(None, "x"))
self.assertIsNone(row_get({}, "missing"))
self.assertEqual(row_get({"a": 1}, "a"), 1)
def test_row_dict_none_and_tuple(self):
self.assertIsNone(_row_dict(None, ["id", "x"]))
self.assertEqual(_row_dict(("a", "b"), ["id", "x"]),
{"id": "a", "x": "b"})
def test_handoff_progress_non_int_zero(self):
d = handoff_to_dict({"id": "h", "progress": "abc"})
self.assertEqual(d["progress"], 0)
def test_handoff_progress_clamped(self):
d = handoff_to_dict({"id": "h", "progress": 150})
self.assertEqual(d["progress"], 100)
class HandoffValidationTestCase(unittest.TestCase):
def test_create_target_too_long(self):
with self.assertRaises(ValueError):
create_handoff(None, "t", "p", "x" * 201, "u")
def test_create_detail_not_string(self):
with self.assertRaises(ValueError):
create_handoff(None, "t", "p", "host", "u", detail=123)
def test_create_detail_too_long(self):
with self.assertRaises(ValueError):
create_handoff(None, "t", "p", "host", "u",
detail="x" * 2001)
def test_create_valid(self):
conn = sqlite3.connect(":memory:")
d = create_handoff(conn, "t1", "p1", " host-b ", "u1")
self.assertEqual(d["status"], "pending")
self.assertEqual(d["target_host"], "host-b")
conn.close()
def test_get_missing_none(self):
conn = sqlite3.connect(":memory:")
self.assertIsNone(get_handoff(conn, "ghost"))
conn.close()
def test_report_bad_status(self):
ok, err = report_handoff(None, "h", "bogus")
self.assertFalse(ok)
self.assertIn("status must be one of", err)
def test_report_progress_not_int(self):
ok, err = report_handoff(None, "h", "running", progress=True)
self.assertFalse(ok)
self.assertIn("must be an integer", err)
def test_report_detail_not_string(self):
ok, err = report_handoff(None, "h", "running", detail=123)
self.assertFalse(ok)
self.assertIn("must be a string", err)
def test_report_detail_too_long(self):
ok, err = report_handoff(None, "h", "running",
detail="x" * 2001)
self.assertFalse(ok)
self.assertIn("too long", err)
class ParseChecksTestCase(unittest.TestCase):
def test_parse_header_single_part_nomatch_fallback(self):
checks, all_pass, failed, pending = parse_pr_checks(
"name\tstatus\nonly\t\nhello world\npass\nci\tpassing")
names = [(c["name"], c["state"]) for c in checks]
self.assertIn(("pass", "pass"), names)
self.assertIn(("ci", "pass"), names)
self.assertTrue(all_pass)
def test_worktree_status_unknown(self):
s = worktree_status("paused", "/tmp/x", False, False)
self.assertEqual(s["status"], "unknown")
class WaitCoresTestCase(unittest.TestCase):
def test_sleep_stop_event(self):
event = threading.Event()
event.set()
slept, interrupted = interruptible_sleep(5, event)
self.assertEqual(slept, 0.0)
self.assertTrue(interrupted)
def test_sleep_full(self):
slept, interrupted = interruptible_sleep(0.05)
self.assertAlmostEqual(slept, 0.05, places=2)
self.assertFalse(interrupted)
def test_poll_statuses_bad_timeout(self):
all_terminal, statuses, waited, timed_out = poll_task_statuses(
lambda: {}, timeout_seconds="bad")
self.assertTrue(all_terminal)
def test_cursor_empty(self):
with self.assertRaises(ValueError):
decode_wait_cursor("")
def test_cursor_not_dict(self):
cur = base64.urlsafe_b64encode(b"[1]").decode()
with self.assertRaises(ValueError):
decode_wait_cursor(cur)
def test_cursor_value_not_dict(self):
cur = base64.urlsafe_b64encode(
json.dumps({"a": 1}).encode()).decode()
with self.assertRaises(ValueError):
decode_wait_cursor(cur)
def test_significant_dirty_numbers(self):
self.assertIsNone(significant_now(
{"status": "running", "pending_approvals": "x",
"pending_input": "y", "pending_followups": "z"}))
def test_significant_needs_approval(self):
self.assertEqual(significant_now(
{"status": "running", "pending_approvals": 1,
"pending_input": 0, "pending_followups": 0}),
"needs_approval")
def test_significant_all_clear(self):
self.assertIsNone(significant_now(
{"status": "running", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 0}))
def test_detect_non_dict_current(self):
self.assertIsNone(detect_significant_change(
None, {"t1": "notadict"}, ["t1"]))
def test_detect_needs_approval(self):
base = {"t1": {"status": "running", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 0}}
cur = {"t1": {"status": "running", "pending_approvals": 1,
"pending_input": 0, "pending_followups": 0}}
task_id, kind, detail = detect_significant_change(
base, cur, ["t1"])
self.assertEqual((task_id, kind), ("t1", "needs_approval"))
def test_detect_needs_continue(self):
base = {"t1": {"status": "running", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 0}}
cur = {"t1": {"status": "running", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 2}}
task_id, kind, detail = detect_significant_change(
base, cur, ["t1"])
self.assertEqual((task_id, kind), ("t1", "needs_continue"))
def test_poll_first_bad_timeout_immediate(self):
fetch = lambda: {"t1": {"status": "completed",
"pending_approvals": 0,
"pending_input": 0,
"pending_followups": 0}}
changed, summaries, waited, timed_out = poll_for_first_change(
fetch, ["t1"], timeout_seconds="bad")
self.assertEqual(changed["kind"], "terminal")
self.assertFalse(timed_out)
def test_poll_first_loop_finds_change(self):
seq = [{"t1": {"status": "running", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 0}},
{"t1": {"status": "completed", "pending_approvals": 0,
"pending_input": 0, "pending_followups": 0}}]
it = iter(seq)
fetch = lambda: next(it, seq[-1])
changed, summaries, waited, timed_out = poll_for_first_change(
fetch, ["t1"], timeout_seconds=5, poll_seconds=0.01)
self.assertEqual(changed["kind"], "terminal")
self.assertFalse(timed_out)
class DiagnosticsTestCase(unittest.TestCase):
def test_normalize_non_list(self):
self.assertEqual(_normalize_json_checks("x"), [])
def test_normalize_skips_nameless(self):
checks = _normalize_json_checks(
[{"state": "pass"}, {"name": "ci", "state": "pass"}])
self.assertEqual([c["name"] for c in checks], ["ci"])
def test_build_both_routes_fail(self):
def run(cmd):
return (1, "", "boom")
with self.assertRaises(RuntimeError):
build_pr_diagnostics(run, 7)
def test_build_check_runs_fetch_failed(self):
def run(cmd):
if "--json" in cmd:
return (0, json.dumps(
[{"name": "ci", "state": "fail"}]), "")
return (0, "", "")
with mock.patch("cloud.wait_ops._fetch_failed_check_runs",
side_effect=RuntimeError("api down")):
diag = build_pr_diagnostics(run, 7, owner_repo="o/r",
head_sha="deadbeef")
self.assertFalse(diag["check_runs"]["available"])
self.assertIn("api down", diag["check_runs"]["note"])
+706
View File
@@ -0,0 +1,706 @@
"""Coverage batch v2: cloud/worker.py _deliver_marker_tools branches.
Baseline left 93 lines of _deliver_marker_tools uncovered: quota /
DB exception paths, automation bind/create failures, control
fallbacks, todo/task/cost/quota/files branches, poll/wait
exceptions and the peer-message delivery error. The harness mirrors
test_marker_tools.WorkerDeliverTestCase (temp SQLite via init_db,
sender PM created by tester/maintainer).
Fault injection notes (honest about what is stubbed and why):
- Collaborator helpers (_ensure_result_columns, _todo_list_db,
_poll_task_snapshot, _wait_task_targets, _db_connect, CostStore,
_marker_ancestry, get_task, cancel, _audit) are patched to
simulate a failure the real code must report, never to fake the
deliver report itself; every assertion reads the real followup
body or the real DB row afterwards.
- The quota owner-from-parent fallback (sender without created_by)
is defensive code: the real _marker_auth returns False for a
creator-less sender, so the fallback is only reachable when auth
is stubbed True. Those two tests say so in their docstrings.
- Root bypasses file permission bits, so the dir-list and file-read
failure branches are reached with a selective patch (listdir /
open raising for exactly the target path), not chmod.
"""
import os
import sqlite3
import sys
import tempfile
import time
import unittest
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.server import init_db
from cloud.todos import create_todo as _create_todo
from cloud.worker import Worker
FAKE_ENV = {"XCM_API_KEY": "test-placeholder"}
def _base_result(**over):
result = {
"status": "completed",
"task_creates": [], "task_polls": [], "wait_threads": [],
"task_cancels": [], "task_retries": [], "todo_lists": [],
"todo_creates": [], "todo_updates": [], "task_lists": [],
"task_costs": [], "quota_status": [], "task_files": [],
"goal_converts": [], "automation_creates": [],
}
result.update(over)
return result
class DeliverBase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
self._insert("PM", status="running", created_by="tester")
self._member("tester", "maintainer")
def tearDown(self):
self.tmp.cleanup()
def _insert(self, task_id, status="queued", created_by="tester",
project_id="p1", parent=None, **cols):
now = time.time()
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"INSERT INTO tasks(id, project_id, prompt, status,"
" idempotency_key, depends_on, priority, created_by,"
" created_at, updated_at, last_heartbeat)"
" VALUES(?, ?, ?, ?, NULL, '[]', 0, ?, ?, ?, NULL)",
(task_id, project_id, "do work", status, created_by,
now, now))
if parent is not None:
conn.execute(
"UPDATE tasks SET parent_task_id = ? WHERE id = ?",
(parent, task_id))
for key, value in cols.items():
conn.execute(
"UPDATE tasks SET %s = ? WHERE id = ?" % key,
(value, task_id))
conn.commit()
finally:
conn.close()
def _member(self, user_id, role):
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"CREATE TABLE IF NOT EXISTS users(id TEXT PRIMARY KEY,"
" username TEXT UNIQUE, api_key_hash TEXT,"
" created_at REAL)")
conn.execute(
"CREATE TABLE IF NOT EXISTS project_members("
"project_id TEXT, user_id TEXT, role TEXT,"
" PRIMARY KEY(project_id, user_id))")
conn.execute(
"INSERT OR REPLACE INTO project_members"
"(project_id, user_id, role) VALUES('p1', ?, ?)",
(user_id, role))
conn.execute(
"INSERT OR IGNORE INTO users(id, username, api_key_hash,"
" created_at) VALUES(?, ?, '', 0)", (user_id, user_id))
conn.commit()
finally:
conn.close()
def _worker(self, **kwargs):
return Worker(self.db_path, "/fake/host.py", env=FAKE_ENV,
**kwargs)
def _sender(self, worker, task_id="PM"):
return worker.get_task(task_id)
def _followup_bodies(self, task_id="PM"):
conn = sqlite3.connect(self.db_path)
try:
rows = conn.execute(
"SELECT body FROM task_followups"
" WHERE task_id = ?", (task_id,)).fetchall()
return [row[0] for row in rows]
finally:
conn.close()
def _status(self, task_id):
conn = sqlite3.connect(self.db_path)
try:
row = conn.execute(
"SELECT status, result_text FROM tasks WHERE id = ?",
(task_id,)).fetchone()
return row[0], row[1]
finally:
conn.close()
def _deliver(self, worker, sender, result, task_id="PM"):
worker._deliver_marker_tools(sender, result)
return self._followup_bodies(task_id)
class CreatesAndAutomationTestCase(DeliverBase):
def test_creates_quota_check_exception(self):
worker = self._worker()
sender = self._sender(worker)
with mock.patch.object(worker, "_quota_exceeded",
side_effect=RuntimeError("boom")):
bodies = self._deliver(worker, sender, _base_result(
task_creates=[{"name": "E1", "prompt": "do e1",
"model": None, "effort": None,
"executor": None}]))
self.assertTrue(any("quota_check_failed" in b for b in bodies))
def test_creates_db_exception(self):
worker = self._worker()
sender = self._sender(worker)
with mock.patch.object(worker, "_ensure_result_columns",
side_effect=RuntimeError("db down")):
bodies = self._deliver(worker, sender, _base_result(
task_creates=[{"name": "E1", "prompt": "do e1",
"model": None, "effort": None,
"executor": None}]))
self.assertTrue(any("创建失败" in b for b in bodies))
def test_automation_first_run_past_skipped(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
automation_creates=[{"name": "auto1", "prompt": "do auto",
"interval_seconds": 3600,
"first_run_at": time.time() - 3600}]))
self.assertTrue(any("首次运行时刻已过去" in b for b in bodies))
def test_automation_duplicate_name_second_fails(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
automation_creates=[
{"name": "dup", "prompt": "do auto",
"interval_seconds": 3600},
{"name": "dup", "prompt": "do auto again",
"interval_seconds": 3600}]))
joined = "\n".join(bodies)
self.assertIn("已创建自动化", joined)
self.assertIn("自动化创建失败", joined)
self.assertIn("already exists", joined)
def test_automation_bind_bad_mode_reports_but_counts(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
automation_creates=[{"name": "b1", "prompt": "do auto",
"interval_seconds": 3600,
"bind": "bogus"}]))
joined = "\n".join(bodies)
self.assertIn("绑定到本会话失败", joined)
self.assertIn("定义本身仍有效", joined)
conn = sqlite3.connect(self.db_path)
try:
row = conn.execute(
"SELECT name FROM automations WHERE name = 'b1'"
).fetchone()
finally:
conn.close()
self.assertIsNotNone(row)
def test_automation_bind_heartbeat_success(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
automation_creates=[{"name": "hb1", "prompt": "do auto",
"interval_seconds": 3600,
"bind": "heartbeat"}]))
self.assertTrue(any("已绑到本会话唤醒" in b for b in bodies))
def test_automation_quota_exception(self):
worker = self._worker()
sender = self._sender(worker)
with mock.patch.object(worker, "_quota_exceeded",
side_effect=RuntimeError("boom")):
bodies = self._deliver(worker, sender, _base_result(
automation_creates=[{"name": "q1", "prompt": "do auto",
"interval_seconds": 3600}]))
joined = "\n".join(bodies)
self.assertIn("自动化创建跳过", joined)
self.assertIn("quota_check_failed", joined)
class ControlFallbackTestCase(DeliverBase):
def test_cancel_target_read_failure(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
sender = self._sender(worker)
# _was_cancelled also calls get_task, so only the target read
# itself may fail; everything else delegates to the real one.
orig_get = worker.get_task
def _flaky_get(task_id):
if task_id == "E1":
raise RuntimeError("db down")
return orig_get(task_id)
with mock.patch.object(worker, "get_task",
side_effect=_flaky_get):
bodies = self._deliver(worker, sender, _base_result(
task_cancels=[{"task_id": "E1"}]))
self.assertTrue(any("读取失败" in b for b in bodies))
self.assertEqual(self._status("E1")[0], "running")
def test_cancel_ancestry_failure_falls_back_to_other(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
with mock.patch("cloud.worker._marker_ancestry",
side_effect=RuntimeError("db down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_cancels=[{"task_id": "E1"}]))
self.assertTrue(any("拒绝" in b for b in bodies))
self.assertEqual(self._status("E1")[0], "running")
def test_cancel_not_done_reports(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
with mock.patch.object(worker, "cancel", return_value=False):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_cancels=[{"task_id": "E1"}]))
self.assertTrue(any("未执行" in b for b in bodies))
self.assertEqual(self._status("E1")[0], "running")
def test_cancel_execution_exception(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
with mock.patch.object(worker, "cancel",
side_effect=RuntimeError("boom")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_cancels=[{"task_id": "E1"}]))
self.assertTrue(any("执行失败" in b for b in bodies))
def test_retry_not_done_when_rowcount_zero(self):
self._insert("E1", status="failed", parent="PM")
worker = self._worker()
real_connect = sqlite3.connect
class _CursorProxy:
def __init__(self, real):
self._real = real
self.rowcount = 0
def __getattr__(self, name):
return getattr(self._real, name)
class _ConnProxy:
def __init__(self, real):
self._real = real
def execute(self, sql, params=()):
if "UPDATE tasks SET status = 'queued'" in sql:
return _CursorProxy(self._real.execute(sql, params))
return self._real.execute(sql, params)
def __getattr__(self, name):
return getattr(self._real, name)
import cloud.worker as worker_mod
real_db_connect = worker_mod._db_connect
def _connect(path):
return _ConnProxy(real_db_connect(path))
with mock.patch("cloud.worker._db_connect", side_effect=_connect):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_retries=[{"task_id": "E1"}]))
# The proxy cursor reports rowcount 0, so deliver must report
# "not executed" -- but the real UPDATE still ran through the
# proxy's delegate, so the test asserts the report, not the
# row, for this TOCTOU simulation.
self.assertTrue(any("未执行" in b for b in bodies))
def test_retry_execution_exception_via_audit(self):
self._insert("E1", status="failed", parent="PM")
worker = self._worker()
sender = self._sender(worker)
orig_audit = worker._audit
calls = [0]
def _flaky_audit(project_id, action, detail=""):
calls[0] += 1
if calls[0] == 1:
raise RuntimeError("audit down")
return orig_audit(project_id, action, detail)
with mock.patch.object(worker, "_audit", side_effect=_flaky_audit):
bodies = self._deliver(worker, sender, _base_result(
task_retries=[{"task_id": "E1"}]))
self.assertTrue(any("执行失败" in b for b in bodies))
class TodoTestCase(DeliverBase):
def test_todo_create_write_denied(self):
self._member("tester", "viewer")
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
todo_creates=[{"title": "T1"}]))
self.assertTrue(any("创建待办" in b and "拒绝" in b for b in bodies))
def test_todo_create_cross_project_task(self):
self._insert("X1", status="running", project_id="p2")
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
todo_creates=[{"title": "T1", "task_id": "X1"}]))
self.assertTrue(any("属于别的项目" in b for b in bodies))
def test_todo_create_validation_failure(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
todo_creates=[{"title": ""}]))
self.assertTrue(any("创建待办" in b and "失败" in b for b in bodies))
def test_todo_update_cross_project_task(self):
self._insert("X1", status="running", project_id="p2")
conn = sqlite3.connect(self.db_path)
try:
todo = _create_todo(conn, "p1", "tester", "T1")
finally:
conn.close()
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
todo_updates=[{"id": todo["id"],
"fields": {"task_id": "X1"}}]))
self.assertTrue(any("属于别的项目" in b for b in bodies))
def test_todo_update_failure(self):
conn = sqlite3.connect(self.db_path)
try:
todo = _create_todo(conn, "p1", "tester", "T1")
finally:
conn.close()
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
todo_updates=[{"id": todo["id"],
"fields": {"status": "bogus"}}]))
self.assertTrue(any("更新待办" in b and "失败" in b for b in bodies))
def test_todo_list_failure(self):
worker = self._worker()
with mock.patch("cloud.worker._todo_list_db",
side_effect=RuntimeError("db down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(todo_lists=[{}]))
self.assertTrue(any("查待办失败" in b for b in bodies))
def test_todo_list_truncation(self):
conn = sqlite3.connect(self.db_path)
try:
for i in range(21):
_create_todo(conn, "p1", "tester", "T%d" % i)
finally:
conn.close()
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker),
_base_result(todo_lists=[{}]))
self.assertTrue(any("还有 1 条未列出" in b for b in bodies))
def test_task_list_failure(self):
worker = self._worker()
sender = self._sender(worker)
# _was_cancelled (first _db_connect use) and the final
# followup insert must still work; only the task_lists query
# itself (second use) fails.
import cloud.worker as worker_mod
real_connect = worker_mod._db_connect
calls = [0]
def _flaky_connect(path):
calls[0] += 1
if calls[0] == 2:
raise RuntimeError("db down")
return real_connect(path)
with mock.patch("cloud.worker._db_connect",
side_effect=_flaky_connect):
bodies = self._deliver(worker, sender,
_base_result(task_lists=[{"limit": 10}]))
self.assertTrue(any("查任务列表失败" in b for b in bodies))
class CostQuotaFilesTestCase(DeliverBase):
def test_task_cost_read_denied(self):
# Viewer still has read permission, so read denial needs a
# sender with no project membership at all.
self._insert("OUT", status="running", created_by="outsider")
self._insert("E1", status="completed", parent="PM")
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker, "OUT"),
_base_result(
task_costs=[{"task_id": "E1"}]),
task_id="OUT")
self.assertTrue(any("创建人无 read 权限" in b for b in bodies))
def test_task_cost_relation_reject(self):
self._insert("S1", status="running", created_by="other")
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_costs=[{"task_id": "S1"}]))
self.assertTrue(any("不在派生链内且创建人不同" in b for b in bodies))
def test_task_cost_ancestry_failure(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
with mock.patch("cloud.worker._marker_ancestry",
side_effect=RuntimeError("db down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_costs=[{"task_id": "E1"}]))
self.assertTrue(any("不在派生链内且创建人不同" in b for b in bodies))
def test_task_cost_store_close_failure_swallowed(self):
self._insert("E1", status="completed", parent="PM",
result_text="done")
worker = self._worker()
class _FakeConn:
def close(self):
raise RuntimeError("close down")
class _FakeStore:
def __init__(self, db_path):
self._conn = _FakeConn()
def report_by_task(self, task_id):
return {"usd_micros": 1234567, "total_tokens": 100,
"actual_tokens": 80, "estimated_tokens": 20,
"turns": 2, "unpriced_actual_tokens": 5}
with mock.patch("cloud.worker.CostStore", _FakeStore):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_costs=[{"task_id": "E1"}]))
joined = "\n".join(bodies)
self.assertIn("成本:$1.2346", joined)
self.assertIn("无价 actual token 5", joined)
def test_task_cost_report_failure(self):
self._insert("E1", status="completed", parent="PM")
worker = self._worker()
class _FailingStore:
def __init__(self, db_path):
self._conn = SimpleNamespace(
close=lambda: None)
def report_by_task(self, task_id):
raise RuntimeError("cost down")
with mock.patch("cloud.worker.CostStore", _FailingStore):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_costs=[{"task_id": "E1"}]))
self.assertTrue(any("成本失败" in b for b in bodies))
def test_quota_owner_from_parent_via_stubbed_auth(self):
# Defensive fallback only: the real _marker_auth returns
# False for a creator-less sender, so this branch is only
# reachable when auth is stubbed True. The test pins the
# fallback logic itself (owner resolved from the parent).
self._insert("ORPH", status="running", created_by=None,
parent="PM")
worker = self._worker()
worker._marker_auth = lambda task, action: (True, "")
sender = self._sender(worker, "ORPH")
bodies = self._deliver(worker, sender,
_base_result(quota_status=[{}]),
task_id="ORPH")
# Owner resolved from the parent (tester, no explicit quota
# row) so the report shows the default quota, not "unlimited".
self.assertTrue(any("配额" in b and "总额 $10.0000" in b
for b in bodies), bodies)
def test_quota_no_owner_via_stubbed_auth(self):
self._insert("ORPH2", status="running", created_by=None)
worker = self._worker()
worker._marker_auth = lambda task, action: (True, "")
sender = self._sender(worker, "ORPH2")
bodies = self._deliver(worker, sender,
_base_result(quota_status=[{}]),
task_id="ORPH2")
self.assertTrue(any("发送方无创建人" in b for b in bodies))
def test_quota_unlimited_explicit_null_row(self):
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
"CREATE TABLE IF NOT EXISTS user_quotas("
"user_id TEXT PRIMARY KEY, quota_usd_micros BIGINT,"
" updated_at DOUBLE PRECISION)")
conn.execute(
"INSERT OR REPLACE INTO user_quotas"
"(user_id, quota_usd_micros, updated_at)"
" VALUES('tester', NULL, 0)")
conn.commit()
finally:
conn.close()
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker),
_base_result(quota_status=[{}]))
self.assertTrue(any("不限额" in b for b in bodies))
def test_quota_limit_failure(self):
worker = self._worker()
with mock.patch.object(worker, "_quota_limit_and_used",
side_effect=RuntimeError("quota down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(quota_status=[{}]))
self.assertTrue(any("查配额失败" in b for b in bodies))
def test_task_files_target_missing(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_files=[{"task_id": "NOPE", "path": ""}]))
self.assertTrue(any("不存在(或属于别的项目)" in b for b in bodies))
def test_task_files_ancestry_failure(self):
self._insert("E1", status="running", parent="PM")
worker = self._worker()
with mock.patch("cloud.worker._marker_ancestry",
side_effect=RuntimeError("db down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_files=[{"task_id": "E1",
"path": ""}]))
self.assertTrue(any("不在派生链内且创建人不同" in b for b in bodies))
class TaskFilesWorktreeTestCase(DeliverBase):
def _worktree_worker(self):
worker = self._worker(tenants_root=self.tmp.name)
worktree = os.path.join(self.tmp.name, "p1", "work",
"host-worktrees", "E1")
os.makedirs(worktree)
return worker, worktree
def test_task_files_dir_and_file_happy(self):
worker, worktree = self._worktree_worker()
Path(worktree, "a.txt").write_text("hello-file", encoding="utf-8")
self._insert("E1", status="running", parent="PM")
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_files=[{"task_id": "E1", "path": ""},
{"task_id": "E1", "path": "a.txt"}]))
joined = "\n".join(bodies)
self.assertIn("a.txt", joined)
self.assertIn("hello-file", joined)
def test_task_files_dir_list_failure(self):
worker, worktree = self._worktree_worker()
Path(worktree, "a.txt").write_text("x", encoding="utf-8")
self._insert("E1", status="running", parent="PM")
with mock.patch("os.listdir", side_effect=OSError("list down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_files=[{"task_id": "E1",
"path": ""}]))
self.assertTrue(any("列任务" in b and "目录失败" in b
for b in bodies))
def test_task_files_file_read_failure(self):
worker, worktree = self._worktree_worker()
target = Path(worktree, "a.txt")
target.write_text("secret", encoding="utf-8")
self._insert("E1", status="running", parent="PM")
real_open = open
def _selective_open(path, *args, **kwargs):
if os.fspath(path) == str(target):
raise OSError("read down")
return real_open(path, *args, **kwargs)
with mock.patch("builtins.open", side_effect=_selective_open):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_files=[{"task_id": "E1",
"path": "a.txt"}]))
self.assertTrue(any("读任务" in b and "失败" in b for b in bodies))
def test_task_files_binary_rejected(self):
worker, worktree = self._worktree_worker()
Path(worktree, "a.bin").write_bytes(b"\x00\x01\x02abc")
self._insert("E1", status="running", parent="PM")
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_files=[{"task_id": "E1", "path": "a.bin"}]))
self.assertTrue(any("二进制文件" in b for b in bodies))
def test_task_files_invalid_utf8_rejected(self):
worker, worktree = self._worktree_worker()
Path(worktree, "bad.bin").write_bytes(b"\xff\xfe\x00abc")
self._insert("E1", status="running", parent="PM")
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_files=[{"task_id": "E1", "path": "bad.bin"}]))
self.assertTrue(any("二进制文件" in b for b in bodies))
def test_task_files_truncation(self):
worker, worktree = self._worktree_worker()
Path(worktree, "big.txt").write_text("x" * 21000, encoding="utf-8")
self._insert("E1", status="running", parent="PM")
bodies = self._deliver(worker, self._sender(worker), _base_result(
task_files=[{"task_id": "E1", "path": "big.txt"}]))
self.assertTrue(any("已截断前 20000 字" in b for b in bodies))
class PollWaitGoalPeerTestCase(DeliverBase):
def test_poll_and_wait_exceptions(self):
worker = self._worker()
with mock.patch.object(worker, "_poll_task_snapshot",
side_effect=RuntimeError("poll down")), \
mock.patch.object(worker, "_wait_task_targets",
side_effect=RuntimeError("wait down")):
bodies = self._deliver(worker, self._sender(worker),
_base_result(
task_polls=[{"task_id": "E1",
"interval_seconds": 5,
"timeout_seconds": 0,
"chars": 2000}],
wait_threads=[{"task_ids": ["E1"],
"interval_seconds": 5,
"timeout_seconds": 0,
"chars": 2000}]))
joined = "\n".join(bodies)
self.assertIn("轮询任务 E1 失败", joined)
self.assertIn("等待任务失败", joined)
def test_goal_converts_queued_after_report(self):
worker = self._worker()
bodies = self._deliver(worker, self._sender(worker), _base_result(
goal_converts=[{"goal": "做目标"}]))
self.assertEqual(bodies, ["/goal 做目标"])
def test_peer_messages_delivery_error(self):
worker = self._worker()
sender = self._sender(worker)
with mock.patch("cloud.worker._deliver_peer_message",
side_effect=RuntimeError("peer down")):
worker._deliver_peer_messages(sender, {
"status": "completed",
"task_messages": [{"to_task_id": "E1", "body": "hi",
"mode": "followup"}]})
conn = sqlite3.connect(self.db_path)
try:
tables = [r[0] for r in conn.execute(
"SELECT name FROM sqlite_master"
" WHERE type = 'table'").fetchall()]
if "audit_log" in tables:
rows = conn.execute(
"SELECT detail FROM audit_log"
" WHERE action = 'worker_peer_messages'").fetchall()
self.assertTrue(any("rejected=1" in (r[0] or "")
for r in rows), rows)
finally:
conn.close()
if __name__ == "__main__":
unittest.main()
+229
View File
@@ -0,0 +1,229 @@
"""Coverage batch v2: host.py shell scanner + classify branches.
Baseline left 48 lines of _scan_shell_command uncovered (NUL,
dquote escapes/backtick, bare $, brace, #-in-word, [-glob, !,
&> / &>>, pending-redirect errors on && / | / ;, >> / >| / <)
plus three classify_verify_command lines (cwd missing, cwd escape
via symlink, script path unvalidated without cwd). Each case below
asserts the real segments/features/denial reason, not just that the
scanner did not crash.
"""
import os
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
import host
from host import _scan_shell_command, classify_verify_command
def _scan(cmd):
return _scan_shell_command(cmd)
def _denied(cmd):
segments, features, denied = _scan(cmd)
return denied
class ScanDenialsTestCase(unittest.TestCase):
def test_nul_and_newlines(self):
self.assertEqual(_denied("echo a\x00b"), "命令含 NUL 字节")
self.assertIn("换行", _denied("echo a\nb"))
self.assertIn("换行", _denied("echo a\rb"))
def test_unclosed_and_command_substitution(self):
self.assertEqual(_denied("echo 'abc"), "引号未闭合")
self.assertEqual(_denied('echo "abc'), "引号未闭合")
self.assertIn("$(...)", _denied("echo $(whoami)"))
self.assertIn("反引号", _denied("echo `whoami`"))
self.assertIn("反引号", _denied('echo "a`whoami`"'))
self.assertIn("${ 未闭合", _denied("echo ${FOO"))
self.assertIn("子 shell", _denied("echo (foo)"))
self.assertIn("花括号", _denied("echo {a,b}"))
self.assertIn("花括号", _denied("echo }"))
self.assertIn("注释", _denied("# echo hi"))
self.assertIn("取反", _denied("! echo hi"))
self.assertIn("后台", _denied("pytest &"))
self.assertIn("here-doc", _denied("cat << EOF"))
def test_pending_redirect_errors(self):
self.assertEqual(_denied("pytest >"), "重定向后缺目标")
self.assertEqual(_denied("pytest > && pytest"), "重定向后缺目标")
self.assertEqual(_denied("pytest > | pytest"), "重定向后缺目标")
self.assertEqual(_denied("pytest > ; pytest"), "重定向后缺目标")
self.assertEqual(_denied("pytest > || pytest"), "重定向后缺目标")
class ScanWordsTestCase(unittest.TestCase):
def test_squote_is_literal(self):
segments, features, denied = _scan("echo 'a$b`c'")
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "a$b`c")
self.assertFalse(segments[0]["words"][1]["expand"])
self.assertNotIn("env", features)
def test_dquote_escape_and_dollar(self):
segments, features, denied = _scan('echo "a\\$b"')
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "a$b")
segments, features, denied = _scan('echo "$FOO"')
self.assertIsNone(denied)
self.assertIn("env", features)
self.assertTrue(segments[0]["words"][1]["expand"])
segments, features, denied = _scan('echo "${FOO}"')
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "${FOO}")
self.assertIn("env", features)
def test_normal_backslash_escape(self):
segments, _features, denied = _scan("echo a\\ b")
self.assertIsNone(denied)
self.assertEqual([w["text"] for w in segments[0]["words"]],
["echo", "a b"])
def test_bare_dollar_is_literal(self):
segments, _features, denied = _scan("echo $%")
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "$%")
segments, _features, denied = _scan("echo $")
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "$")
def test_hash_inside_word_is_literal(self):
segments, _features, denied = _scan("echo foo#bar")
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "foo#bar")
def test_bracket_glob_vs_test_command(self):
segments, features, denied = _scan("echo foo[ab]")
self.assertIsNone(denied)
self.assertIn("glob", features)
self.assertTrue(segments[0]["words"][1]["glob"])
self.assertEqual(segments[0]["words"][1]["text"], "foo[ab]")
segments, features, denied = _scan("[ -f x ]")
self.assertIsNone(denied)
self.assertNotIn("glob", features)
self.assertEqual(segments[0]["words"][0]["text"], "[")
def test_bang_inside_word_is_literal(self):
segments, _features, denied = _scan("echo foo!bar")
self.assertIsNone(denied)
self.assertEqual(segments[0]["words"][1]["text"], "foo!bar")
class ScanOperatorsTestCase(unittest.TestCase):
def test_dollar_error_inside_dquote(self):
self.assertIn("$(...)", _denied('echo "$(whoami)"'))
def test_plain_and_pipe_and_semicolon(self):
segments, features, denied = _scan("pytest && pytest")
self.assertIsNone(denied)
self.assertIn("compound", features)
self.assertEqual(len(segments), 2)
segments, features, denied = _scan("pytest | pytest")
self.assertIsNone(denied)
self.assertIn("pipe", features)
self.assertEqual(len(segments), 2)
segments, features, denied = _scan("pytest ; pytest")
self.assertIsNone(denied)
self.assertIn("compound", features)
self.assertEqual(len(segments), 2)
def test_compound_or(self):
segments, features, denied = _scan("pytest || pytest")
self.assertIsNone(denied)
self.assertIn("compound", features)
self.assertEqual(len(segments), 2)
def test_and_redirect_and_append_and_clobber(self):
segments, features, denied = _scan("echo hi &> out.txt")
self.assertIsNone(denied)
self.assertIn("redirect", features)
self.assertEqual(segments[0]["redirects"][0]["target"], "out.txt")
self.assertFalse(segments[0]["redirects"][0]["fd"])
segments, features, denied = _scan("echo hi &>> out.txt")
self.assertIsNone(denied)
self.assertIn("redirect", features)
self.assertEqual(segments[0]["redirects"][0]["target"], "out.txt")
segments, features, denied = _scan("pytest >> out.txt")
self.assertIsNone(denied)
self.assertEqual(segments[0]["redirects"][0]["target"], "out.txt")
segments, features, denied = _scan("pytest >| out.txt")
self.assertIsNone(denied)
self.assertEqual(segments[0]["redirects"][0]["target"], "out.txt")
segments, features, denied = _scan("cat < in.txt")
self.assertIsNone(denied)
self.assertEqual(segments[0]["redirects"][0]["target"], "in.txt")
self.assertFalse(segments[0]["redirects"][0]["fd"])
def test_fd_redirect(self):
segments, features, denied = _scan("pytest 2>&1")
self.assertIsNone(denied)
self.assertIn("redirect", features)
self.assertEqual(segments[0]["redirects"][0]["target"], "1")
self.assertTrue(segments[0]["redirects"][0]["fd"])
segments, features, denied = _scan("pytest 2> err.txt")
self.assertIsNone(denied)
self.assertEqual(segments[0]["redirects"][0]["target"], "err.txt")
self.assertFalse(segments[0]["redirects"][0]["fd"])
def test_tilde_and_glob_features(self):
_segments, features, denied = _scan("echo ~/x")
self.assertIsNone(denied)
self.assertIn("env", features)
_segments, features, denied = _scan("echo *.py")
self.assertIsNone(denied)
self.assertIn("glob", features)
class ClassifyCwdTestCase(unittest.TestCase):
def test_script_path_unvalidated_without_cwd(self):
result = classify_verify_command("./run.sh", cwd=None)
self.assertTrue(any("未校验" in note for note in result["notes"]),
result)
def test_script_path_cwd_missing(self):
result = classify_verify_command("./run.sh",
cwd="/nonexistent-xyz-abc")
self.assertEqual(result["kind"], "denied")
self.assertIn("cwd 不存在", result["reason"])
def test_script_path_escapes_cwd_via_symlink(self):
with tempfile.TemporaryDirectory() as tmp:
outside = os.path.join(tmp, "outside")
os.mkdir(outside)
target = os.path.join(outside, "x.sh")
Path(target).write_text("#!/bin/sh\n")
os.chmod(target, 0o755)
work = os.path.join(tmp, "work")
os.mkdir(work)
os.symlink(outside, os.path.join(work, "link"))
result = classify_verify_command("link/x.sh", cwd=work)
self.assertEqual(result["kind"], "denied")
self.assertIn("越出 cwd", result["reason"])
def test_script_path_inside_cwd_executable_passes(self):
with tempfile.TemporaryDirectory() as tmp:
script = os.path.join(tmp, "run.sh")
Path(script).write_text("#!/bin/sh\n")
os.chmod(script, 0o755)
result = classify_verify_command("./run.sh", cwd=tmp)
self.assertNotEqual(result["kind"], "denied")
def test_absolute_and_dotdot_paths_denied(self):
self.assertEqual(classify_verify_command("/bin/pytest")["kind"],
"denied")
self.assertIn("绝对路径",
classify_verify_command("/bin/pytest")["reason"])
self.assertEqual(classify_verify_command("../run.sh")["kind"],
"denied")
if __name__ == "__main__":
unittest.main()