"""长函数小提取第二轨(#229 之后):四处纯函数钉住测试。 只钉住本轨抽出的四个纯函数(成功路径与错误串),不改原有 断言语义;原路由行为不变由 test_task_continue / test_thread_wait_ops / test_thread_ops 等定向套件覆盖。 """ import sys import unittest from pathlib import Path try: from cloud.server import ( TASK_STATUS_KEYS, _summarize_metrics_tasks, _summarize_trace_entries, _validate_continue_options, _validate_wait_params, ) except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from cloud.server import ( TASK_STATUS_KEYS, _summarize_metrics_tasks, _summarize_trace_entries, _validate_continue_options, _validate_wait_params, ) def _task(**over): task = {"plan": 0, "goal_mode": 0, "approval": "ask"} task.update(over) return task class ValidateContinueOptionsTestCase(unittest.TestCase): def test_success_defaults_from_task(self): opts, err = _validate_continue_options({}, _task(), None) self.assertIsNone(err) self.assertIsNone(opts["model"]) self.assertIsNone(opts["effort"]) self.assertFalse(opts["child_model_present"]) self.assertFalse(opts["child_effort_present"]) self.assertIsNone(opts["approval"]) self.assertIsNone(opts["plan"]) self.assertIsNone(opts["goal_mode"]) def test_success_explicit_and_empty_child_clears(self): opts, err = _validate_continue_options( {"model": " m ", "effort": "low", "child_model": "", "child_effort": "", "approval": "auto", "plan": True, "goal_mode": 0}, _task(), None) self.assertIsNone(err) self.assertEqual(opts["model"], "m") self.assertEqual(opts["effort"], "low") self.assertIsNone(opts["child_model"]) self.assertTrue(opts["child_model_present"]) self.assertIsNone(opts["child_effort"]) self.assertTrue(opts["child_effort_present"]) self.assertEqual(opts["plan"], 1) self.assertEqual(opts["goal_mode"], 0) def test_goal_command_forces_goal_mode(self): opts, err = _validate_continue_options( {"goal_mode": 0}, _task(), "do X") self.assertIsNone(err) self.assertEqual(opts["goal_mode"], 1) def test_error_strings_verbatim(self): cases = [ ({"model": 1}, "model must be a string"), ({"effort": "huge"}, "effort must be low, medium, high, xhigh or max"), ({"child_model": 1}, "child_model must be a string"), ({"child_effort": "huge"}, "child_effort must be low, medium, high, xhigh or max"), ({"approval": "all"}, "approval must be ask, auto or full"), ({"plan": True, "approval": "full"}, "plan 模式与 approval=full 冲突(只读 vs 完全访问)"), ({"plan": True, "goal_mode": 1}, "plan 模式与 goal 模式冲突"), ] for body, want in cases: with self.subTest(body=body): opts, err = _validate_continue_options(body, _task(), None) self.assertIsNone(opts) self.assertEqual(err, want) class ValidateWaitParamsTestCase(unittest.TestCase): def test_default_and_valid(self): timeout, requested, err = _validate_wait_params({}) self.assertEqual(timeout, 30) self.assertIsNone(requested) self.assertIsNone(err) timeout, requested, err = _validate_wait_params( {"timeout_seconds": 5, "task_ids": ["a"]}) self.assertEqual((timeout, requested, err), (5, ["a"], None)) def test_error_payloads_verbatim(self): cases = [ ({"timeout_seconds": True}, {"error": "timeout_seconds must be a number"}), ({"timeout_seconds": 121}, {"error": "timeout_seconds must be between 0 and 120"}), ({"task_ids": "a"}, {"error": "task_ids must be a list"}), ({"task_ids": [1]}, {"error": "task_ids must be a list"}), ({"task_ids": []}, {"error": "task_ids must not be empty"}), ({"task_ids": ["a"] * 9}, {"error": "at most 8 threads per wait", "count": 9}), ] for body, want in cases: with self.subTest(body=body): _, _, err = _validate_wait_params(body) self.assertEqual(err, want) class SummarizeTraceEntriesTestCase(unittest.TestCase): def test_changed_commands_activity(self): entries = [ {"kind": "file", "path": "a.py", "change": "mod", "status": "ok"}, {"kind": "file", "path": "a.py", "change": "mod2", "status": "ok2"}, {"kind": "exec", "command": "pytest", "exitCode": 0, "durationMs": 7}, {"kind": "tool", "server": "s", "tool": "t", "status": "done", "id": "x"}, {"kind": "message", "text": "hi", "id": "y"}, "not-a-dict", ] changed, commands, activity = _summarize_trace_entries(entries) self.assertEqual(changed, [{"path": "a.py", "change": "mod2", "status": "ok2"}]) self.assertEqual(commands, [{"command": "pytest", "exit_code": 0, "duration_ms": 7}]) self.assertEqual(activity[3], {"kind": "tool", "id": "x", "text": "s/t: done"}) self.assertEqual(activity[4], {"kind": "message", "id": "y", "text": "hi"}) self.assertEqual(activity[5], {"kind": "unknown"}) def test_truncation_windows(self): entries = [{"kind": "message", "text": "t%d" % i, "id": i} for i in range(15)] _, _, activity = _summarize_trace_entries(entries) self.assertEqual(len(activity), 10) self.assertEqual(activity[0]["text"], "t5") class SummarizeMetricsTasksTestCase(unittest.TestCase): def test_aggregation_and_member_filter(self): now = 1000000.0 rows = [ {"project_id": "p1", "status": "completed", "started_at": 100.0, "finished_at": 160.0}, {"project_id": "p1", "status": "failed", "started_at": None, "finished_at": now - 100}, {"project_id": "p1", "status": "running", "started_at": now - 400, "finished_at": None}, {"project_id": "p2", "status": "completed", "started_at": 0.0, "finished_at": 10.0}, ] by_status, durations, failures, over = _summarize_metrics_tasks( rows, {"p1"}, now) self.assertEqual(set(by_status), set(TASK_STATUS_KEYS)) self.assertEqual(by_status["completed"], 1) self.assertEqual(by_status["failed"], 1) self.assertEqual(by_status["running"], 1) self.assertEqual(durations, [60.0]) self.assertEqual(failures, 1) self.assertEqual(over, 1) if __name__ == "__main__": unittest.main()