from datetime import datetime import pytest from app.services import market_analyzer as ma def _rows(n): return [{"stck_clpr": str(100 + i * 0.1), "stck_hgpr": str(101 + i * 0.1), "stck_lwpr": str(99 + i * 0.1), "acml_vol": "1000"} for i in range(n)][::-1] class _Api: def __init__(self, rng, chart): self.rng, self.chart, self.calls = rng, chart, [] async def get_daily_ohlcv_range(self, *a): self.calls.append("range") if isinstance(self.rng, Exception): raise self.rng return self.rng async def get_daily_chart(self, *a, **k): self.calls.append("chart") return {"output2": self.chart} @pytest.fixture(autouse=True) def _setup(monkeypatch): saved = dict(ma._market_regime_cache) ma._market_regime_cache.update(result=None, ts=0.0) monkeypatch.setattr(ma, "is_market_open", lambda: True) monkeypatch.setattr(ma.settings, "TRADING_HALTED", True, raising=False) async def _naver_fail(*a, **k): raise RuntimeError("naver down") import app.services.naver_finance as nf monkeypatch.setattr(nf, "get_naver_daily_chart", _naver_fail) yield ma._market_regime_cache.update(saved) @pytest.mark.asyncio async def test_kis_range_fallback_reaches_205(monkeypatch): api = _Api(_rows(250), _rows(30)) monkeypatch.setattr(ma, "_real_market_api", api) r = await ma.check_market_regime() assert r["regime"] == "halted" # 데이터 부족 분기를 통과해 TRADING_HALTED 까지 도달 assert api.calls == ["range"] assert ma.get_regime_source_health()["source"] == "kis_range" @pytest.mark.asyncio async def test_short_data_blocks_with_error_log(monkeypatch, caplog): api = _Api(RuntimeError("kis down"), _rows(30)) monkeypatch.setattr(ma, "_real_market_api", api) caplog.set_level("ERROR") r = await ma.check_market_regime() assert r["max_trades"] == 0 assert any("신규 진입 차단" in m for m in caplog.messages) h = ma.get_regime_source_health() assert h["blocked"] is True and h["bars"] == 30 def _dated(rows, date): out = [dict(r) for r in rows] for i, r in enumerate(out): r["stck_bsop_date"] = date if i == 0 else "20200101" return out @pytest.mark.asyncio async def test_stale_naver_falls_to_kis_fresh(monkeypatch): import app.services.naver_finance as nf monkeypatch.setattr(ma, "_get_kst_now", lambda: datetime(2026, 10, 9, 10, 0)) old = list(reversed(_dated(_rows(250), "20261008"))) # oldest-first, 최신봉=전일 async def _naver(*a, **k): return old monkeypatch.setattr(nf, "get_naver_daily_chart", _naver) api = _Api(_dated(_rows(250), "20261009"), _rows(30)) monkeypatch.setattr(ma, "_real_market_api", api) await ma.check_market_regime() h = ma.get_regime_source_health() assert h["source"] == "kis_range" and h["stale"] is False assert api.calls == ["range"] @pytest.mark.asyncio async def test_all_sources_stale_warns_and_flags(monkeypatch, caplog): import app.services.naver_finance as nf monkeypatch.setattr(ma, "_get_kst_now", lambda: datetime(2026, 10, 9, 10, 0)) old = list(reversed(_dated(_rows(250), "20261008"))) async def _naver(*a, **k): return old monkeypatch.setattr(nf, "get_naver_daily_chart", _naver) monkeypatch.setattr(ma, "_real_market_api", _Api(_dated(_rows(250), "20261008"), _rows(30))) caplog.set_level("WARNING") r = await ma.check_market_regime() assert r["regime"] == "halted" assert ma.get_regime_source_health()["stale"] is True assert any("낡음" in m for m in caplog.messages)