import unittest from unittest.mock import patch from app.services.sync_push_service import ( _build_task_pull_backlog_limits, _extract_detect_result_projection_events, _resolve_detect_result_target_job_id, _should_throttle_task_pull, ingest_runtime_projection, ) class _FakeCursor: def __init__(self, rows): self._rows = list(rows) self.executed = [] def execute(self, sql, params=None): self.executed.append((sql, params)) def fetchone(self): if self._rows: return self._rows.pop(0) return None def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False class _FakeConnection: def __init__(self, rows): self.cursor_obj = _FakeCursor(rows) self.committed = False def cursor(self): return self.cursor_obj def commit(self): self.committed = True def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False class SyncPushServiceTests(unittest.TestCase): def test_build_task_pull_backlog_limits_scales_with_thread_configuration(self) -> None: limits = _build_task_pull_backlog_limits( 5000, settings_payload={ "thread_count": 100, "node_thread_counts": { "mainland-controller-01": 2000, "mainland-worker-01": 1200, }, }, ) self.assertEqual(3200, limits["estimated_total_threads"]) self.assertEqual(6400, limits["max_pending_total"]) self.assertEqual(3200, limits["max_register_pending"]) self.assertEqual(800, limits["max_downstream_pending"]) def test_should_throttle_task_pull_when_register_backlog_overwhelms_downstream(self) -> None: should_throttle, reason = _should_throttle_task_pull( { "pending_total": 9438, "claimed_total": 410, "running_total": 15, "register_pending": 8487, "downstream_pending": 951, }, { "max_pending_total": 12000, "max_register_pending": 3200, "max_downstream_pending": 800, }, ) self.assertTrue(should_throttle) self.assertEqual("register_pending", reason) def test_should_not_throttle_task_pull_when_backlog_is_within_limits(self) -> None: should_throttle, reason = _should_throttle_task_pull( { "pending_total": 1800, "claimed_total": 100, "running_total": 300, "register_pending": 600, "downstream_pending": 120, }, { "max_pending_total": 6400, "max_register_pending": 3200, "max_downstream_pending": 800, }, ) self.assertFalse(should_throttle) self.assertEqual("", reason) def test_extract_detect_result_projection_events_adds_import_metadata(self) -> None: projection = { "job": { "job_id": 12, "job_code": "detect-local-001", }, "recent_domain_events": [ { "node_code": "mainland-worker-01", "event_type": "domain_completed", "level": "info", "message": "完成 a.com", "created_at": "2026-04-19 01:05:00", "payload": {"domain": "a.com", "domain_id": 101, "cycle_token": "cycle-1"}, } ], } events = _extract_detect_result_projection_events( source_region="mainland", source_record_id=88, projection=projection, ) self.assertEqual(1, len(events)) event = events[0] self.assertEqual("domain_completed", event["event_type"]) self.assertEqual("mainland", event["payload"]["import_source_region"]) self.assertEqual(88, event["payload"]["import_source_record_id"]) self.assertEqual("detect-local-001", event["payload"]["import_source_job_code"]) self.assertTrue(event["payload"]["imported_from_projection"]) self.assertTrue(event["payload"]["import_fingerprint"]) @patch("app.services.sync_push_service.get_db") def test_resolve_detect_result_target_job_id_prefers_matching_job_code(self, mock_get_db) -> None: fake_conn = _FakeConnection(rows=[(456,)]) mock_get_db.return_value = fake_conn target_job_id = _resolve_detect_result_target_job_id( projection={ "job": { "job_code": "sync-mainland-123", } } ) self.assertEqual(456, target_job_id) self.assertIn("WHERE job_code = %s", fake_conn.cursor_obj.executed[0][0]) @patch("app.services.detect_job_service.get_active_detect_job_summary") @patch("app.services.sync_push_service.get_db") def test_resolve_detect_result_target_job_id_falls_back_to_active_job( self, mock_get_db, mock_get_active_detect_job_summary, ) -> None: fake_conn = _FakeConnection(rows=[None]) mock_get_db.return_value = fake_conn mock_get_active_detect_job_summary.return_value = {"job_id": 789} target_job_id = _resolve_detect_result_target_job_id( projection={ "job": { "job_code": "sync-mainland-999", } } ) self.assertEqual(789, target_job_id) @patch("app.services.sync_push_service._import_detect_result_projection_events") @patch("app.services.sync_push_service.get_db") def test_ingest_runtime_projection_imports_detect_result_events_on_new_record( self, mock_get_db, mock_import_events, ) -> None: fake_conn = _FakeConnection(rows=[None, (321,)]) mock_get_db.return_value = fake_conn mock_import_events.return_value = {"imported_count": 2, "deduplicated_count": 0, "target_job_id": 9} ok, message, data = ingest_runtime_projection( { "sync_type": "detect_result_projection", "source_region": "mainland", "source_record_id": 66, "projection_hash": "hash-001", "projection": {"recent_domain_events": []}, } ) self.assertTrue(ok) self.assertEqual("同步投影接收成功", message) self.assertEqual(321, data["record_id"]) self.assertEqual(2, data["event_import"]["imported_count"]) mock_import_events.assert_called_once_with( source_region="mainland", source_record_id=66, projection={"recent_domain_events": []}, ) self.assertTrue(fake_conn.committed) @patch("app.services.sync_push_service._import_detect_result_projection_events") @patch("app.services.sync_push_service.get_db") def test_ingest_runtime_projection_imports_detect_result_events_on_deduplicated_record( self, mock_get_db, mock_import_events, ) -> None: fake_conn = _FakeConnection(rows=[(777,)]) mock_get_db.return_value = fake_conn mock_import_events.return_value = {"imported_count": 0, "deduplicated_count": 3, "target_job_id": 9} ok, message, data = ingest_runtime_projection( { "sync_type": "detect_result_projection", "source_region": "mainland", "source_record_id": 66, "projection_hash": "hash-001", "projection": {"recent_domain_events": []}, } ) self.assertTrue(ok) self.assertEqual("同步投影已存在,已按幂等处理", message) self.assertTrue(data["deduplicated"]) self.assertEqual(3, data["event_import"]["deduplicated_count"]) mock_import_events.assert_called_once_with( source_region="mainland", source_record_id=66, projection={"recent_domain_events": []}, ) if __name__ == "__main__": unittest.main()