Files
getDomain/domain-api/tests/test_detect_job_service.py

2770 lines
113 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch
from psycopg2 import errors
import app.services.detect_job_service as detect_job_service_module
from app.services.detect_job_service import (
append_detect_job_event,
_build_active_job_summary_from_runtime_snapshot,
_build_step_payload,
_classify_pipeline_item_outcome,
_classify_runtime_debug_event,
_build_runtime_snapshot_from_projection,
_enrich_active_job_summary_with_runtime,
_build_display_summary,
_build_runtime_display_bucket,
_load_latest_runtime_active_job_snapshot,
get_active_detect_job_summary,
normalize_detect_step_code,
get_detect_queue_health,
process_detect_pipeline_now,
resolve_initial_domain_pipeline_item,
resolve_domain_pipeline_step,
resolve_detect_job_definition,
)
class _SequenceCursor:
def __init__(self, *, fetchone_results=None, fetchall_results=None) -> None:
self.fetchone_results = list(fetchone_results or [])
self.fetchall_results = list(fetchall_results or [])
self.exec_calls = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def execute(self, sql, params=None):
self.exec_calls.append((sql, params))
def fetchone(self):
if self.fetchone_results:
return self.fetchone_results.pop(0)
return None
def fetchall(self):
if self.fetchall_results:
return self.fetchall_results.pop(0)
return []
class _SequenceConn:
def __init__(self, *, fetchone_results=None, fetchall_results=None) -> None:
self.cursor_obj = _SequenceCursor(fetchone_results=fetchone_results, fetchall_results=fetchall_results)
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def cursor(self):
return self.cursor_obj
class DetectJobServiceTests(unittest.TestCase):
def test_load_runtime_display_rows_excludes_disabled_managed_nodes(self) -> None:
cursor = MagicMock()
cursor.fetchall.return_value = [
("mainland-controller-01", "mainland", "control", "busy", 1, {}, datetime.now()),
("mainland-worker-01", "mainland", "worker", "busy", 1, {}, datetime.now()),
]
with patch("app.services.detect_job_service._load_disabled_managed_node_codes", return_value={"mainland-worker-01"}):
rows = detect_job_service_module._load_runtime_display_rows(cursor)
self.assertEqual(["mainland-controller-01"], [row[0] for row in rows])
def test_build_active_job_summary_from_runtime_snapshot_keeps_non_zero_display_when_queue_display_is_zero(self) -> None:
with patch("app.services.detect_job_service.settings.node_region", "mainland"):
summary = _build_active_job_summary_from_runtime_snapshot(
{
"job": {
"job_id": 60,
"job_code": "sync-overseas-255",
"status": "running",
"items_total": 8402,
"items_pending": 7865,
"items_claimed": 0,
"items_running": 537,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"items_terminal": 0,
"display_items_running": 0,
"display_active_threads": 0,
"display_max_threads": 0,
"node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 0,
"display_running": 0,
"active_threads": 0,
"max_threads": 0,
}
],
},
"queue_health": {
"queue": {
"items_total": 8402,
"pending": 7865,
"claimed": 0,
"running": 537,
"display_running": 0,
"completed": 0,
"blacklisted": 0,
"failed": 0,
"terminal": 0,
},
"nodes": [
{
"node_code": "mainland-controller-01-a",
"items_running": 0,
"display_running": 0,
"active_threads": 0,
"max_threads": 0,
}
],
"steps": [],
"throughput": {},
},
"_snapshot_source": "runtime_ingest",
}
)
assert summary is not None
self.assertEqual(537, summary["display_items_running"])
self.assertEqual(537, summary["display_active_threads"])
self.assertEqual(537, summary["display_current_load"])
def test_build_active_job_summary_from_runtime_snapshot_drops_stale_runtime_nodes_from_live_display(self) -> None:
stale_heartbeat = (datetime.now() - timedelta(minutes=8)).isoformat(sep=" ", timespec="seconds")
snapshot_created_at = datetime.now().isoformat(sep=" ", timespec="seconds")
summary = _build_active_job_summary_from_runtime_snapshot(
{
"job": {
"job_id": 61,
"job_code": "sync-overseas-256",
"status": "running",
"items_total": 8402,
"items_pending": 7865,
"items_claimed": 0,
"items_running": 537,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"items_terminal": 0,
"display_items_running": 537,
"display_active_threads": 537,
"node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 537,
"display_running": 537,
"active_threads": 537,
"max_threads": 1000,
"status": "busy",
"last_heartbeat_at": stale_heartbeat,
}
],
},
"queue_health": {
"queue": {
"items_total": 8402,
"pending": 7865,
"claimed": 0,
"running": 537,
"display_running": 537,
"completed": 0,
"blacklisted": 0,
"failed": 0,
"terminal": 0,
},
"nodes": [
{
"node_code": "mainland-controller-01-a",
"items_running": 537,
"display_running": 537,
"active_threads": 537,
"max_threads": 1000,
"status": "busy",
"last_heartbeat_at": stale_heartbeat,
}
],
"steps": [],
"throughput": {},
},
"_snapshot_source": "runtime_ingest",
"_created_at": snapshot_created_at,
}
)
assert summary is not None
self.assertEqual(537, summary["items_running"])
self.assertEqual(0, summary["display_items_running"])
self.assertEqual(0, summary["display_active_threads"])
self.assertEqual([], summary["node_stats"])
def test_build_active_job_summary_from_runtime_snapshot_does_not_promote_unassigned_runtime_load(self) -> None:
with patch("app.services.detect_job_service.settings.node_region", "mainland"):
summary = _build_active_job_summary_from_runtime_snapshot(
{
"job": {
"job_id": 62,
"job_code": "sync-overseas-257",
"status": "running",
"items_total": 4000,
"items_pending": 0,
"items_claimed": 23,
"items_running": 2100,
"items_completed": 1117,
"items_blacklisted": 0,
"items_failed": 760,
"items_terminal": 1877,
"display_items_running": 0,
"display_active_threads": 0,
"display_max_threads": 0,
"node_stats": [
{
"node_code": "unassigned",
"items_claimed": 23,
"items_running": 1863,
"display_running": 1863,
"active_threads": 0,
"max_threads": 0,
}
],
},
"queue_health": {
"queue": {
"items_total": 4000,
"pending": 0,
"claimed": 23,
"running": 2100,
"display_running": 2100,
"completed": 1117,
"blacklisted": 0,
"failed": 760,
"terminal": 1877,
},
"nodes": [
{
"node_code": "unassigned",
"items_claimed": 23,
"items_running": 1863,
"display_running": 1863,
"active_threads": 0,
"max_threads": 0,
}
],
"steps": [],
"throughput": {},
},
"_snapshot_source": "runtime_ingest",
}
)
assert summary is not None
self.assertEqual(0, summary["display_items_running"])
self.assertEqual(0, summary["display_active_threads"])
self.assertEqual([], summary["display_active_node_codes"])
def test_append_detect_job_event_skips_missing_job_fk(self) -> None:
class FakeCursor:
def __init__(self) -> None:
self.exec_calls = []
self.selects = 0
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def execute(self, sql, params=None):
self.exec_calls.append((sql, params))
if "INSERT INTO detect_run_events" in sql:
raise AssertionError("should not insert detect_run_events when job row is missing")
if "SELECT 1 FROM detect_jobs" in sql:
self.selects += 1
def fetchone(self):
return None
class FakeConn:
def __init__(self) -> None:
self.cursor_obj = FakeCursor()
self.commit_calls = 0
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
def cursor(self):
return self.cursor_obj
def commit(self):
self.commit_calls += 1
fake_conn = FakeConn()
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service.push_debug_event") as mock_push:
append_detect_job_event(
5,
event_type="job_dispatch_requested",
message="控制面已发送检测启动请求",
)
self.assertEqual(1, fake_conn.cursor_obj.selects)
self.assertEqual(0, fake_conn.commit_calls)
mock_push.assert_called_once()
def test_process_detect_pipeline_now_retries_deadlock_once(self) -> None:
calls = {"count": 0}
def _run_pipeline(*, limit, job_id):
calls["count"] += 1
if calls["count"] == 1:
raise errors.DeadlockDetected()
return {
"processed_items": 7,
"advanced_items": 3,
"retried_items": 1,
}
with patch("app.services.detect_job_service.process_detect_pipeline", side_effect=_run_pipeline):
with patch("app.services.detect_job_service.time.sleep") as mock_sleep:
ok, message, data = process_detect_pipeline_now(limit=123, job_id=45)
self.assertTrue(ok)
self.assertIn("deadlock 自动重试 1 次后成功", message)
self.assertEqual(1, data["retry_attempts"])
self.assertEqual(7, data["processed_items"])
self.assertEqual(2, calls["count"])
mock_sleep.assert_called_once()
def test_build_runtime_snapshot_from_projection_prefers_distributed_projection_nodes(self) -> None:
snapshot = _build_runtime_snapshot_from_projection(
{
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 1972,
"max_thread_count": 2000,
"progress": {
"pending": 4578,
"running": 1972,
"completed": 690,
"blacklisted": 0,
"failed": 24,
},
"active_job": {
"job_id": 55,
"job_code": "sync-overseas-55",
"status": "running",
"progress_percent": 13.49,
"items_total": 5292,
"items_terminal": 714,
"items_pending": 4578,
"items_running": 1972,
"items_failed": 24,
"display_items_claimed": 120,
"display_items_running": 1972,
"display_active_threads": 1972,
"display_max_threads": 2000,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
"region": "mainland",
"role": "control",
"status": "busy",
},
{
"node_code": "mainland-controller-01-b",
"items_running": 972,
"display_running": 972,
"active_threads": 972,
"max_threads": 1000,
"region": "mainland",
"role": "control",
"status": "busy",
},
],
},
}
)
queue = snapshot["queue_health"]["queue"]
nodes = snapshot["queue_health"]["nodes"]
self.assertEqual(1972, queue["running"])
self.assertEqual(1972, queue["display_running"])
self.assertEqual(3, len(nodes))
self.assertEqual("mainland-controller-01-a", nodes[0]["node_code"])
self.assertEqual(1000, nodes[0]["display_running"])
self.assertEqual(972, nodes[1]["active_threads"])
self.assertEqual("unassigned", nodes[2]["node_code"])
def test_enrich_active_job_summary_with_runtime_does_not_overwrite_non_zero_display_with_zero_snapshot(self) -> None:
summary = _enrich_active_job_summary_with_runtime(
{
"job_id": 60,
"job_code": "sync-overseas-255",
"status": "running",
"items_total": 8402,
"items_pending": 7865,
"items_claimed": 0,
"items_running": 537,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"items_terminal": 0,
"display_items_running": 537,
"display_active_threads": 537,
"display_current_load": 537,
"display_max_threads": 80000,
"recent_events": [],
},
runtime_activity={},
runtime_snapshot={
"job": {
"job_id": 60,
"job_code": "sync-overseas-255",
"status": "running",
"node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 0,
"display_running": 0,
"active_threads": 0,
"max_threads": 0,
}
],
},
"queue_health": {
"queue": {
"items_total": 8402,
"pending": 7865,
"claimed": 0,
"running": 537,
"display_running": 0,
"completed": 0,
"blacklisted": 0,
"failed": 0,
"terminal": 0,
},
"nodes": [
{
"node_code": "mainland-controller-01-a",
"items_running": 0,
"display_running": 0,
"active_threads": 0,
"max_threads": 0,
}
],
"steps": [],
},
},
)
assert summary is not None
self.assertEqual(537, summary["display_items_running"])
self.assertEqual(537, summary["display_active_threads"])
self.assertEqual(80000, summary["display_max_threads"])
def test_classify_runtime_debug_event_maps_single_step_finalized_completed(self) -> None:
classified = _classify_runtime_debug_event(
event_type="worker_log",
message="检测步骤跟踪: domain=example.com | step=注册状态检测 | stage=single_step_finalized | elapsed_ms=2700 | ok=1 | detect_key=detect_register | final_status=completed | result_state=passed",
payload={"job_code": "sync-overseas-27456", "node_code": "mainland-worker-01"},
)
self.assertEqual("sync-overseas-27456", classified["job_code"])
self.assertTrue(classified["terminal"])
self.assertEqual("completed", classified["terminal_status"])
self.assertEqual("detect_register", classified["step_code"])
def test_build_runtime_display_bucket_prefers_real_active_threads_over_raw_current_load(self) -> None:
bucket = _build_runtime_display_bucket(
(
"mainland-worker-01",
"mainland",
"worker",
"busy",
753,
{
"job_items_total": 1000,
"job_items_claimed": 0,
"job_items_running": 0,
"job_items_completed": 342,
"active_threads": 61,
"max_threads": 400,
"detect_participating": True,
},
None,
)
)
self.assertIsNotNone(bucket)
self.assertEqual(61, bucket["current_load"])
self.assertEqual(61, bucket["display_running"])
self.assertEqual(61, bucket["active_threads"])
def test_build_runtime_display_bucket_skips_local_overseas_control_plane_load(self) -> None:
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.settings.node_code", "overseas-control-01"):
bucket = _build_runtime_display_bucket(
(
"overseas-control-01",
"overseas",
"control",
"busy",
371,
{
"active_threads": 371,
"max_threads": 3200,
"detect_participating": False,
},
None,
)
)
self.assertIsNone(bucket)
def test_build_runtime_display_bucket_skips_any_control_node_without_worker_support(self) -> None:
bucket = _build_runtime_display_bucket(
(
"overseas-control-01",
"overseas",
"control",
"busy",
378,
{
"active_threads": 378,
"max_threads": 3200,
"detect_participating": False,
"worker_online": False,
},
None,
)
)
self.assertIsNone(bucket)
def test_load_latest_runtime_active_job_snapshot_uses_runtime_ingest_when_debug_missing(self) -> None:
ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 177,
"max_thread_count": 2000,
"progress": {
"pending": 200,
"running": 177,
"completed": 440,
"blacklisted": 0,
"failed": 4,
},
"backlog": {
"pending_total": 200,
"running_total": 177,
},
"active_job": {
"job_id": 551,
"job_code": "sync-overseas-55",
"status": "running",
"items_total": 821,
"items_terminal": 444,
"items_failed": 4,
"progress_percent": 54.08,
"node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_total": 400,
"items_pending": 0,
"items_claimed": 80,
"items_running": 100,
"items_completed": 220,
"items_blacklisted": 0,
"items_failed": 0,
"active_threads": 100,
"max_threads": 1000,
}
],
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[],
[(ingest_payload, datetime(2026, 4, 22, 18, 5, 36))],
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
snapshot = _load_latest_runtime_active_job_snapshot(15)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-55", snapshot["job"]["job_code"])
self.assertEqual(821, snapshot["queue_health"]["queue"]["items_total"])
self.assertEqual(177, snapshot["queue_health"]["queue"]["display_running"])
self.assertEqual(200, snapshot["backlog"]["pending_total"])
def test_load_latest_runtime_active_job_snapshot_prefers_fresher_runtime_ingest(self) -> None:
debug_payload = {
"job": {
"job_id": 84,
"job_code": "sync-overseas-366",
"status": "running",
},
"queue_health": {
"queue": {
"items_total": 5000,
"running": 2,
}
},
}
ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 177,
"max_thread_count": 2000,
"progress": {
"pending": 200,
"running": 177,
"completed": 440,
"blacklisted": 0,
"failed": 4,
},
"active_job": {
"job_id": 551,
"job_code": "sync-overseas-55",
"status": "running",
"items_total": 821,
"items_terminal": 444,
"items_failed": 4,
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[(debug_payload, datetime(2026, 4, 22, 17, 37, 30))],
[(ingest_payload, datetime(2026, 4, 22, 18, 5, 36))],
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
snapshot = _load_latest_runtime_active_job_snapshot(15)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-55", snapshot["job"]["job_code"])
def test_load_latest_runtime_active_job_snapshot_prefers_richer_ingest_over_newer_debug_snapshot(self) -> None:
debug_payload = {
"job": {
"job_id": 376,
"job_code": "sync-overseas-376",
"status": "running",
"display_items_running": 138,
"display_active_threads": 138,
"node_stats": [
{
"node_code": "mainland-controller-01",
"items_running": 138,
"display_running": 138,
"active_threads": 138,
"max_threads": 2000,
}
],
},
"queue_health": {
"queue": {
"items_total": 8402,
"running": 138,
"display_running": 138,
},
"nodes": [
{
"node_code": "mainland-controller-01",
"items_running": 138,
"display_running": 138,
"active_threads": 138,
"max_threads": 2000,
}
],
},
}
ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 5909,
"max_thread_count": 80000,
"progress": {
"pending": 2493,
"running": 5909,
"completed": 0,
"blacklisted": 0,
"failed": 0,
},
"active_job": {
"job_id": 551,
"job_code": "sync-overseas-51",
"status": "running",
"items_total": 8402,
"items_terminal": 0,
"items_failed": 0,
"display_items_running": 5909,
"display_active_threads": 5909,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-b",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-c",
"items_running": 3909,
"display_running": 3909,
"active_threads": 3909,
"max_threads": 78000,
},
],
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[(debug_payload, datetime(2026, 4, 23, 2, 45, 0))],
[(ingest_payload, datetime(2026, 4, 23, 2, 38, 55))],
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
snapshot = _load_latest_runtime_active_job_snapshot(15)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-51", snapshot["job"]["job_code"])
self.assertEqual(5909, snapshot["queue_health"]["queue"]["display_running"])
self.assertEqual(
[
"mainland-controller-01-a",
"mainland-controller-01-b",
"mainland-controller-01-c",
"unassigned",
],
[item["node_code"] for item in snapshot["queue_health"]["nodes"]],
)
def test_load_latest_runtime_active_job_snapshot_prefers_focus_job_match_over_newer_debug_snapshot(self) -> None:
debug_payload = {
"job": {
"job_id": 376,
"job_code": "sync-overseas-376",
"status": "running",
},
"queue_health": {
"queue": {
"items_total": 5000,
"running": 282,
}
},
}
ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 177,
"max_thread_count": 2000,
"progress": {
"pending": 7800,
"running": 177,
"completed": 420,
"blacklisted": 0,
"failed": 5,
},
"active_job": {
"job_id": 551,
"job_code": "sync-overseas-51",
"status": "running",
"items_total": 8402,
"items_terminal": 425,
"items_failed": 5,
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[(debug_payload, datetime(2026, 4, 22, 23, 17, 47))],
[
({"projection": {"active_job": {"job_code": "sync-overseas-55"}}}, datetime(2026, 4, 22, 18, 6, 10)),
(ingest_payload, datetime(2026, 4, 22, 18, 5, 36)),
],
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch(
"app.services.detect_job_service._load_runtime_activity_snapshot",
return_value={"focus_job_code": "sync-overseas-51"},
):
snapshot = _load_latest_runtime_active_job_snapshot(15)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-51", snapshot["job"]["job_code"])
def test_load_latest_runtime_ingest_active_job_snapshot_keeps_recent_focus_match_over_marginally_newer_non_focus_snapshot(self) -> None:
rich_non_focus_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 5909,
"max_thread_count": 80000,
"progress": {
"pending": 2493,
"running": 5909,
"completed": 0,
"blacklisted": 0,
"failed": 0,
},
"active_job": {
"job_id": 198,
"job_code": "sync-overseas-198",
"status": "running",
"items_total": 8402,
"items_terminal": 0,
"items_failed": 0,
"display_items_running": 5909,
"display_active_threads": 5909,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-b",
"items_running": 4909,
"display_running": 4909,
"active_threads": 4909,
"max_threads": 79000,
},
],
},
}
}
focus_match_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 177,
"max_thread_count": 2000,
"progress": {
"pending": 7800,
"running": 177,
"completed": 420,
"blacklisted": 0,
"failed": 5,
},
"active_job": {
"job_id": 551,
"job_code": "sync-overseas-51",
"status": "running",
"items_total": 8402,
"items_terminal": 425,
"items_failed": 5,
"display_items_running": 177,
"display_active_threads": 177,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01",
"items_running": 177,
"display_running": 177,
"active_threads": 177,
"max_threads": 2000,
}
],
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[
(rich_non_focus_payload, datetime(2026, 4, 23, 2, 45, 0)),
(focus_match_payload, datetime(2026, 4, 23, 2, 41, 0)),
]
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
snapshot = detect_job_service_module._load_latest_runtime_ingest_active_job_snapshot(
15,
preferred_job_codes=["sync-overseas-51"],
)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-51", snapshot["job"]["job_code"])
def test_load_latest_runtime_active_job_snapshot_prefers_newer_richer_ingest_over_stale_focus_job_match(self) -> None:
rich_ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 5909,
"max_thread_count": 80000,
"progress": {
"pending": 2493,
"running": 5909,
"completed": 0,
"blacklisted": 0,
"failed": 0,
},
"active_job": {
"job_id": 198,
"job_code": "sync-overseas-198",
"status": "running",
"items_total": 8402,
"items_terminal": 0,
"items_failed": 0,
"display_items_running": 5909,
"display_active_threads": 5909,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-b",
"items_running": 1000,
"display_running": 1000,
"active_threads": 1000,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-c",
"items_running": 3909,
"display_running": 3909,
"active_threads": 3909,
"max_threads": 78000,
},
],
},
}
}
stale_focus_ingest_payload = {
"projection": {
"node": {
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
},
"active_thread_count": 138,
"max_thread_count": 2000,
"progress": {
"pending": 5001,
"running": 0,
"completed": 0,
"blacklisted": 0,
"failed": 0,
},
"active_job": {
"job_id": 51,
"job_code": "sync-overseas-51",
"status": "running",
"items_total": 8402,
"items_terminal": 3402,
"items_failed": 0,
"display_items_running": 138,
"display_active_threads": 138,
"distributed_node_stats": [
{
"node_code": "mainland-controller-01",
"items_running": 0,
"display_running": 138,
"active_threads": 138,
"max_threads": 2000,
}
],
},
}
}
fake_conn = _SequenceConn(
fetchall_results=[
[],
[
(rich_ingest_payload, datetime(2026, 4, 23, 2, 38, 55)),
(stale_focus_ingest_payload, datetime(2026, 4, 22, 18, 2, 4)),
],
]
)
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch(
"app.services.detect_job_service._load_runtime_activity_snapshot",
return_value={"focus_job_code": "sync-overseas-51", "job_codes": ["sync-overseas-51"]},
):
snapshot = _load_latest_runtime_active_job_snapshot(15)
self.assertEqual("runtime_ingest", snapshot["_snapshot_source"])
self.assertEqual("sync-overseas-198", snapshot["job"]["job_code"])
self.assertEqual(5909, snapshot["queue_health"]["queue"]["display_running"])
def test_get_active_detect_job_summary_falls_back_to_runtime_snapshot(self) -> None:
runtime_snapshot = {
"job": {
"job_id": 551,
"job_code": "sync-overseas-55",
"status": "running",
"progress_percent": 54.08,
"node_stats": [
{
"node_code": "mainland-controller-01",
"items_total": 821,
"items_pending": 200,
"items_claimed": 0,
"items_running": 177,
"items_completed": 440,
"items_blacklisted": 0,
"items_failed": 4,
"active_threads": 177,
"max_threads": 2000,
}
],
},
"queue_health": {
"queue": {
"items_total": 821,
"pending": 200,
"claimed": 0,
"running": 177,
"display_claimed": 0,
"display_running": 177,
"completed": 440,
"blacklisted": 0,
"failed": 4,
"terminal": 444,
},
"nodes": [
{
"node_code": "mainland-controller-01",
"items_total": 821,
"items_pending": 200,
"items_claimed": 0,
"items_running": 177,
"items_completed": 440,
"items_blacklisted": 0,
"items_failed": 4,
"active_threads": 177,
"max_threads": 2000,
}
],
"throughput": {
"processed_recent": 0,
"processed_per_minute": 0,
"completed_recent": 0,
"blacklisted_recent": 0,
"failed_recent": 0,
},
},
"_created_at": "2026-04-22 18:05:36",
"_snapshot_source": "runtime_ingest",
}
fake_conn = _SequenceConn(fetchall_results=[[]])
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
summary = get_active_detect_job_summary(event_limit=10)
self.assertIsNotNone(summary)
self.assertEqual("sync-overseas-55", summary["job_code"])
self.assertEqual(821, summary["items_total"])
self.assertEqual(177, summary["display_items_running"])
def test_recycle_expired_detect_job_items_once_refreshes_touched_jobs(self) -> None:
conn = MagicMock()
main_cursor_cm = MagicMock()
main_cursor = MagicMock()
unlock_cursor_cm = MagicMock()
unlock_cursor = MagicMock()
main_cursor_cm.__enter__.return_value = main_cursor
main_cursor_cm.__exit__.return_value = False
unlock_cursor_cm.__enter__.return_value = unlock_cursor
unlock_cursor_cm.__exit__.return_value = False
conn.cursor.side_effect = [main_cursor_cm, unlock_cursor_cm]
main_cursor.fetchone.side_effect = [
(True,),
(2, [11, 12]),
("domain_pipeline", 1, 0, 0, 0, 0),
("single_step", 0, 0, 1, 0, 0),
]
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
db_cm.__exit__.return_value = False
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
with patch("app.services.detect_job_service.push_debug_event") as mock_push:
recycled = detect_job_service_module._recycle_expired_detect_job_items_once()
self.assertEqual(2, recycled)
self.assertTrue(any("UPDATE detect_job_items" in call.args[0] for call in main_cursor.execute.call_args_list))
self.assertTrue(any("FOR UPDATE SKIP LOCKED" in call.args[0] for call in main_cursor.execute.call_args_list))
self.assertTrue(
any(
"UPDATE detect_jobs" in call.args[0]
and call.args[1] == (11,)
for call in main_cursor.execute.call_args_list
)
)
self.assertTrue(
any(
"UPDATE detect_jobs" in call.args[0]
and call.args[1] == ("failed", 12)
for call in main_cursor.execute.call_args_list
)
)
unlock_cursor.execute.assert_called_once_with(
"SELECT pg_advisory_unlock(%s)",
(detect_job_service_module._DETECT_JOB_ITEM_RECYCLE_ADVISORY_LOCK_KEY,),
)
self.assertGreaterEqual(conn.commit.call_count, 1)
conn.rollback.assert_not_called()
mock_push.assert_called_once()
def test_recycle_expired_detect_job_items_once_skips_when_lock_is_busy(self) -> None:
conn = MagicMock()
main_cursor_cm = MagicMock()
main_cursor = MagicMock()
main_cursor_cm.__enter__.return_value = main_cursor
main_cursor_cm.__exit__.return_value = False
conn.cursor.return_value = main_cursor_cm
main_cursor.fetchone.return_value = (False,)
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
db_cm.__exit__.return_value = False
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
with patch("app.services.detect_job_service.push_debug_event") as mock_push:
recycled = detect_job_service_module._recycle_expired_detect_job_items_once()
self.assertEqual(0, recycled)
conn.rollback.assert_called_once()
conn.commit.assert_not_called()
mock_push.assert_not_called()
def test_get_active_detect_job_summary_attempts_expired_item_recycle_before_query(self) -> None:
fake_conn = _SequenceConn(fetchall_results=[[]])
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._maybe_recycle_expired_detect_job_items") as mock_recycle:
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value={}):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
summary = get_active_detect_job_summary(event_limit=10)
self.assertIsNone(summary)
mock_recycle.assert_called_once_with()
def test_get_active_detect_job_summary_prefers_runtime_focus_job_code(self) -> None:
rows = [
(
85,
"sync-overseas-376",
"manual",
"domain_pipeline",
"",
"running",
"tester",
datetime(2026, 4, 22, 22, 0, 0),
datetime(2026, 4, 22, 22, 0, 1),
None,
),
(
11,
"sync-overseas-51",
"manual",
"domain_pipeline",
"",
"running",
"tester",
datetime(2026, 4, 22, 17, 0, 0),
datetime(2026, 4, 22, 17, 0, 1),
None,
),
]
fake_conn = _SequenceConn(fetchall_results=[rows])
selected_rows = []
def _fake_fetch(_cur, row, event_limit=20):
selected_rows.append((row, event_limit))
return {"job_id": row[0], "job_code": row[1], "status": row[5]}
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch(
"app.services.detect_job_service._load_runtime_activity_snapshot",
return_value={"focus_job_code": "sync-overseas-51", "job_codes": ["sync-overseas-51"]},
):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value={}):
with patch("app.services.detect_job_service._fetch_job_summary", side_effect=_fake_fetch):
with patch(
"app.services.detect_job_service._enrich_active_job_summary_with_runtime",
side_effect=lambda summary, **kwargs: summary,
):
summary = get_active_detect_job_summary(event_limit=10)
self.assertEqual("sync-overseas-51", summary["job_code"])
self.assertEqual("sync-overseas-51", selected_rows[0][0][1])
self.assertEqual(10, selected_rows[0][1])
def test_get_active_detect_job_summary_prefers_runtime_snapshot_job_code_over_stale_runtime_focus(self) -> None:
rows = [
(
85,
"sync-overseas-376",
"manual",
"domain_pipeline",
"",
"running",
"tester",
datetime(2026, 4, 22, 22, 0, 0),
datetime(2026, 4, 22, 22, 0, 1),
None,
),
(
11,
"sync-overseas-51",
"manual",
"domain_pipeline",
"",
"running",
"tester",
datetime(2026, 4, 22, 17, 0, 0),
datetime(2026, 4, 22, 17, 0, 1),
None,
),
]
fake_conn = _SequenceConn(fetchall_results=[rows])
selected_rows = []
def _fake_fetch(_cur, row, event_limit=20):
selected_rows.append((row, event_limit))
return {"job_id": row[0], "job_code": row[1], "status": row[5]}
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch(
"app.services.detect_job_service._load_runtime_activity_snapshot",
return_value={"focus_job_code": "sync-overseas-51", "job_codes": ["sync-overseas-51"]},
):
with patch(
"app.services.detect_job_service._load_latest_runtime_active_job_snapshot",
return_value={"job": {"job_code": "sync-overseas-376"}},
):
with patch("app.services.detect_job_service._fetch_job_summary", side_effect=_fake_fetch):
with patch(
"app.services.detect_job_service._enrich_active_job_summary_with_runtime",
side_effect=lambda summary, **kwargs: summary,
):
summary = get_active_detect_job_summary(event_limit=10)
self.assertEqual("sync-overseas-376", summary["job_code"])
self.assertEqual("sync-overseas-376", selected_rows[0][0][1])
self.assertEqual(10, selected_rows[0][1])
def test_get_active_detect_job_summary_falls_back_when_detect_jobs_legacy_columns_are_missing(self) -> None:
rows = [
(
85,
"sync-overseas-376",
"manual",
"domain_pipeline",
"",
"running",
"tester",
datetime(2026, 4, 22, 22, 0, 0),
datetime(2026, 4, 22, 22, 0, 1),
None,
),
]
class LegacyCursor(_SequenceCursor):
def execute(self, sql, params=None):
self.exec_calls.append((sql, params))
normalized_sql = str(sql or "")
if (
"FROM detect_jobs" in normalized_sql
and "task_mode" in normalized_sql
and " AS task_mode" not in normalized_sql
):
raise errors.UndefinedColumn()
class LegacyConn(_SequenceConn):
def __init__(self) -> None:
self.cursor_obj = LegacyCursor(fetchall_results=[rows])
fake_conn = LegacyConn()
with patch.object(detect_job_service_module, "_DETECT_JOBS_LEGACY_SELECT_MODE", None):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value={}):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value={}):
with patch(
"app.services.detect_job_service._fetch_job_summary",
side_effect=lambda _cur, row, event_limit=20: {
"job_id": row[0],
"job_code": row[1],
"task_mode": row[3],
"step_code": row[4],
"status": row[5],
},
):
with patch(
"app.services.detect_job_service._enrich_active_job_summary_with_runtime",
side_effect=lambda summary, **kwargs: summary,
):
summary = get_active_detect_job_summary(event_limit=10)
self.assertEqual("sync-overseas-376", summary["job_code"])
self.assertEqual("domain_pipeline", summary["task_mode"])
self.assertEqual("", summary["step_code"])
self.assertTrue(any(" AS task_mode" in call[0] for call in fake_conn.cursor_obj.exec_calls))
def test_build_display_summary_ignores_inflated_raw_current_load(self) -> None:
summary = _build_display_summary(
[
{
"node_code": "mainland-controller-01",
"items_claimed": 0,
"items_running": 0,
"display_running": 729,
"current_load": 729,
"active_threads": 729,
"max_threads": 800,
"items_completed": 196,
"items_failed": 0,
},
{
"node_code": "mainland-worker-01",
"items_claimed": 0,
"items_running": 0,
"display_running": 753,
"current_load": 753,
"active_threads": 61,
"max_threads": 400,
"items_completed": 146,
"items_failed": 0,
},
]
)
self.assertEqual(790, summary["display_running"])
self.assertEqual(790, summary["current_load"])
self.assertEqual(790, summary["active_threads"])
def test_enrich_active_job_summary_with_runtime_adds_runtime_job_code_and_recent_events(self) -> None:
summary = _enrich_active_job_summary_with_runtime(
{
"job_id": 275,
"job_code": "sync-overseas-5297",
"processed_recent": 0,
"processed_per_minute": 0,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
"recent_domain_events": [],
},
event_limit=20,
window_minutes=15,
runtime_activity={
"focus_job_code": "sync-overseas-9506",
"job_codes": ["sync-overseas-9506", "sync-overseas-27456"],
"processed_recent": 120,
"completed_recent": 118,
"failed_recent": 1,
"blacklisted_recent": 1,
},
runtime_snapshot={
"job": {
"job_id": 1835,
"job_code": "sync-overseas-9506",
"progress_percent": 44.2,
},
"queue_health": {
"queue": {
"items_total": 1000,
"pending": 176,
"claimed": 223,
"running": 159,
"completed": 442,
"display_running": 220,
}
},
},
recent_domain_events=[
{
"event_type": "domain_completed",
"message": "域名检测完成: example.com",
"created_at": "2026-04-21 04:10:00",
}
],
)
self.assertEqual("sync-overseas-9506", summary["runtime_job_code"])
self.assertEqual(["sync-overseas-9506", "sync-overseas-27456"], summary["runtime_job_codes"])
self.assertEqual(120, summary["processed_recent"])
self.assertEqual(8.0, summary["processed_per_minute"])
self.assertEqual(118, summary["completed_recent"])
self.assertEqual(1, summary["failed_recent"])
self.assertEqual(1, summary["blacklisted_recent"])
self.assertEqual(1, len(summary["recent_domain_events"]))
self.assertEqual("sync-overseas-9506", summary["runtime_snapshot_job_code"])
self.assertEqual(1000, summary["runtime_snapshot_queue"]["items_total"])
self.assertEqual(1000, summary["items_total"])
self.assertEqual(176, summary["items_pending"])
self.assertEqual(223, summary["items_claimed"])
self.assertEqual(159, summary["items_running"])
self.assertEqual(442, summary["items_completed"])
self.assertEqual(220, summary["display_items_running"])
def test_enrich_active_job_summary_with_runtime_prefers_snapshot_identity_and_events(self) -> None:
summary = _enrich_active_job_summary_with_runtime(
{
"job_id": 275,
"job_code": "sync-overseas-5297",
"status": "running",
"recent_events": [
{
"node_code": "mainland-worker-01",
"event_type": "domain_started",
"message": "开始检测域名: stale.com",
"payload": {"job_code": "sync-overseas-5297"},
"created_at": "2026-04-21 11:58:00",
}
],
"current_cycle_events": [],
"latest_event": None,
"display_items_running": 0,
"display_current_load": 0,
"display_active_threads": 0,
"display_max_threads": 0,
"display_active_node_codes": [],
},
event_limit=10,
window_minutes=15,
runtime_activity={
"focus_job_code": "sync-overseas-31437",
"job_codes": ["sync-overseas-31437"],
"processed_recent": 10,
"completed_recent": 10,
"failed_recent": 0,
"blacklisted_recent": 0,
},
runtime_snapshot={
"job": {
"job_id": 1902,
"job_code": "sync-overseas-31437",
"status": "running",
"progress_percent": 34.3,
"node_stats": [
{
"node_code": "mainland-controller-01",
"items_claimed": 230,
"items_running": 67,
"items_completed": 343,
"items_failed": 0,
"active_threads": 170,
"max_threads": 2000,
},
{
"node_code": "mainland-worker-01",
"items_claimed": 75,
"items_running": 0,
"items_completed": 0,
"items_failed": 0,
"active_threads": 19,
"max_threads": 1200,
},
],
},
"queue_health": {
"queue": {
"items_total": 1000,
"pending": 285,
"claimed": 305,
"running": 67,
"completed": 343,
"display_running": 189,
}
},
"recent_events": [
{
"job_id": 2137,
"node_code": "mainland-controller-01",
"event_type": "job_created",
"message": "同步拉取待检测批次 sync-overseas-35461共 1000 个任务项",
"payload": {"source_record_id": 35461},
"created_at": "2026-04-22 01:21:29",
},
{
"job_id": 1902,
"node_code": "mainland-controller-01",
"event_type": "worker_log",
"message": "从任务队列获取到 125 个需要检测的域名",
"payload": {"job_code": "sync-overseas-31437"},
"created_at": "2026-04-22 01:19:25",
},
],
},
)
self.assertEqual(1902, summary["job_id"])
self.assertEqual("sync-overseas-31437", summary["job_code"])
self.assertEqual("sync-overseas-31437", summary["runtime_job_code"])
self.assertEqual(189, summary["display_items_running"])
self.assertEqual(189, summary["display_current_load"])
self.assertEqual(189, summary["display_active_threads"])
self.assertEqual(3200, summary["display_max_threads"])
self.assertEqual(["mainland-controller-01", "mainland-worker-01"], summary["display_active_node_codes"])
self.assertEqual(1, len(summary["recent_events"]))
self.assertEqual("worker_log", summary["latest_event"]["event_type"])
self.assertEqual("sync-overseas-31437", summary["recent_events"][0]["payload"]["job_code"])
def test_enrich_active_job_summary_with_runtime_does_not_promote_unassigned_snapshot_running(self) -> None:
summary = _enrich_active_job_summary_with_runtime(
{
"job_id": 384,
"job_code": "sync-overseas-16654",
"status": "running",
"display_items_running": 331,
"display_current_load": 331,
"display_active_threads": 331,
"display_max_threads": 1000,
"display_active_node_codes": [],
},
event_limit=10,
window_minutes=15,
runtime_activity={
"focus_job_code": "sync-overseas-16654",
"job_codes": ["sync-overseas-16654"],
"processed_recent": 0,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
},
runtime_snapshot={
"_has_runtime_node_rows": True,
"_dropped_runtime_node_count": 0,
"job": {
"job_id": 384,
"job_code": "sync-overseas-16654",
"status": "running",
"node_stats": [
{
"node_code": "unassigned",
"items_claimed": 23,
"items_running": 1863,
"display_running": 1863,
"active_threads": 0,
"max_threads": 0,
}
],
},
"queue_health": {
"queue": {
"items_total": 4000,
"pending": 0,
"claimed": 23,
"running": 2100,
"completed": 1117,
"blacklisted": 0,
"failed": 760,
"display_running": 2100,
},
"nodes": [
{
"node_code": "unassigned",
"items_claimed": 23,
"items_running": 1863,
"display_running": 1863,
"active_threads": 0,
"max_threads": 0,
}
],
},
},
)
self.assertEqual(0, summary["display_items_running"])
self.assertEqual(0, summary["display_active_threads"])
self.assertEqual([], summary["display_active_node_codes"])
def test_get_detect_queue_health_preserves_runtime_node_throughput_after_snapshot_override(self) -> None:
lease_row = (None, None, 0, 0)
throughput_rows = []
step_throughput_rows = []
runtime_display_rows = [
(
"mainland-controller-01",
"mainland",
"control",
"busy",
170,
{
"job_items_total": 265,
"job_items_claimed": 85,
"job_items_running": 0,
"job_items_completed": 105,
"active_threads": 170,
"max_threads": 2000,
"detect_participating": True,
},
None,
),
(
"mainland-worker-01",
"mainland",
"worker",
"busy",
19,
{
"job_items_total": 139,
"job_items_claimed": 64,
"job_items_running": 0,
"job_items_completed": 4,
"active_threads": 19,
"max_threads": 1200,
"detect_participating": True,
},
None,
),
(
"overseas-control-01",
"overseas",
"control",
"busy",
371,
{
"active_threads": 371,
"max_threads": 3200,
"detect_participating": False,
},
None,
),
]
conn = MagicMock()
cursor_cm = MagicMock()
cursor = MagicMock()
conn.cursor.return_value = cursor_cm
cursor_cm.__enter__.return_value = cursor
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
cursor.fetchone.return_value = lease_row
cursor.fetchall.side_effect = [throughput_rows, step_throughput_rows, runtime_display_rows]
active_job = {
"job_id": 1937,
"job_code": "sync-overseas-31987",
"status": "running",
"items_total": 1000,
"items_pending": 596,
"items_claimed": 149,
"items_running": 146,
"items_completed": 109,
"items_blacklisted": 0,
"items_failed": 0,
"progress_percent": 10.9,
"node_stats": [],
"distributed_node_stats": [
{
"node_code": "mainland-controller-01",
"items_total": 265,
"items_pending": 0,
"items_claimed": 85,
"items_running": 75,
"items_completed": 105,
"items_blacklisted": 0,
"items_failed": 0,
"metrics_source": "runtime",
},
{
"node_code": "mainland-worker-01",
"items_total": 139,
"items_pending": 0,
"items_claimed": 64,
"items_running": 71,
"items_completed": 4,
"items_blacklisted": 0,
"items_failed": 0,
"metrics_source": "runtime",
},
],
"step_stats": [
{
"step_code": "detect_register",
"items_total": 1000,
"items_pending": 449,
"items_claimed": 131,
"items_running": 273,
"items_completed": 147,
"items_blacklisted": 0,
"items_failed": 0,
}
],
}
runtime_activity = {
"processed_recent": 133,
"completed_recent": 113,
"failed_recent": 20,
"blacklisted_recent": 0,
"focus_job_code": "sync-overseas-31563",
"job_codes": ["sync-overseas-31563"],
"step_code": "detect_register",
"nodes": {
"mainland-controller-01": {
"node_code": "mainland-controller-01",
"processed_recent": 74,
"completed_recent": 54,
"failed_recent": 20,
"blacklisted_recent": 0,
},
"mainland-worker-01": {
"node_code": "mainland-worker-01",
"processed_recent": 59,
"completed_recent": 59,
"failed_recent": 0,
"blacklisted_recent": 0,
},
},
}
runtime_snapshot = {
"job": {
"job_id": 1937,
"job_code": "sync-overseas-31987",
"progress_percent": 10.9,
},
"queue_health": {
"queue": {
"items_total": 1000,
"pending": 596,
"claimed": 149,
"running": 146,
"completed": 109,
"blacklisted": 0,
"failed": 0,
"display_claimed": 149,
"display_running": 403,
},
"nodes": [
{
"node_code": "mainland-controller-01",
"items_total": 265,
"items_pending": 0,
"items_claimed": 85,
"items_running": 75,
"items_completed": 105,
"items_blacklisted": 0,
"items_failed": 0,
"processed_recent": 0,
"processed_per_minute": 0,
"completed_recent": 0,
"blacklisted_recent": 0,
"failed_recent": 0,
"metrics_source": "runtime",
},
{
"node_code": "mainland-worker-01",
"items_total": 139,
"items_pending": 0,
"items_claimed": 64,
"items_running": 71,
"items_completed": 4,
"items_blacklisted": 0,
"items_failed": 0,
"processed_recent": 0,
"processed_per_minute": 0,
"completed_recent": 0,
"blacklisted_recent": 0,
"failed_recent": 0,
"metrics_source": "runtime",
},
],
"steps": [],
},
}
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.settings.node_code", "overseas-control-01"):
with patch("app.services.detect_job_service.get_active_detect_job_summary", return_value=active_job):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value=runtime_activity):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service._load_runtime_display_rows", return_value=runtime_display_rows):
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
health = get_detect_queue_health(window_minutes=15)
node_map = {item["node_code"]: item for item in health["nodes"]}
self.assertEqual(74, node_map["mainland-controller-01"]["processed_recent"])
self.assertEqual(54, node_map["mainland-controller-01"]["completed_recent"])
self.assertEqual(20, node_map["mainland-controller-01"]["failed_recent"])
self.assertEqual(170, node_map["mainland-controller-01"]["items_running"])
self.assertEqual(59, node_map["mainland-worker-01"]["processed_recent"])
self.assertEqual(59, node_map["mainland-worker-01"]["completed_recent"])
self.assertEqual(19, node_map["mainland-worker-01"]["items_running"])
self.assertEqual(113, health["throughput"]["completed_recent"])
self.assertEqual(189, health["queue"]["display_running"])
self.assertNotIn("overseas-control-01", node_map)
def test_get_detect_queue_health_preserves_snapshot_node_display_metrics_for_child_instances(self) -> None:
lease_row = (None, None, 0, 0)
throughput_rows = []
step_throughput_rows = []
runtime_display_rows = []
conn = MagicMock()
cursor_cm = MagicMock()
cursor = MagicMock()
conn.cursor.return_value = cursor_cm
cursor_cm.__enter__.return_value = cursor
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
cursor.fetchone.return_value = lease_row
cursor.fetchall.side_effect = [throughput_rows, step_throughput_rows, runtime_display_rows]
active_job = {
"job_id": 198,
"job_code": "sync-overseas-198",
"status": "running",
"items_total": 10344,
"items_pending": 4945,
"items_claimed": 0,
"items_running": 5909,
"items_completed": 5344,
"items_blacklisted": 0,
"items_failed": 42,
"progress_percent": 52.07,
"display_items_claimed": 13,
"display_items_running": 11799,
"display_active_threads": 11799,
"display_max_threads": 75400,
"node_stats": [],
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-ae",
"items_total": 947,
"items_pending": 0,
"items_claimed": 0,
"items_running": 947,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
},
{
"node_code": "mainland-controller-01-au",
"items_total": 637,
"items_pending": 0,
"items_claimed": 0,
"items_running": 637,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
},
],
"step_stats": [],
}
runtime_activity = {
"processed_recent": 0,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
"focus_job_code": "sync-overseas-198",
"job_codes": ["sync-overseas-198"],
"step_code": "detect_register",
"nodes": {},
}
runtime_snapshot = {
"job": {
"job_id": 198,
"job_code": "sync-overseas-198",
"progress_percent": 52.07,
},
"queue_health": {
"queue": {
"items_total": 10344,
"pending": 4945,
"claimed": 0,
"running": 5909,
"completed": 5344,
"blacklisted": 0,
"failed": 42,
"display_claimed": 13,
"display_running": 5909,
},
"nodes": [
{
"node_code": "mainland-controller-01-ae",
"items_total": 947,
"items_pending": 0,
"items_claimed": 0,
"items_running": 947,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"display_running": 947,
"current_load": 947,
"active_threads": 947,
"max_threads": 1000,
},
{
"node_code": "mainland-controller-01-au",
"items_total": 637,
"items_pending": 0,
"items_claimed": 0,
"items_running": 637,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"display_running": 637,
"current_load": 637,
"active_threads": 637,
"max_threads": 1000,
},
],
"steps": [],
},
}
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.settings.node_code", "overseas-control-01"):
with patch("app.services.detect_job_service.get_active_detect_job_summary", return_value=active_job):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value=runtime_activity):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
with patch("app.services.detect_job_service._load_disabled_managed_node_codes", return_value=set()):
health = get_detect_queue_health(window_minutes=15)
node_map = {item["node_code"]: item for item in health["nodes"]}
self.assertEqual(1584, health["queue"]["display_running"])
self.assertEqual(947, node_map["mainland-controller-01-ae"]["display_running"])
self.assertEqual(947, node_map["mainland-controller-01-ae"]["active_threads"])
self.assertEqual(1000, node_map["mainland-controller-01-ae"]["max_threads"])
self.assertEqual(637, node_map["mainland-controller-01-au"]["display_running"])
self.assertEqual(637, node_map["mainland-controller-01-au"]["active_threads"])
def test_get_detect_queue_health_drops_stale_runtime_nodes_from_display_running(self) -> None:
lease_row = (None, None, 0, 0)
throughput_rows = []
step_throughput_rows = []
runtime_display_rows = []
conn = MagicMock()
cursor_cm = MagicMock()
cursor = MagicMock()
conn.cursor.return_value = cursor_cm
cursor_cm.__enter__.return_value = cursor
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
cursor.fetchone.return_value = lease_row
cursor.fetchall.side_effect = [throughput_rows, step_throughput_rows, runtime_display_rows]
stale_heartbeat = (datetime.now() - timedelta(minutes=8)).isoformat(sep=" ", timespec="seconds")
live_heartbeat = datetime.now().isoformat(sep=" ", timespec="seconds")
active_job = {
"job_id": 199,
"job_code": "sync-overseas-199",
"status": "running",
"items_total": 2600,
"items_pending": 500,
"items_claimed": 0,
"items_running": 2100,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"progress_percent": 80.77,
"display_items_running": 2100,
"display_active_threads": 2100,
"display_max_threads": 2000,
"node_stats": [],
"distributed_node_stats": [
{
"node_code": "mainland-controller-01-a",
"items_total": 300,
"items_pending": 0,
"items_claimed": 0,
"items_running": 300,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
},
{
"node_code": "mainland-controller-01-b",
"items_total": 1800,
"items_pending": 0,
"items_claimed": 0,
"items_running": 1800,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
},
],
"step_stats": [],
}
runtime_activity = {
"processed_recent": 0,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
"focus_job_code": "sync-overseas-199",
"job_codes": ["sync-overseas-199"],
"step_code": "detect_register",
"nodes": {},
}
runtime_snapshot = {
"_created_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
"job": {
"job_id": 199,
"job_code": "sync-overseas-199",
"progress_percent": 80.77,
},
"queue_health": {
"queue": {
"items_total": 2600,
"pending": 500,
"claimed": 0,
"running": 2100,
"completed": 0,
"blacklisted": 0,
"failed": 0,
"display_claimed": 0,
"display_running": 2100,
},
"nodes": [
{
"node_code": "mainland-controller-01-a",
"items_total": 300,
"items_pending": 0,
"items_claimed": 0,
"items_running": 300,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"display_running": 300,
"current_load": 300,
"active_threads": 300,
"max_threads": 1000,
"status": "busy",
"last_heartbeat_at": live_heartbeat,
},
{
"node_code": "mainland-controller-01-b",
"items_total": 1800,
"items_pending": 0,
"items_claimed": 0,
"items_running": 1800,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"display_running": 1800,
"current_load": 1800,
"active_threads": 1800,
"max_threads": 1000,
"status": "stale",
"last_heartbeat_at": stale_heartbeat,
},
],
"steps": [],
},
}
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.settings.node_code", "overseas-control-01"):
with patch("app.services.detect_job_service.get_active_detect_job_summary", return_value=active_job):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value=runtime_activity):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service._load_runtime_display_rows", return_value=runtime_display_rows):
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
health = get_detect_queue_health(window_minutes=15)
self.assertEqual(300, health["queue"]["running"])
self.assertEqual(300, health["queue"]["display_running"])
self.assertEqual(["mainland-controller-01-a"], [item["node_code"] for item in health["nodes"]])
def test_enrich_active_job_summary_drops_old_runtime_ingest_nodes_without_heartbeat(self) -> None:
stale_snapshot_created_at = (datetime.now() - timedelta(minutes=12)).isoformat(sep=" ", timespec="seconds")
summary = _enrich_active_job_summary_with_runtime(
{
"job_id": 384,
"job_code": "sync-overseas-16654",
"status": "running",
"display_items_running": 0,
"display_current_load": 0,
"display_active_threads": 0,
"display_max_threads": 0,
"display_active_node_codes": [],
},
event_limit=10,
window_minutes=15,
runtime_activity={
"focus_job_code": "sync-overseas-16654",
"job_codes": ["sync-overseas-16654"],
"processed_recent": 0,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
},
runtime_snapshot={
"_snapshot_source": "runtime_ingest",
"_created_at": stale_snapshot_created_at,
"job": {
"job_id": 384,
"job_code": "sync-overseas-16654",
"status": "running",
"display_items_running": 2100,
"display_active_threads": 2100,
"node_stats": [
{
"node_code": "mainland-controller-01-ah",
"items_running": 62,
"display_running": 62,
},
{
"node_code": "mainland-controller-01-bn",
"items_running": 61,
"display_running": 61,
},
{
"node_code": "unassigned",
"items_running": 1863,
"display_running": 1863,
},
],
},
"queue_health": {
"queue": {
"items_total": 4000,
"pending": 0,
"claimed": 23,
"running": 2100,
"completed": 1117,
"blacklisted": 0,
"failed": 760,
"display_running": 2100,
},
"nodes": [
{
"node_code": "mainland-controller-01-ah",
"items_running": 62,
"display_running": 62,
},
{
"node_code": "mainland-controller-01-bn",
"items_running": 61,
"display_running": 61,
},
{
"node_code": "unassigned",
"items_running": 1863,
"display_running": 1863,
},
],
},
},
)
self.assertEqual(0, summary["display_items_running"])
self.assertEqual(0, summary["display_active_threads"])
self.assertEqual([], summary["display_active_node_codes"])
def test_get_detect_queue_health_aligns_runtime_job_identity_with_active_job(self) -> None:
lease_row = (None, None, 0, 0)
throughput_rows = []
step_throughput_rows = []
runtime_display_rows = []
conn = MagicMock()
cursor_cm = MagicMock()
cursor = MagicMock()
conn.cursor.return_value = cursor_cm
cursor_cm.__enter__.return_value = cursor
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
cursor.fetchone.return_value = lease_row
cursor.fetchall.side_effect = [throughput_rows, step_throughput_rows, runtime_display_rows]
active_job = {
"job_id": 112,
"job_code": "sync-overseas-599",
"runtime_job_code": "sync-overseas-599",
"runtime_snapshot_job_code": "sync-overseas-599",
"status": "running",
"items_total": 5274,
"items_pending": 4959,
"items_claimed": 0,
"items_running": 840,
"items_completed": 274,
"items_blacklisted": 0,
"items_failed": 21,
"progress_percent": 5.59,
"display_items_claimed": 17,
"display_items_running": 1377,
"display_active_threads": 1377,
"display_max_threads": 81200,
"node_stats": [],
"distributed_node_stats": [],
"step_stats": [],
}
runtime_activity = {
"processed_recent": 93,
"completed_recent": 24,
"failed_recent": 69,
"blacklisted_recent": 0,
"focus_job_code": "sync-overseas-198",
"job_codes": ["sync-overseas-198", "sync-overseas-97"],
"step_code": "detect_360_site",
"step_stats": {},
"nodes": {},
}
runtime_snapshot = {
"job": {
"job_id": 112,
"job_code": "sync-overseas-599",
"status": "running",
"progress_percent": 5.59,
},
"queue_health": {
"queue": {
"items_total": 5274,
"pending": 4959,
"claimed": 0,
"running": 1377,
"display_claimed": 17,
"display_running": 1377,
"completed": 274,
"blacklisted": 0,
"failed": 21,
},
"nodes": [],
"steps": [],
},
}
with patch("app.services.detect_job_service.get_active_detect_job_summary", return_value=active_job):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value=runtime_activity):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
with patch("app.services.detect_job_service._load_disabled_managed_node_codes", return_value=set()):
health = get_detect_queue_health(window_minutes=15)
self.assertEqual("sync-overseas-599", health["job"]["job_code"])
self.assertEqual("sync-overseas-599", health["job"]["runtime_job_code"])
self.assertEqual("sync-overseas-599", health["job"]["runtime_job_codes"][0])
self.assertEqual(1377, health["queue"]["running"])
self.assertEqual(1377, health["queue"]["display_running"])
def test_get_detect_queue_health_ignores_mismatched_runtime_overlay_for_unrelated_job(self) -> None:
lease_row = (None, None, 0, 0)
throughput_rows = []
step_throughput_rows = []
runtime_display_rows = []
conn = MagicMock()
cursor_cm = MagicMock()
cursor = MagicMock()
conn.cursor.return_value = cursor_cm
cursor_cm.__enter__.return_value = cursor
db_cm = MagicMock()
db_cm.__enter__.return_value = conn
cursor.fetchone.return_value = lease_row
cursor.fetchall.side_effect = [throughput_rows, step_throughput_rows, runtime_display_rows]
active_job = {
"job_id": 1,
"job_code": "detect-20260421232649-acfa3d",
"runtime_job_code": "sync-overseas-40",
"status": "running",
"items_total": 14961,
"items_pending": 9478,
"items_claimed": 0,
"items_running": 1,
"items_completed": 4996,
"items_blacklisted": 0,
"items_failed": 486,
"progress_percent": 36.64,
"display_items_claimed": 0,
"display_items_running": 0,
"display_active_threads": 0,
"display_max_threads": 0,
"node_stats": [],
"distributed_node_stats": [
{
"node_code": "mainland-worker-01",
"items_total": 0,
"items_pending": 0,
"items_claimed": 0,
"items_running": 0,
"display_running": 1,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"metrics_source": "agent-heartbeat",
"region": "mainland",
"role": "worker",
"status": "busy",
"current_load": 1,
"active_threads": 1,
"max_threads": 1,
"last_heartbeat_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
},
{
"node_code": "mainland-controller-01",
"items_total": 1,
"items_pending": 0,
"items_claimed": 0,
"items_running": 1,
"display_running": 0,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"metrics_source": "",
"region": "",
"role": "",
"status": "",
"current_load": 1,
"active_threads": 0,
"max_threads": 0,
"last_heartbeat_at": "",
},
{
"node_code": "unassigned",
"items_total": 14960,
"items_pending": 14960,
"items_claimed": 0,
"items_running": 0,
"display_running": 0,
"items_completed": 0,
"items_blacklisted": 0,
"items_failed": 0,
"metrics_source": "central_queue",
},
],
"step_stats": [],
}
runtime_activity = {
"processed_recent": 52,
"completed_recent": 0,
"failed_recent": 0,
"blacklisted_recent": 0,
"focus_job_code": "sync-overseas-40",
"job_codes": ["sync-overseas-40", "sync-overseas-14"],
"step_code": "detect_register",
"nodes": {
"mainland-controller-01-k": {
"node_code": "mainland-controller-01-k",
"processed_recent": 20,
"completed_recent": 20,
"failed_recent": 0,
"blacklisted_recent": 0,
},
"mainland-controller-01-a": {
"node_code": "mainland-controller-01-a",
"processed_recent": 16,
"completed_recent": 16,
"failed_recent": 0,
"blacklisted_recent": 0,
},
"mainland-controller-01-s": {
"node_code": "mainland-controller-01-s",
"processed_recent": 16,
"completed_recent": 16,
"failed_recent": 0,
"blacklisted_recent": 0,
},
},
}
runtime_snapshot = {
"_created_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
"job": {
"job_id": 551,
"job_code": "sync-overseas-17149",
"status": "running",
"progress_percent": 41.2,
},
"queue_health": {
"queue": {
"items_total": 4000,
"pending": 0,
"claimed": 2352,
"running": 51,
"display_claimed": 2352,
"display_running": 51,
"completed": 0,
"blacklisted": 0,
"failed": 0,
},
"nodes": [
{
"node_code": "mainland-controller-01-k",
"items_total": 1200,
"items_pending": 0,
"items_claimed": 1179,
"items_running": 20,
"display_running": 20,
"current_load": 20,
"active_threads": 20,
"max_threads": 900,
"status": "busy",
"last_heartbeat_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
},
{
"node_code": "mainland-controller-01-a",
"items_total": 1400,
"items_pending": 0,
"items_claimed": 1383,
"items_running": 17,
"display_running": 17,
"current_load": 17,
"active_threads": 17,
"max_threads": 900,
"status": "busy",
"last_heartbeat_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
},
{
"node_code": "mainland-controller-01-s",
"items_total": 1400,
"items_pending": 0,
"items_claimed": 1395,
"items_running": 14,
"display_running": 14,
"current_load": 14,
"active_threads": 14,
"max_threads": 900,
"status": "busy",
"last_heartbeat_at": datetime.now().isoformat(sep=" ", timespec="seconds"),
},
],
"steps": [],
},
}
with patch("app.services.detect_job_service.settings.node_region", "overseas"):
with patch("app.services.detect_job_service.settings.node_role", "control"):
with patch("app.services.detect_job_service.settings.node_code", "overseas-control-01"):
with patch("app.services.detect_job_service.get_active_detect_job_summary", return_value=active_job):
with patch("app.services.detect_job_service._load_runtime_activity_snapshot", return_value=runtime_activity):
with patch("app.services.detect_job_service._load_latest_runtime_active_job_snapshot", return_value=runtime_snapshot):
with patch("app.services.detect_job_service._load_runtime_display_rows", return_value=runtime_display_rows):
with patch("app.services.detect_job_service.get_db", return_value=db_cm):
health = get_detect_queue_health(window_minutes=15)
node_codes = [item["node_code"] for item in health["nodes"]]
self.assertEqual("detect-20260421232649-acfa3d", health["job"]["job_code"])
self.assertEqual(2, health["queue"]["running"])
self.assertEqual(2, health["queue"]["display_running"])
self.assertCountEqual(["mainland-worker-01", "mainland-controller-01", "unassigned"], node_codes)
self.assertNotIn("mainland-controller-01-k", node_codes)
def test_load_runtime_activity_snapshot_prefers_recent_runtime_snapshot_job_code(self) -> None:
event_rows = [
(
"mainland-worker-01-a",
"domain_completed",
"",
{"job_code": "sync-overseas-198", "detect_key": "detect_register"},
datetime(2026, 4, 23, 22, 8, 0),
),
(
"mainland-controller-01-a",
"domain_started",
"",
{"job_code": "sync-overseas-599", "detect_key": "detect_wayback"},
datetime(2026, 4, 23, 22, 12, 0),
),
(
"mainland-controller-01-a",
"domain_failed",
"",
{"job_code": "sync-overseas-599", "detect_key": "detect_wayback"},
datetime(2026, 4, 23, 22, 12, 5),
),
]
fake_conn = _SequenceConn(fetchall_results=[event_rows])
with patch.object(detect_job_service_module.settings, "node_region", "overseas"):
with patch.object(detect_job_service_module.settings, "node_role", "control"):
with patch("app.services.detect_job_service.get_db", return_value=fake_conn):
with patch(
"app.services.detect_job_service._load_recent_runtime_snapshot_job_codes",
return_value=["sync-overseas-599"],
):
snapshot = detect_job_service_module._load_runtime_activity_snapshot(15)
self.assertEqual("sync-overseas-599", snapshot["focus_job_code"])
self.assertEqual(["sync-overseas-599", "sync-overseas-198"], snapshot["job_codes"])
self.assertEqual(1, snapshot["processed_recent"])
self.assertEqual(1, snapshot["failed_recent"])
self.assertEqual(0, snapshot["completed_recent"])
self.assertEqual("detect_wayback", snapshot["step_code"])
self.assertIn("mainland-controller-01-a", snapshot["nodes"])
self.assertNotIn("mainland-worker-01-a", snapshot["nodes"])
def test_normalize_detect_step_code_accepts_supported_single_step(self) -> None:
self.assertEqual("detect_baidu_site", normalize_detect_step_code("detect_baidu_site"))
self.assertEqual("detect_wayback", normalize_detect_step_code("detect_wayback"))
def test_normalize_detect_step_code_rejects_unknown_step(self) -> None:
self.assertEqual("", normalize_detect_step_code("detect_unknown"))
def test_resolve_detect_job_definition_builds_single_step_job(self) -> None:
definition = resolve_detect_job_definition("detect_baidu_site")
self.assertTrue(definition["is_single_step"])
self.assertEqual("single_step", definition["task_mode"])
self.assertEqual("detect_baidu_site", definition["step_code"])
def test_resolve_detect_job_definition_builds_wayback_single_step_job(self) -> None:
definition = resolve_detect_job_definition("detect_wayback")
self.assertTrue(definition["is_single_step"])
self.assertEqual("single_step", definition["task_mode"])
self.assertEqual("detect_wayback", definition["step_code"])
def test_build_step_payload_adds_wayback_recent_years_strategy(self) -> None:
payload = _build_step_payload(
step_code="detect_wayback",
domain_snapshot={"domain": "example.com", "source_type": 2},
settings_payload={"detect_options": {"detect_wayback": True}},
)
self.assertEqual("detect_wayback", payload["step_code"])
self.assertEqual("recent_years", payload["wayback_strategy"])
self.assertEqual(5, payload["wayback_recent_years"])
self.assertTrue(payload["wayback_stop_on_first_hit"])
def test_resolve_detect_job_definition_defaults_to_domain_pipeline(self) -> None:
definition = resolve_detect_job_definition(None)
self.assertFalse(definition["is_single_step"])
self.assertEqual("domain_pipeline", definition["task_mode"])
self.assertEqual("", definition["step_code"])
def test_resolve_domain_pipeline_step_skips_yikoujia_register(self) -> None:
step_code = resolve_domain_pipeline_step(
{
"source_type": 1,
"register_status": 0,
"baidu_site": {},
"qihu360_site": {},
"chinaz_info": {},
"aizhan_info": {},
"wayback_info": {},
"jucha_info": {},
"juziseo_info": {},
},
settings_payload={
"detect_options": {
"detect_register": True,
"detect_baidu_site": True,
"detect_360_site": False,
"detect_chinaz": False,
"detect_aizhan": False,
"detect_wayback": False,
"detect_jucha": False,
"detect_juziseo": False,
"detect_order": ["detect_register", "detect_baidu_site"],
}
},
)
self.assertEqual("detect_baidu_site", step_code)
def test_resolve_domain_pipeline_step_moves_to_next_incomplete_step(self) -> None:
step_code = resolve_domain_pipeline_step(
{
"source_type": 2,
"register_status": 3,
"baidu_site": {"state": "passed"},
"qihu360_site": {},
"chinaz_info": {},
"aizhan_info": {},
"wayback_info": {},
"jucha_info": {},
"juziseo_info": {},
},
settings_payload={
"detect_options": {
"detect_register": True,
"detect_baidu_site": True,
"detect_360_site": True,
"detect_chinaz": False,
"detect_aizhan": False,
"detect_wayback": False,
"detect_jucha": False,
"detect_juziseo": False,
"detect_order": ["detect_register", "detect_baidu_site", "detect_360_site"],
}
},
after_step_code="detect_baidu_site",
)
self.assertEqual("detect_360_site", step_code)
def test_resolve_initial_domain_pipeline_item_builds_first_step_payload(self) -> None:
step_code, payload = resolve_initial_domain_pipeline_item(
{
"id": 10,
"domain": "example.com",
"source_type": 2,
"register_status": 0,
"baidu_site": {},
"qihu360_site": {},
"chinaz_info": {},
"aizhan_info": {},
"wayback_info": {},
"jucha_info": {},
"juziseo_info": {},
},
settings_payload={
"detect_options": {
"detect_register": True,
"detect_baidu_site": True,
"detect_order": ["detect_register", "detect_baidu_site"],
}
},
)
self.assertEqual("detect_register", step_code)
self.assertIsNotNone(payload)
self.assertEqual("detect_register", payload["step_code"])
self.assertEqual("example.com", payload["domain"])
def test_resolve_initial_domain_pipeline_item_returns_empty_when_pipeline_already_done(self) -> None:
step_code, payload = resolve_initial_domain_pipeline_item(
{
"id": 11,
"domain": "done.com",
"source_type": 2,
"register_status": 3,
"baidu_site": {"state": "passed"},
"qihu360_site": {},
"chinaz_info": {},
"aizhan_info": {},
"wayback_info": {},
"jucha_info": {},
"juziseo_info": {},
},
settings_payload={
"detect_options": {
"detect_register": True,
"detect_baidu_site": True,
"detect_order": ["detect_register", "detect_baidu_site"],
}
},
)
self.assertEqual("", step_code)
self.assertIsNone(payload)
def test_classify_pipeline_item_outcome_retries_external_failure(self) -> None:
outcome = _classify_pipeline_item_outcome(
item_status="failed",
result_payload={"state": "degraded", "message": "timeout", "retry_recommended": True},
step_code="detect_baidu_site",
attempt_count=0,
)
self.assertEqual("retry", outcome["action"])
self.assertTrue(outcome["should_retry"])
self.assertEqual("external_retry", outcome["reason_code"])
def test_classify_pipeline_item_outcome_rejects_business_failure(self) -> None:
outcome = _classify_pipeline_item_outcome(
item_status="failed",
result_payload={"state": "rejected", "message": "title contains forbidden keyword"},
step_code="detect_chinaz",
attempt_count=0,
)
self.assertEqual("reject", outcome["action"])
self.assertFalse(outcome["should_retry"])
self.assertEqual("business_reject", outcome["reason_code"])
def test_classify_pipeline_item_outcome_marks_blacklisted_terminal(self) -> None:
outcome = _classify_pipeline_item_outcome(
item_status="blacklisted",
result_payload={"state": "blacklisted", "message": "risk hit"},
step_code="detect_baidu_site",
attempt_count=0,
)
self.assertEqual("black_hit", outcome["action"])
self.assertFalse(outcome["should_retry"])
self.assertEqual("blacklisted", outcome["reason_code"])
if __name__ == "__main__":
unittest.main()