import asyncio import httpx import pytest from app.services import naver_finance as nf @pytest.fixture(autouse=True) def _reset(): for k in nf._naver_close_stats: nf._naver_close_stats[k] = 0 def test_tick_validity(): assert nf.is_krx_tick_valid(274500) # 500 단위 아님 -> 아래 확인 assert not nf.is_krx_tick_valid(274750) assert not nf.is_krx_tick_valid(1837500) assert nf.is_krx_tick_valid(1837000) assert not nf.is_krx_tick_valid(30075) assert nf.is_krx_tick_valid(30050) assert not nf.is_krx_tick_valid(0) def test_annotate_keeps_values_and_counts(): rows = [{"stck_clpr": "274750"}, {"stck_clpr": "274500"}] out = nf.annotate_naver_bars_v2(rows) assert out[0]["stck_clpr"] == "274750" and out[0]["krx_tick_ok"] is False assert out[1]["krx_tick_ok"] is True q = nf.get_naver_close_quality() assert q["checked"] == 2 and q["off_tick"] == 1 and q["off_tick_ratio"] == 0.5 def test_etf_skipped(): rows = [{"stck_clpr": "10005"}] nf.annotate_naver_bars_v2(rows, is_etf=True) assert "krx_tick_ok" not in rows[0] def test_compare_with_krx(): nv = [{"stck_bsop_date": "20260929", "stck_clpr": "318000"}, {"stck_bsop_date": "20260930", "stck_clpr": "100"}] krx = [{"stck_bsop_date": "20260929", "stck_clpr": "315500"}] r = nf.compare_naver_with_krx_v2(nv, krx) assert r == {"compared": 1, "mismatch": 1} assert nv[0]["krx_close_mismatch"] is True assert "krx_close_mismatch" not in nv[1] assert nf.get_naver_close_quality()["krx_mismatch_ratio"] == 1.0 def test_chart_fn_annotates_without_network(): body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","274750","10"],\n]' def handler(req): return httpx.Response(200, text=body) async def run(): async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c: return await nf.get_naver_daily_chart("005930", days=5, client=c) res = asyncio.run(run()) assert res[0]["stck_clpr"] == "274750" and res[0]["krx_tick_ok"] is False def _fetch(code, **kw): body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","109530","10"],\n]' def handler(req): return httpx.Response(200, text=body) async def run(): async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as c: return await nf.get_naver_daily_chart_v2(code, days=5, client=c, **kw) return asyncio.run(run()) def test_benchmark_and_registered_etf_not_counted(): res = _fetch("069500") assert "krx_tick_ok" not in res[0] nf.register_etf_codes_v2(["069660"]) try: res = asyncio.run(_wrap("069660")) assert "krx_tick_ok" not in res[0] finally: nf._registered_etf_codes.discard("069660") assert nf.get_naver_close_quality()["checked"] == 0 async def _wrap(code): body = '[["날짜","시가","고가","저가","종가","거래량"],\n["20260930","1","1","1","109530","10"],\n]' async with httpx.AsyncClient(transport=httpx.MockTransport(lambda r: httpx.Response(200, text=body))) as c: return await nf.get_naver_daily_chart("069660", days=5, client=c) def test_explicit_is_etf_and_stock_counted(): assert "krx_tick_ok" not in _fetch("005930", is_etf=True)[0] assert _fetch("005930")[0]["krx_tick_ok"] is False assert nf.get_naver_close_quality()["checked"] == 1 def test_quality_alert(): assert nf.naver_close_quality_alert_v2() is None nf._naver_close_stats.update(krx_compared=100, krx_mismatch=30) assert "30/100" in nf.naver_close_quality_alert_v2()