109 lines
3.6 KiB
Python
109 lines
3.6 KiB
Python
import datetime as dt
|
|
import sqlite3
|
|
import pytest
|
|
|
|
from app.screener.ai_news import articles_source
|
|
from app.screener.schema import ensure_screener_schema
|
|
|
|
|
|
@pytest.fixture
|
|
def conn():
|
|
c = sqlite3.connect(":memory:")
|
|
c.row_factory = sqlite3.Row
|
|
ensure_screener_schema(c)
|
|
yield c
|
|
c.close()
|
|
|
|
|
|
def _seed_master(conn, ticker, name):
|
|
conn.execute(
|
|
"INSERT INTO krx_master (ticker, name, market, market_cap, updated_at) "
|
|
"VALUES (?, ?, 'KOSPI', 1000000000, datetime('now'))",
|
|
(ticker, name),
|
|
)
|
|
|
|
|
|
def _seed_article(conn, title, summary="", crawled_at="2026-05-14T07:30:00"):
|
|
import hashlib
|
|
h = hashlib.md5(f"{title}|x".encode()).hexdigest()
|
|
conn.execute(
|
|
"INSERT INTO articles (hash, title, summary, link, press, pub_date, crawled_at) "
|
|
"VALUES (?, ?, ?, '', '', '2026-05-14', ?)",
|
|
(h, title, summary, crawled_at),
|
|
)
|
|
|
|
|
|
ASOF = dt.date(2026, 5, 14)
|
|
|
|
|
|
def test_single_ticker_match_in_title(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
_seed_article(conn, "삼성전자, HBM 양산 가시화")
|
|
conn.commit()
|
|
out, stats = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert len(out["005930"]) == 1
|
|
assert out["005930"][0]["title"] == "삼성전자, HBM 양산 가시화"
|
|
assert stats["matched_pairs"] == 1
|
|
assert stats["hit_tickers"] == 1
|
|
|
|
|
|
def test_single_ticker_match_in_summary(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
_seed_article(conn, "메모리 시장 회복세", summary="삼성전자가 1분기 어닝 서프라이즈")
|
|
conn.commit()
|
|
out, _ = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert len(out["005930"]) == 1
|
|
|
|
|
|
def test_multi_ticker_match(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
_seed_master(conn, "000660", "SK하이닉스")
|
|
_seed_article(conn, "삼성전자와 SK하이닉스, 메모리 양산 경쟁")
|
|
conn.commit()
|
|
out, stats = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930", "000660"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert len(out["005930"]) == 1
|
|
assert len(out["000660"]) == 1
|
|
assert stats["matched_pairs"] == 2
|
|
assert stats["hit_tickers"] == 2
|
|
|
|
|
|
def test_no_match_returns_empty_list(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
_seed_article(conn, "엔비디아 실적 발표", summary="AI 칩 수요 견조")
|
|
conn.commit()
|
|
out, stats = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert out["005930"] == []
|
|
assert stats["matched_pairs"] == 0
|
|
assert stats["hit_tickers"] == 0
|
|
|
|
|
|
def test_max_per_ticker_caps_results(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
for i in range(6):
|
|
_seed_article(conn, f"삼성전자 뉴스 #{i}", crawled_at=f"2026-05-14T0{i}:00:00")
|
|
conn.commit()
|
|
out, _ = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert len(out["005930"]) == 5
|
|
|
|
|
|
def test_window_days_filters_old_articles(conn):
|
|
_seed_master(conn, "005930", "삼성전자")
|
|
_seed_article(conn, "삼성전자 최신 뉴스", crawled_at="2026-05-14T07:00:00")
|
|
_seed_article(conn, "삼성전자 오래된 뉴스", crawled_at="2026-05-01T07:00:00")
|
|
conn.commit()
|
|
out, _ = articles_source.gather_articles_for_tickers(
|
|
conn, ["005930"], ASOF, window_days=1, max_per_ticker=5,
|
|
)
|
|
assert len(out["005930"]) == 1
|
|
assert "최신" in out["005930"][0]["title"]
|