import gc import os import sys import tempfile _fd, _TMP = tempfile.mkstemp(suffix=".db") os.close(_fd) os.unlink(_TMP) os.environ["AGENT_OFFICE_DB_PATH"] = _TMP sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import pytest from app.curator import signal_runner from app import db db.DB_PATH = _TMP # patch frozen module-level DB_PATH (import order safety) @pytest.fixture(autouse=True) def fresh_db(): gc.collect() if os.path.exists(_TMP): os.remove(_TMP) db.init_db() yield gc.collect() if os.path.exists(_TMP): try: os.remove(_TMP) except PermissionError: pass # Windows: WAL-mode file locked; DB is ephemeral anyway def test_evaluate_and_persist_cold_start(): """첫 호출은 warmup으로 기록되고 baseline에 값이 들어간다.""" result = signal_runner.evaluate_metric_and_persist( source="light", metric="sim_signal", value=1.5, draw_no=None, z_normal=1.5, z_urgent=2.5, push_to_window=True, ) assert result["fire_level"] == "warmup" assert result["z_score"] is None bl = db.get_baseline("sim_signal") assert bl is not None assert bl["window_values"] == [1.5] def test_evaluate_after_window_filled_normal_fire(): """8회 push 후 정상 운영, 평균 대비 z≥1.5면 normal.""" for v in [1.0, 1.1, 0.9, 1.0, 1.0, 1.1, 0.9, 1.0]: signal_runner.evaluate_metric_and_persist( source="sim", metric="sim_signal", value=v, draw_no=None, z_normal=1.5, z_urgent=2.5, push_to_window=True, ) result = signal_runner.evaluate_metric_and_persist( source="sim", metric="sim_signal", value=1.12, draw_no=None, z_normal=1.5, z_urgent=2.5, push_to_window=True, ) assert result["fire_level"] in ("normal", "urgent") assert result["z_score"] is not None and result["z_score"] >= 1.5 def test_evaluate_drift_skips_same_draw_push(): """drift는 회차 단위. 같은 회차에서 두 번 호출하면 두 번째는 window push X.""" signal_runner.evaluate_metric_and_persist( source="sim", metric="drift", value=0.05, draw_no=1100, z_normal=1.5, z_urgent=2.5, push_to_window=True, ) bl_before = db.get_baseline("drift") assert bl_before["window_values"] == [0.05] assert bl_before["last_pushed_draw_no"] == 1100 signal_runner.evaluate_metric_and_persist( source="sim", metric="drift", value=0.08, draw_no=1100, z_normal=1.5, z_urgent=2.5, push_to_window=True, ) bl_after = db.get_baseline("drift") assert bl_after["window_values"] == [0.05] @pytest.mark.asyncio async def test_run_signal_check_aggregates_three_metrics(monkeypatch): """run_signal_check이 3종 메트릭 모두 평가하고 overall fire를 반환.""" async def fake_lotto_best(): return [{"numbers": [1,2,3,4,5,6], "scores": [10,10,10,10,10]}] * 20 async def fake_lotto_strategy_weights(): return {"gap_focus": 0.4, "hot_focus": 0.3, "pair_bias": 0.3} monkeypatch.setattr(signal_runner, "_fetch_best_picks", fake_lotto_best) monkeypatch.setattr(signal_runner, "_fetch_strategy_weights", fake_lotto_strategy_weights) out = await signal_runner.run_signal_check(source="light", curate_result=None, current_draw_no=1101) assert "overall_fire" in out assert "results" in out assert any(r["metric"] == "sim_signal" for r in out["results"]) # light_check는 confidence 평가 안 함 assert not any(r["metric"] == "confidence" for r in out["results"])