257 lines
12 KiB
Python
257 lines
12 KiB
Python
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()
|