import unittest from unittest.mock import patch from app.services.ops_service import get_ops_node_scene_log class OpsServiceNodeSceneLogTests(unittest.TestCase): def _runtime_payload_with_worker_logs(self) -> dict: return { "detect": { "log_sync": { "enabled": True, "mode": "key", "line_count": 2, "source_node_count": 1, "source_nodes": ["mainland-worker-01"], "source_node_summaries": [ { "node_code": "mainland-worker-01", "line_count": 2, "key_line_count": 2, "full_line_count": 0, "last_at": "2026-04-18 05:02:00", "last_line": "[2026-04-18 05:02:00] [mainland-worker-01] 域名检测完成: alpha.com", } ], "preview_lines": [ "[2026-04-18 05:00:00] [mainland-worker-01] 开始检测域名: alpha.com", "[2026-04-18 05:02:00] [mainland-worker-01] 域名检测完成: alpha.com", ], "last_at": "2026-04-18 05:02:00", "last_line": "[2026-04-18 05:02:00] [mainland-worker-01] 域名检测完成: alpha.com", }, "participation_summary": { "participating_nodes": 1, "dispatch_active_nodes": 1, "recent_only_nodes": 0, "non_participating_nodes": 1, "standby_nodes": 1, "load_syncing_nodes": 0, "effective_online_nodes": 2, "dedicated_worker_nodes": 2, "controller_worker_nodes": 0, "dispatch_active_node_codes": ["mainland-worker-01"], "recent_only_node_codes": [], "non_participating_node_codes": ["mainland-worker-02"], "standby_node_codes": ["mainland-worker-02"], "load_syncing_node_codes": [], "summary": "有效执行节点 2 台;正在执行/领任务 1 台;近窗刚有吞吐 0 台;在线但未参与 1 台", }, "participating_nodes": [ { "node_code": "mainland-worker-01", "region": "mainland", "role": "worker", "status": "busy", "current_load": 2, "last_heartbeat_at": "2026-04-18 05:02:10", "participation_state": "claimed", "participation_label": "已领待跑", "participation_reason": "已领取 2 项任务,等待线程继续执行。", "is_dispatch_active": True, } ], "non_participating_nodes": [ { "node_code": "mainland-worker-02", "region": "mainland", "role": "worker", "status": "online", "current_load": 0, "last_heartbeat_at": "2026-04-18 05:02:11", "participation_state": "standby", "participation_label": "在线待命", "participation_reason": "当前节点在线,但尚未参与本轮检测。", "is_dispatch_active": False, } ], "active_job": { "job_code": "detect-001", "status": "running", "current_cycle_token": "cycle-1", "current_cycle_events": [ { "event_type": "worker_log", "created_at": "2026-04-18 05:02:00", "node_code": "mainland-worker-01", "message": "域名检测完成: alpha.com", "payload": {"cycle_token": "cycle-1", "log_mode": "key"}, }, { "event_type": "worker_log", "created_at": "2026-04-18 05:01:00", "node_code": "mainland-worker-01", "message": "代理池刷新完成", "payload": {"cycle_token": "cycle-1", "log_mode": "full"}, }, { "event_type": "worker_log", "created_at": "2026-04-18 05:00:00", "node_code": "mainland-worker-01", "message": "开始检测域名: alpha.com", "payload": {"cycle_token": "cycle-1", "log_mode": "key"}, }, { "event_type": "worker_log", "created_at": "2026-04-18 04:59:00", "node_code": "mainland-worker-01", "message": "旧周期日志", "payload": {"cycle_token": "cycle-0", "log_mode": "key"}, }, ], }, }, "cluster": { "nodes": [ { "node_code": "mainland-worker-01", "region": "mainland", "role": "worker", "status": "busy", "current_load": 2, "last_heartbeat_at": "2026-04-18 05:02:10", }, { "node_code": "mainland-worker-02", "region": "mainland", "role": "worker", "status": "online", "current_load": 0, "last_heartbeat_at": "2026-04-18 05:02:11", }, ] }, } @patch("app.services.ops_service.get_runtime_status") def test_node_scene_log_filters_key_mode_records_for_participating_node(self, mock_get_runtime_status) -> None: mock_get_runtime_status.return_value = self._runtime_payload_with_worker_logs() payload = get_ops_node_scene_log("mainland-worker-01", limit=10) self.assertEqual("mainland-worker-01", payload["node_code"]) self.assertEqual("healthy", payload["status"]) self.assertEqual("关键覆盖", payload["status_label"]) self.assertEqual("key", payload["mode"]) self.assertTrue(payload["log_sync_enabled"]) self.assertTrue(payload["participation"]["detect_participating"]) self.assertEqual(2, payload["records_total"]) self.assertEqual( [ "开始检测域名: alpha.com", "域名检测完成: alpha.com", ], [item["message"] for item in payload["records"]], ) self.assertEqual(2, payload["source_summary"]["line_count"]) self.assertEqual(2, payload["source_summary"]["key_line_count"]) self.assertEqual(0, payload["source_summary"]["full_line_count"]) self.assertEqual("ops_observability_contract", payload["contract_navigation"]["primary_contract_key"]) @patch("app.services.ops_service.get_runtime_status") def test_node_scene_log_can_request_full_mode_records(self, mock_get_runtime_status) -> None: mock_get_runtime_status.return_value = self._runtime_payload_with_worker_logs() payload = get_ops_node_scene_log("mainland-worker-01", limit=10, mode="full") self.assertEqual("full_capture", payload["status"]) self.assertEqual("full", payload["mode"]) self.assertEqual(3, payload["records_total"]) self.assertEqual( [ "开始检测域名: alpha.com", "代理池刷新完成", "域名检测完成: alpha.com", ], [item["message"] for item in payload["records"]], ) self.assertEqual(3, payload["source_summary"]["line_count"]) self.assertEqual(2, payload["source_summary"]["key_line_count"]) self.assertEqual(1, payload["source_summary"]["full_line_count"]) @patch("app.services.ops_service.get_runtime_status") def test_node_scene_log_marks_waiting_sample_for_missing_participating_node(self, mock_get_runtime_status) -> None: mock_get_runtime_status.return_value = { "detect": { "log_sync": { "enabled": True, "mode": "key", "line_count": 0, "source_node_count": 0, "source_nodes": [], "source_node_summaries": [], "preview_lines": [], "last_at": "", "last_line": "", }, "participation_summary": { "participating_nodes": 1, "dispatch_active_nodes": 1, "recent_only_nodes": 0, "non_participating_nodes": 0, "standby_nodes": 0, "load_syncing_nodes": 0, "effective_online_nodes": 1, "dedicated_worker_nodes": 1, "controller_worker_nodes": 0, "dispatch_active_node_codes": ["mainland-worker-02"], "recent_only_node_codes": [], "non_participating_node_codes": [], "standby_node_codes": [], "load_syncing_node_codes": [], "summary": "有效执行节点 1 台;正在执行/领任务 1 台;近窗刚有吞吐 0 台;在线但未参与 0 台", }, "participating_nodes": [ { "node_code": "mainland-worker-02", "region": "mainland", "role": "worker", "status": "busy", "current_load": 1, "last_heartbeat_at": "2026-04-18 05:05:00", "participation_state": "running", "participation_label": "正在执行", "participation_reason": "当前存在执行中工作负载。", "is_dispatch_active": True, } ], "non_participating_nodes": [], "active_job": {"job_code": "detect-002", "status": "running", "current_cycle_events": []}, }, "cluster": { "nodes": [ { "node_code": "mainland-worker-02", "region": "mainland", "role": "worker", "status": "busy", "current_load": 1, "last_heartbeat_at": "2026-04-18 05:05:00", } ] }, } payload = get_ops_node_scene_log("mainland-worker-02") self.assertEqual("waiting_sample", payload["status"]) self.assertEqual("等待样本", payload["status_label"]) self.assertEqual(0, payload["records_total"]) self.assertTrue(payload["participation"]["detect_participating"]) self.assertIn("现场样本尚未形成", payload["summary"]) if __name__ == "__main__": unittest.main()