"""架构收敛 A 线单测:超时 / 门禁 / 成本配额 / 沙箱清理。 只覆盖本轨新增的四个叶子模块与调用点的委托关系,不跑 模型、不联网、不碰生产:SQLite 内存库 + 临时目录。 钉住的冻结语义(改动这些断言即改语义,必须先改设计): - 审批默认 3600、服务端入口 60..86400、已建项 expires_at 永不因默认值变化而重算;600 秒是单轮护栏,不是审批超时。 - 目录门无 confirm / override / bypass 参数;合并状态门 仅 completed 放行;控制门与旧 marker 行为逐字一致。 - 配额判定唯一来源是 quota.decide:USD 门先于无价门, 命中均为 >=;无价只计 source='actual',估算行不触发上限; 无配额行按默认值,显式 NULL 行才是不限额。 - 失联清理只在 interrupted + 心跳停滞严格超阈时放行; 容器清理只认 codex-<32 位 hex>,绝不按前缀删。 """ import inspect import os import sqlite3 import sys import tempfile import unittest from pathlib import Path from unittest import mock try: from cloud import gates, quota, sandbox_policy, timeouts from cloud.approvals import DEFAULT_TIMEOUT_SECONDS, create_approval from cloud.automations import ( _automation_quota_exceeded, _unpriced_token_cap as _auto_cap, ) from cloud.coordinator import DEFAULT_TIMEOUT_SECONDS as _COORD_TIMEOUT from cloud.cost import unpriced_token_cap as _cost_cap from cloud.harness2_tasks import ( DEFAULT_TASK_TIMEOUT_SECONDS as _HARNESS_TIMEOUT, ) from cloud.marker_tools import ( MAX_POLL_TIMEOUT_SECONDS as _MARKER_POLL_MAX, ) from cloud.marker_tools import check_control_gate, safe_worktree_path from cloud.sandbox import DockerExecutor from cloud.server import _safe_worktree_path as _server_safe_path from cloud.tree import resolve_tree_path from cloud.workers import DEFAULT_ALIVE_SECONDS as _WORKER_ALIVE except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud import gates, quota, sandbox_policy, timeouts from cloud.approvals import DEFAULT_TIMEOUT_SECONDS, create_approval from cloud.automations import ( _automation_quota_exceeded, _unpriced_token_cap as _auto_cap, ) from cloud.coordinator import DEFAULT_TIMEOUT_SECONDS as _COORD_TIMEOUT from cloud.cost import unpriced_token_cap as _cost_cap from cloud.harness2_tasks import ( DEFAULT_TASK_TIMEOUT_SECONDS as _HARNESS_TIMEOUT, ) from cloud.marker_tools import ( MAX_POLL_TIMEOUT_SECONDS as _MARKER_POLL_MAX, ) from cloud.marker_tools import check_control_gate, safe_worktree_path from cloud.sandbox import DockerExecutor from cloud.server import _safe_worktree_path as _server_safe_path from cloud.tree import resolve_tree_path from cloud.workers import DEFAULT_ALIVE_SECONDS as _WORKER_ALIVE def _quota_conn(): """配额测试用的最小库:tasks + cost_records + user_quotas。""" conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row conn.execute("CREATE TABLE tasks(id TEXT PRIMARY KEY," " created_by TEXT)") conn.execute("CREATE TABLE cost_records(task_id TEXT," " usd_micros INTEGER, input_tokens INTEGER," " output_tokens INTEGER, day TEXT, source TEXT)") conn.execute("CREATE TABLE user_quotas(user_id TEXT PRIMARY KEY," " quota_usd_micros BIGINT, updated_at DOUBLE PRECISION)") conn.execute("INSERT INTO tasks(id, created_by) VALUES('t1', 'u1')") conn.commit() return conn def _cost_row(conn, task_id, usd, inp, out, source, day="2026-10-31"): conn.execute( "INSERT INTO cost_records(task_id, usd_micros, input_tokens," " output_tokens, day, source) VALUES(?, ?, ?, ?, ?, ?)", (task_id, usd, inp, out, day, source)) conn.commit() class TimeoutConvergeTestCase(unittest.TestCase): def test_constants_match_old_literals(self): self.assertEqual(timeouts.APPROVAL_DEFAULT_SECONDS, 3600) self.assertEqual(timeouts.APPROVAL_MIN_SECONDS, 60) self.assertEqual(timeouts.APPROVAL_MAX_SECONDS, 86400) self.assertEqual(timeouts.TURN_SECONDS, 600) self.assertEqual(timeouts.STALE_SWEEP_SECONDS, 300) self.assertEqual(timeouts.WORKER_ALIVE_SECONDS, 120) # 各模块别名与单一来源同值(调用点 import 的名字不变)。 self.assertEqual(DEFAULT_TIMEOUT_SECONDS, 3600) self.assertEqual(_COORD_TIMEOUT, timeouts.LONG_TIMEOUT_SECONDS) self.assertEqual(_HARNESS_TIMEOUT, timeouts.LONG_TIMEOUT_SECONDS) self.assertEqual(_MARKER_POLL_MAX, timeouts.LONG_TIMEOUT_SECONDS) self.assertEqual(_WORKER_ALIVE, timeouts.WORKER_ALIVE_SECONDS) def test_server_validation_keeps_old_bounds_and_message(self): for good in (60, 3600, 86400): self.assertEqual(timeouts.validate_server_timeout(good), good) for bad in (None, True, 59, 86401, 3.5, "3600"): with self.assertRaises(ValueError) as ctx: timeouts.validate_server_timeout(bad) self.assertEqual(str(ctx.exception), "timeout_seconds must be 60..86400") def test_module_timeout_semantics(self): self.assertEqual(timeouts.resolve_module_timeout(None), 3600) self.assertIsNone(timeouts.resolve_module_timeout(0)) self.assertEqual(timeouts.resolve_module_timeout(60), 60) for bad in (-1, True, "60"): with self.assertRaises(ValueError): timeouts.resolve_module_timeout(bad) self.assertIsNone(timeouts.expires_at_for(1000, None)) self.assertEqual(timeouts.expires_at_for(1000, 3600), 4600) def test_env_override_and_invalid_fallback(self): with mock.patch.dict(os.environ, {"CLOUD_APPROVAL_TIMEOUT_SECONDS": "1800"}): self.assertEqual(timeouts.approval_default_seconds(), 1800) with mock.patch.dict(os.environ, {"CLOUD_APPROVAL_TIMEOUT_SECONDS": "30"}): self.assertEqual(timeouts.approval_default_seconds(), 3600) with mock.patch.dict(os.environ, {"CLOUD_APPROVAL_TIMEOUT_SECONDS": "abc"}): self.assertEqual(timeouts.approval_default_seconds(), 3600) def test_existing_approval_expires_at_never_recomputed(self): conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row try: with mock.patch.dict(os.environ, {}, clear=False): os.environ.pop("CLOUD_APPROVAL_TIMEOUT_SECONDS", None) old = create_approval(conn, "t1", "p1", summary="s", now=1000.0) self.assertEqual(old["expires_at"], 4600.0) with mock.patch.dict(os.environ, {"CLOUD_APPROVAL_TIMEOUT_SECONDS": "60"}): new = create_approval(conn, "t1", "p1", summary="s", now=2000.0) self.assertEqual(new["expires_at"], 2060.0) # 旧行不受默认值变化影响:不回填、不重算。 from cloud.approvals import get_approval self.assertEqual(get_approval(conn, old["id"])["expires_at"], 4600.0) finally: conn.close() class GatesConvergeTestCase(unittest.TestCase): def test_path_gate_cases_across_all_wrappers(self): with tempfile.TemporaryDirectory() as tmp: base = os.path.join(tmp, "w") os.makedirs(os.path.join(base, "sub")) # 目录内指回内部的符号链接放行,指出去的拦住。 os.symlink(os.path.join(base, "sub"), os.path.join(base, "inner-link")) os.symlink("/etc", os.path.join(base, "escape-link")) for fn in (gates.resolve_inside, resolve_tree_path, safe_worktree_path, _server_safe_path): self.assertEqual(fn(base, ""), base) self.assertEqual(fn(base, "sub"), os.path.realpath(os.path.join(base, "sub"))) self.assertIsNone(fn(base, "../etc/passwd")) self.assertIsNone(fn(base, "/etc/passwd")) self.assertIsNotNone(fn(base, "inner-link")) self.assertIsNone(fn(base, "escape-link")) def test_path_and_control_gates_have_no_bypass_parameter(self): for fn in (gates.resolve_inside, gates.decide_control, gates.merge_state_decision): params = set(inspect.signature(fn).parameters) self.assertFalse(params & {"confirm", "override", "bypass", "force", "confirmed"}) def test_merge_state_gate(self): self.assertEqual(gates.merge_state_decision("completed"), (True, "")) for status in ("queued", "running", "failed", "interrupted", "merged", "merge-failed"): ok, reason = gates.merge_state_decision(status) self.assertFalse(ok) self.assertEqual(reason, "only completed tasks can be marked merged") def test_control_gate_matches_old_marker_behavior(self): sender = {"id": "s1", "project_id": "p1"} target = {"id": "t1", "project_id": "p1", "status": "running"} self.assertEqual(check_control_gate(target, sender, "descendant", "cancel"), (True, "")) self.assertEqual( check_control_gate(target, sender, "descendant", "retry")[0], False) target2 = dict(target, status="failed") self.assertEqual(check_control_gate(target2, sender, "same_creator", "retry"), (True, "")) self.assertEqual( check_control_gate(target2, sender, "other", "retry")[0], False) self.assertEqual( check_control_gate(target2, {"id": "t1", "project_id": "p1"}, "descendant", "cancel")[0], False) goal = dict(target2, goal_mode=1) self.assertEqual( check_control_gate(goal, sender, "descendant", "retry")[0], False) cross = dict(target2, project_id="p2") self.assertEqual( check_control_gate(cross, sender, "descendant", "retry")[0], False) class QuotaConvergeTestCase(unittest.TestCase): def test_decide_precedence_and_equality(self): self.assertIsNone(quota.decide(None, 999999, 999999, 100, "2026-10")) self.assertIsNone(quota.decide(1000, 999, 99, 100, "2026-10")) both = quota.decide(1000, 1000, 1000, 100, "2026-10") self.assertEqual(both["kind"], "usd") only_unpriced = quota.decide(1000, 10, 100, 100, "2026-10") self.assertEqual(only_unpriced["kind"], "unpriced") self.assertIn("quota_exceeded", only_unpriced["message"]) def test_quota_for_owner_rows(self): conn = _quota_conn() try: with mock.patch.dict(os.environ, {}, clear=False): os.environ.pop("CLOUD_DEFAULT_QUOTA_USD_MICROS", None) self.assertEqual(quota.quota_for_owner(conn, "u1"), 10000000) conn.execute("INSERT INTO user_quotas VALUES('u1', NULL, 0)") conn.commit() self.assertIsNone(quota.quota_for_owner(conn, "u1")) conn.execute("UPDATE user_quotas SET quota_usd_micros = 5000" " WHERE user_id = 'u1'") conn.commit() self.assertEqual(quota.quota_for_owner(conn, "u1"), 5000) finally: conn.close() def test_monthly_usage_actual_only(self): conn = _quota_conn() try: _cost_row(conn, "t1", 300, 10, 5, "actual") _cost_row(conn, "t1", None, 100, 50, "actual") _cost_row(conn, "t1", None, 999900, 99, "estimated") used, unpriced = quota.monthly_usage(conn, "u1", "2026-10") self.assertEqual(used, 300) # 估算行 999999 token 不计入无价上限口径。 self.assertEqual(unpriced, 150) finally: conn.close() def test_automation_gate_uses_actual_only(self): conn = _quota_conn() try: conn.execute("INSERT INTO user_quotas VALUES('u1', 100000000, 0)") conn.commit() _cost_row(conn, "t1", None, 999900, 99, "estimated") with mock.patch.dict( os.environ, {"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "1000"}): # 估算行再大也不拦。 self.assertIsNone(_automation_quota_exceeded(conn, "u1")) _cost_row(conn, "t1", None, 600, 500, "actual") blocked = _automation_quota_exceeded(conn, "u1") self.assertIsNotNone(blocked) self.assertIn("quota_exceeded", blocked) finally: conn.close() def test_unpriced_cap_env_alias_matches_cost(self): with mock.patch.dict( os.environ, {"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "12345"}): self.assertEqual(_auto_cap(), 12345) self.assertEqual(_cost_cap(), 12345) with mock.patch.dict( os.environ, {"CLOUD_QUOTA_UNPRICED_TOKEN_CAP": "junk"}): self.assertEqual(_auto_cap(), _cost_cap()) class SandboxPolicyTestCase(unittest.TestCase): def test_tmpfs_matrix(self): self.assertTrue(sandbox_policy.tmpfs_isolated("docker", "/tmp")[0]) self.assertTrue( sandbox_policy.tmpfs_isolated("docker", "/var/tmp")[0]) self.assertFalse( sandbox_policy.tmpfs_isolated("host-bwrap", "/tmp")[0]) self.assertFalse(sandbox_policy.tmpfs_isolated("local", "/tmp")[0]) self.assertFalse( sandbox_policy.tmpfs_isolated("docker", "/work")[0]) def test_task_container_regex_shared_with_sandbox(self): self.assertIs(sandbox_policy.TASK_CONTAINER_RE, DockerExecutor._TASK_CONTAINER_RE) good = "codex-" + "a" * 32 self.assertTrue(sandbox_policy.TASK_CONTAINER_RE.match(good)) for bad in ("codex-postgres", "codex-xyz", "codex-" + "a" * 31, "codex-" + "A" * 32): self.assertIsNone(sandbox_policy.TASK_CONTAINER_RE.match(bad)) def test_cleanup_decision(self): now = 10000.0 self.assertEqual( sandbox_policy.cleanup_decision("running", 1.0, now, 300)[0], False) self.assertEqual( sandbox_policy.cleanup_decision("interrupted", now - 301, now, 300)[0], True) # 恰等于阈值不杀(旧 _reap:hb >= now - threshold 即跳过)。 self.assertEqual( sandbox_policy.cleanup_decision("interrupted", now - 300, now, 300)[0], False) self.assertEqual( sandbox_policy.cleanup_decision("interrupted", now - 10, now, 300)[0], False) self.assertEqual( sandbox_policy.cleanup_decision("interrupted", None, now, 300)[0], True) self.assertEqual( sandbox_policy.cleanup_decision("interrupted", "junk", now, 300)[0], False) def test_worker_address(self): self.assertEqual(sandbox_policy.worker_address("my-host-42"), ("my-host", 42)) self.assertEqual(sandbox_policy.worker_address("plain"), ("plain", None)) self.assertEqual(sandbox_policy.worker_address("host-abc"), ("host-abc", None)) self.assertEqual(sandbox_policy.worker_address(""), ("", None)) if __name__ == "__main__": unittest.main()