import unittest from unittest.mock import patch from app.services import detect_service class DetectServiceStatusFallbackTests(unittest.TestCase): def test_get_detect_status_keeps_runtime_snapshot_when_db_is_unreachable(self) -> None: runtime_state = { "service_running": True, "detecting": True, "active_threads": 7, "max_threads": 120, "phase": "running", "detail": "Worker 正在处理 7 个检测任务", "updated_at": "2026-04-20 23:59:00", "available_proxy_count": 18, } with patch("app.services.detect_service.ensure_runtime_schema"), \ patch("app.services.detect_service.get_db", side_effect=RuntimeError("db down")), \ patch("app.services.detect_service.get_settings_payload", return_value={"proxy_config": {"proxy_enable": True, "allow_direct": False, "proxy_urls": ["a"]}}), \ patch("app.services.detect_service.get_runtime_settings", return_value={"worker_log_sync_enabled": False, "worker_log_sync_mode": "full"}), \ patch("app.services.detect_service._load_recent_worker_lines", return_value=(True, "", [])), \ patch("app.services.detect_service.detect_worker_runtime", return_value={"mode": "linux-systemd", "running": True, "process_count": 1, "latest_start_time": "2026-04-20 23:58:00", "message": "active/running"}), \ patch("app.services.detect_service._load_runtime_state", return_value=runtime_state), \ patch("app.services.detect_service._load_runtime_state_from_cluster_node", return_value={}), \ patch("app.services.detect_service._extract_available_proxy_count", return_value=0), \ patch("app.services.detect_service._extract_active_thread_snapshot", return_value={"active": 0, "max": 0}), \ patch("app.services.detect_service._normalize_recent_warning", return_value=""), \ patch("app.services.detect_service._build_proxy_runtime_snapshot", return_value={"state": "healthy", "label": "代理正常", "detail": "healthy", "direct_fallback_active": False, "reason": "healthy", "last_refresh_status": "ok", "last_refresh_time": "", "source_count": 2, "raw_items": 18, "validated_count": 18, "available_count": 18, "source_stats": [], "supplier_empty": False}), \ patch("app.services.detect_service.resolve_thread_count", return_value={"effective_thread_count": 120, "default_thread_count": 5, "source": "node_override", "override_thread_count": 120, "node_code": "mainland-worker-01"}), \ patch("app.services.detect_service.get_active_detect_job_summary", side_effect=RuntimeError("db down")), \ patch("app.services.detect_service.sync_detect_runs", return_value=[]), \ patch("app.services.detect_service._resolve_remote_log_snapshot", return_value={}), \ patch("app.services.detect_service._extract_dependency_alerts", return_value=[]), \ patch("app.services.detect_service.append_detect_result_projection_if_changed"): payload = detect_service.get_detect_status() self.assertTrue(payload["worker_online"]) self.assertTrue(payload["detecting"]) self.assertEqual(7, payload["active_thread_count"]) self.assertEqual(120, payload["max_thread_count"]) self.assertEqual(0, payload["progress"]["pending"]) self.assertEqual(0, payload["progress"]["completed"]) def test_filter_lines_since_supports_journalctl_syslog_timestamps(self) -> None: lines = [ "Apr 21 20:12:42 mainland-controller python[1]: 当前实际线程数量: 323/4", "Apr 21 20:17:20 mainland-controller python[2]: Worker 已启动,等待检测指令", ] filtered = detect_service._filter_lines_since(lines, "2026-04-21 20:17:00") self.assertEqual( ["Apr 21 20:17:20 mainland-controller python[2]: Worker 已启动,等待检测指令"], filtered, ) def test_filter_lines_since_falls_back_when_no_timestamp_is_parseable(self) -> None: lines = ["no timestamp line 1", "no timestamp line 2"] filtered = detect_service._filter_lines_since(lines, "2026-04-21 20:17:00") self.assertEqual(lines, filtered) if __name__ == "__main__": unittest.main()