# backend/tests/test_purchase_manager.py import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app")) # Also insert the backend root so that "backend.app" package is importable sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) import sqlite3 import pytest from unittest.mock import patch, MagicMock # ":memory:" 공유 커넥션 — 각 테스트에서 독립적으로 생성 def _make_mem_conn(): conn = sqlite3.connect(":memory:") conn.row_factory = sqlite3.Row return conn def test_purchase_history_has_new_columns(): """purchase_history 테이블에 신규 컬럼이 존재하는지 검증""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() cols = {r["name"] for r in mem.execute("PRAGMA table_info(purchase_history)").fetchall()} assert "numbers" in cols assert "is_real" in cols assert "source_strategy" in cols assert "source_detail" in cols assert "checked" in cols assert "results" in cols assert "total_prize" in cols # 기존 컬럼도 유지 assert "draw_no" in cols assert "amount" in cols assert "sets" in cols assert "prize" in cols assert "note" in cols mem.close() def test_strategy_performance_table_exists(): """strategy_performance 테이블이 생성되는지 검증""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() cols = {r["name"] for r in mem.execute("PRAGMA table_info(strategy_performance)").fetchall()} assert "strategy" in cols assert "draw_no" in cols assert "sets_count" in cols assert "total_correct" in cols assert "avg_score" in cols mem.close() def test_strategy_weights_table_exists(): """strategy_weights 테이블이 생성되고 초기값이 있는지 검증""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() rows = mem.execute("SELECT * FROM strategy_weights ORDER BY strategy").fetchall() strategies = {r["strategy"] for r in rows} assert strategies == {"combined", "simulation", "heatmap", "manual", "custom"} # 가중치 합이 1.0 total_weight = sum(r["weight"] for r in rows) assert abs(total_weight - 1.0) < 0.01 mem.close() def test_add_purchase_with_numbers(): """번호 포함 구매 등록""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() result = db.add_purchase( draw_no=1150, amount=5000, sets=5, numbers=[[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]], is_real=False, source_strategy="simulation", source_detail={"run_id": 42}, ) assert result["draw_no"] == 1150 assert result["amount"] == 5000 assert result["is_real"] == 0 assert result["source_strategy"] == "simulation" assert result["numbers"] == [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]] assert result["source_detail"] == {"run_id": 42} mem.close() def test_get_purchases_filter_is_real(): """is_real 필터 동작""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() db.add_purchase(draw_no=1150, amount=5000, sets=5, is_real=True) db.add_purchase(draw_no=1150, amount=1000, sets=1, is_real=False) real_only = db.get_purchases(is_real=True) virtual_only = db.get_purchases(is_real=False) assert len(real_only) == 1 assert real_only[0]["is_real"] == 1 assert len(virtual_only) == 1 assert virtual_only[0]["is_real"] == 0 mem.close() def test_get_purchase_stats_by_type(): """실제/가상 분리 통계""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() db.add_purchase(draw_no=1150, amount=5000, sets=5, is_real=True, source_strategy="manual") db.add_purchase(draw_no=1150, amount=1000, sets=1, is_real=False, source_strategy="simulation") stats = db.get_purchase_stats() assert "total" in stats assert "real" in stats assert "virtual" in stats assert "by_strategy" in stats assert stats["total"]["sets"] == 6 assert stats["real"]["sets"] == 5 assert stats["virtual"]["sets"] == 1 assert "manual" in stats["by_strategy"] assert "simulation" in stats["by_strategy"] # 하위호환 필드 assert "total_records" in stats assert stats["total_records"] == 2 mem.close() def test_upsert_strategy_performance(): """전략 성과 upsert""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() # 최초 insert db.upsert_strategy_performance( strategy="simulation", draw_no=1150, sets_count=10, total_correct=30, max_correct=5, prize_total=5000, avg_score=3.0, ) rows = db.get_strategy_performance(strategy="simulation") assert len(rows) == 1 assert rows[0]["sets_count"] == 10 assert rows[0]["avg_score"] == 3.0 # upsert (동일 strategy+draw_no) db.upsert_strategy_performance( strategy="simulation", draw_no=1150, sets_count=20, total_correct=60, max_correct=6, prize_total=10000, avg_score=4.5, ) rows = db.get_strategy_performance(strategy="simulation") assert len(rows) == 1 # 중복 없이 1개 assert rows[0]["sets_count"] == 20 assert rows[0]["avg_score"] == 4.5 mem.close() def test_update_strategy_weight(): """전략 가중치 업데이트""" import db mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() # 초기값 확인 weights_before = db.get_strategy_weights() combined_before = next(w for w in weights_before if w["strategy"] == "combined") original_weight = combined_before["weight"] # 업데이트 db.update_strategy_weight( strategy="combined", weight=0.5, ema_score=0.75, total_sets=100, total_hits_3plus=20, ) weights_after = db.get_strategy_weights() combined_after = next(w for w in weights_after if w["strategy"] == "combined") assert combined_after["weight"] == 0.5 assert combined_after["ema_score"] == 0.75 assert combined_after["total_sets"] == 100 assert combined_after["total_hits_3plus"] == 20 mem.close() # ── purchase_manager 테스트 ─────────────────────────────────────────────────── def _import_purchase_manager_with_mem(mem_conn): """purchase_manager를 메모리 DB에 연결된 상태로 임포트.""" import db import importlib # backend.app 패키지로 로드해 상대 임포트가 동작하게 함 import backend.app.purchase_manager as pm return pm def test_check_purchases_for_draw(): """특정 회차 구매 건들의 결과 체크""" import db import backend.app.purchase_manager as pm mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() # 당첨번호 삽입: 1125회 [3,12,23,34,38,45] bonus=7 mem.execute( """INSERT INTO draws (drw_no, drw_date, n1, n2, n3, n4, n5, n6, bonus) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (1125, "2024-12-01", 3, 12, 23, 34, 38, 45, 7), ) mem.commit() # 구매 등록: 1등 번호 세트 + 낙첨 세트 purchase = db.add_purchase( draw_no=1125, amount=2000, sets=2, numbers=[[3, 12, 23, 34, 38, 45], [1, 2, 3, 4, 5, 6]], is_real=False, source_strategy="simulation", ) with patch("db._conn", return_value=mem), \ patch("backend.app.purchase_manager.get_draw", side_effect=lambda drw: db.get_draw(drw)), \ patch("backend.app.purchase_manager.get_purchases", side_effect=lambda **kw: db.get_purchases(**kw)), \ patch("backend.app.purchase_manager.update_purchase_results", side_effect=lambda *a, **kw: db.update_purchase_results(*a, **kw)), \ patch("backend.app.purchase_manager.upsert_strategy_performance", side_effect=lambda **kw: db.upsert_strategy_performance(**kw)): count = pm.check_purchases_for_draw(1125) assert count == 1 # 결과 확인 with patch("db._conn", return_value=mem): checked = db.get_purchases(draw_no=1125, checked=True) assert len(checked) == 1 results = checked[0]["results"] assert results is not None assert len(results) == 2 # 첫 번째 세트: 6개 일치 → 1등 assert results[0]["rank"] == 1 assert results[0]["correct"] == 6 # 두 번째 세트: 3 하나만 일치 → 낙첨(correct=1) assert results[1]["rank"] == 0 assert results[1]["correct"] == 1 mem.close() def test_check_purchases_updates_strategy_performance(): """결과 체크 후 strategy_performance가 갱신되는지 검증""" import db import backend.app.purchase_manager as pm mem = _make_mem_conn() with patch("db._conn", return_value=mem): db.init_db() # 당첨번호 삽입: 1126회 mem.execute( """INSERT INTO draws (drw_no, drw_date, n1, n2, n3, n4, n5, n6, bonus) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (1126, "2024-12-08", 1, 2, 3, 4, 5, 6, 7), ) mem.commit() db.add_purchase( draw_no=1126, amount=5000, sets=5, numbers=[[1, 2, 3, 4, 5, 6], [10, 20, 30, 40, 41, 42]], is_real=False, source_strategy="simulation", ) with patch("db._conn", return_value=mem), \ patch("backend.app.purchase_manager.get_draw", side_effect=lambda drw: db.get_draw(drw)), \ patch("backend.app.purchase_manager.get_purchases", side_effect=lambda **kw: db.get_purchases(**kw)), \ patch("backend.app.purchase_manager.update_purchase_results", side_effect=lambda *a, **kw: db.update_purchase_results(*a, **kw)), \ patch("backend.app.purchase_manager.upsert_strategy_performance", side_effect=lambda **kw: db.upsert_strategy_performance(**kw)): count = pm.check_purchases_for_draw(1126) assert count == 1 with patch("db._conn", return_value=mem): perf = db.get_strategy_performance(strategy="simulation") assert len(perf) >= 1 entry = next((p for p in perf if p["draw_no"] == 1126), None) assert entry is not None, "draw_no=1126 에 대한 strategy_performance 없음" assert entry["strategy"] == "simulation" assert entry["sets_count"] == 2 # 2개 세트 mem.close()