from __future__ import annotations import tempfile import unittest from pathlib import Path from datahub.adapters.ifind import IfindAdapter from datahub.db import HubDB from datahub.serving import ApiError from datahub.steward import steward_query class _Resp: def __init__(self, payload: dict, status: int = 200) -> None: import json self.status = status self._raw = json.dumps(payload).encode("utf-8") def read(self): return self._raw def __enter__(self): return self def __exit__(self, *args): return False class IfindObservabilityTests(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 _adapter_with_urlopen(self, urlopen) -> IfindAdapter: return IfindAdapter(refresh_token="rt", access_token="at", urlopen=urlopen) def test_successful_fetch_is_logged_without_changing_rows(self) -> None: def urlopen(request, timeout=None): return _Resp( { "errorcode": 0, "tables": [{"thscode": ["000001.SZ"], "table": {"涨停原因": ["重组"]}}], } ) adapter = self._adapter_with_urlopen(urlopen) class _Api: ifind = adapter db = self.db payload = steward_query( _Api(), {"api_name": "ifind_wencai", "params": {"query": "涨停"}} ) self.assertEqual(payload["data"][0]["thscode"], "000001.SZ") log = self.db.fetchone("SELECT * FROM provider_call_log WHERE provider = 'ifind'") self.assertIsNotNone(log) self.assertEqual(log["interface"], "wencai") self.assertEqual(log["status"], "ok") def test_failed_fetch_reraises_and_logs_error(self) -> None: def urlopen(request, timeout=None): return _Resp({"errorcode": -9999, "errmsg": "quota exceeded"}) adapter = self._adapter_with_urlopen(urlopen) class _Api: ifind = adapter db = self.db with self.assertRaises(ApiError) as ctx: steward_query(_Api(), {"api_name": "ifind_wencai", "params": {"query": "涨停"}}) self.assertEqual(ctx.exception.code, "SOURCE_UNAVAILABLE") log = self.db.fetchone("SELECT * FROM provider_call_log WHERE provider = 'ifind'") self.assertIsNotNone(log) self.assertEqual(log["status"], "error") def test_status_check_alone_does_not_dial_or_log_a_fetch_call(self) -> None: class _Api: ifind = IfindAdapter() db = self.db payload = steward_query(_Api(), {"api_name": "ifind_status", "params": {}}) self.assertFalse(payload["data"][0]["configured"]) log = self.db.fetchall("SELECT * FROM provider_call_log WHERE provider = 'ifind'") self.assertEqual(log, []) def test_api_double_without_db_attribute_still_works(self) -> None: # Mirrors tests/test_ifind_adapter.py's `_Api` double, which has no # `db` attribute at all. Observability must not require it. class _Api: ifind = IfindAdapter() payload = steward_query(_Api(), {"api_name": "ifind_status", "params": {}}) self.assertFalse(payload["data"][0]["configured"]) with self.assertRaises(ApiError): steward_query(_Api(), {"api_name": "ifind_wencai", "params": {"query": "涨停"}}) if __name__ == "__main__": unittest.main()