from __future__ import annotations import tempfile import unittest from contextlib import contextmanager from pathlib import Path from unittest.mock import patch from datahub.adapters.base import AdapterError from datahub.db import HubDB from datahub.realtime_serve import fetch_index_quotes, fetch_intraday, fetch_quotes class _WriteBreaksDB: """Wraps a real HubDB but breaks only the write path, to prove the real serving path (reads/caches) is untouched by an observability failure while still exercising real fetch/cache code around it.""" def __init__(self, real: HubDB) -> None: self._real = real def fetchall(self, sql, params=()): return self._real.fetchall(sql, params) def fetchone(self, sql, params=()): return self._real.fetchone(sql, params) def execute(self, sql, params=()): return self._real.execute(sql, params) def executemany(self, sql, rows): return self._real.executemany(sql, rows) @contextmanager def write(self): raise RuntimeError("db is not writable right now") yield None # pragma: no cover class RealtimeObservabilityTests(unittest.TestCase): def setUp(self) -> None: self.tmp = tempfile.TemporaryDirectory() self.db = HubDB(Path(self.tmp.name) / "hub.db") def tearDown(self) -> None: self.tmp.cleanup() def test_eastmoney_success_is_logged_without_changing_payload(self) -> None: rows = [ {"ts_code": "000001.SH", "code": "000001", "name": "上证指数", "price": 3000.0, "previous_close": 2990.0, "quote_time_epoch": 0, "source": "eastmoney_push2"}, {"ts_code": "399001.SZ", "code": "399001", "name": "深证成指", "price": 9000.0, "previous_close": 8990.0, "quote_time_epoch": 0, "source": "eastmoney_push2"}, {"ts_code": "399006.SZ", "code": "399006", "name": "创业板指", "price": 1800.0, "previous_close": 1790.0, "quote_time_epoch": 0, "source": "eastmoney_push2"}, ] with patch("datahub.realtime_serve.EastmoneyAdapter") as mocked: mocked.return_value.fetch_indices.return_value = rows payload = fetch_index_quotes(self.db) self.assertEqual(payload["data"], rows) self.assertEqual(payload["meta"]["source"], "eastmoney:ulist") log = self.db.fetchall("SELECT * FROM provider_call_log WHERE provider = 'eastmoney'") self.assertEqual(len(log), 1) self.assertEqual(log[0]["interface"], "indices") self.assertEqual(log[0]["status"], "ok") health = self.db.fetchone( "SELECT * FROM provider_health WHERE provider = 'eastmoney' AND interface = 'indices'" ) self.assertEqual(health["state"], "ok") def test_eastmoney_failure_falls_back_to_tencent_and_logs_both(self) -> None: tencent_rows = [ {"ts_code": "000001.SH", "code": "000001", "name": "上证指数", "price": 3000.0, "previous_close": 2990.0, "quote_time_epoch": 0, "source": "tencent_qt"}, {"ts_code": "399001.SZ", "code": "399001", "name": "深证成指", "price": 9000.0, "previous_close": 8990.0, "quote_time_epoch": 0, "source": "tencent_qt"}, {"ts_code": "399006.SZ", "code": "399006", "name": "创业板指", "price": 1800.0, "previous_close": 1790.0, "quote_time_epoch": 0, "source": "tencent_qt"}, ] with patch("datahub.realtime_serve.EastmoneyAdapter") as eastmoney, patch( "datahub.realtime_serve.TencentAdapter" ) as tencent: eastmoney.return_value.fetch_indices.side_effect = AdapterError("Eastmoney returned 0/3 indices") tencent.return_value.fetch_indices.return_value = tencent_rows payload = fetch_index_quotes(self.db) self.assertEqual(payload["meta"]["source"], "tencent:qt") self.assertEqual(payload["data"], tencent_rows) east_log = self.db.fetchone("SELECT * FROM provider_call_log WHERE provider = 'eastmoney'") self.assertEqual(east_log["status"], "empty") tencent_log = self.db.fetchone("SELECT * FROM provider_call_log WHERE provider = 'tencent'") self.assertEqual(tencent_log["status"], "ok") def test_observability_db_failure_never_breaks_a_real_successful_fetch(self) -> None: rows = [ {"ts_code": "000001.SH", "price": 3000.0, "previous_close": 2990.0, "quote_time_epoch": 0}, {"ts_code": "399001.SZ", "price": 9000.0, "previous_close": 8990.0, "quote_time_epoch": 0}, {"ts_code": "399006.SZ", "price": 1800.0, "previous_close": 1790.0, "quote_time_epoch": 0}, ] broken = _WriteBreaksDB(self.db) with patch("datahub.realtime_serve.EastmoneyAdapter") as mocked: mocked.return_value.fetch_indices.return_value = rows payload = fetch_index_quotes(broken) self.assertEqual(payload["data"], rows) self.assertEqual(payload["meta"]["source"], "eastmoney:ulist") def test_observability_db_failure_never_masks_a_real_source_outage(self) -> None: broken = _WriteBreaksDB(self.db) with patch("datahub.realtime_serve.EastmoneyAdapter") as eastmoney, patch( "datahub.realtime_serve.TencentAdapter" ) as tencent: eastmoney.return_value.fetch_indices.side_effect = AdapterError("down") tencent.return_value.fetch_indices.side_effect = AdapterError("also down") with self.assertRaises(Exception): fetch_index_quotes(broken) def test_named_quotes_records_both_providers_on_partial_merge(self) -> None: with patch("datahub.realtime_serve.EastmoneyAdapter") as eastmoney, patch( "datahub.realtime_serve.TencentAdapter" ) as tencent: eastmoney.return_value.fetch_quotes.return_value = [ {"ts_code": "000001.SZ", "close": 10, "pre_close": 9, "quote_date": "20260907"}, ] tencent.return_value.fetch_quotes.return_value = [ {"ts_code": "000002.SZ", "close": 20, "pre_close": 19, "quote_date": "20260907"}, ] payload = fetch_quotes(self.db, ["000001.SZ", "000002.SZ"]) self.assertEqual(payload["meta"]["complete"], True) east_log = self.db.fetchone( "SELECT * FROM provider_call_log WHERE provider = 'eastmoney' AND interface = 'named_quotes'" ) self.assertEqual(east_log["status"], "ok") tencent_log = self.db.fetchone( "SELECT * FROM provider_call_log WHERE provider = 'tencent' AND interface = 'named_quotes'" ) self.assertEqual(tencent_log["status"], "ok") def test_intraday_success_is_logged_as_ok(self) -> None: payload_data = { "entity_type": "stock", "ts_code": "601318.SH", "trade_date": "2026-09-07", "previous_close": 55.8, "points": [{"date": "2026-09-07", "time": "09:30", "close": 55.9}], } with patch("datahub.realtime_serve.EastmoneyAdapter") as mocked: mocked.return_value.fetch_intraday.return_value = payload_data payload = fetch_intraday(self.db, "601318.SH") self.assertEqual(payload["data"], payload_data) log = self.db.fetchone( "SELECT * FROM provider_call_log WHERE provider = 'eastmoney' AND interface = 'intraday'" ) self.assertEqual(log["status"], "ok") def test_intraday_failure_is_logged_as_empty(self) -> None: with patch("datahub.realtime_serve.EastmoneyAdapter") as mocked: mocked.return_value.fetch_intraday.side_effect = AdapterError("No intraday chart data returned") with self.assertRaises(Exception): fetch_intraday(self.db, "000001.SZ") log = self.db.fetchone( "SELECT * FROM provider_call_log WHERE provider = 'eastmoney' AND interface = 'intraday'" ) self.assertEqual(log["status"], "empty") if __name__ == "__main__": unittest.main()