"""历史日报解析器单元测试(基于真实样例 HTML)。""" from __future__ import annotations from datetime import date, datetime from pathlib import Path import pytest from report_import.parser import ( ReportParseError, parse_finance_report, parse_intl_report, parse_report, ) FIXTURES = Path(__file__).parent / "fixtures" FINANCE_HTML = (FIXTURES / "finance_news_daily_20260710_0720.html").read_text(encoding="utf-8") INTL_HTML = (FIXTURES / "intl_news_daily_20260711_070304.html").read_text(encoding="utf-8") class TestFinanceParse: def test_metadata(self) -> None: r = parse_finance_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") assert r.report_date == date(2026, 7, 10) assert r.report_type == "finance" assert r.file_name == "finance_news_daily_20260710_0720.html" assert r.generated_at == datetime(2026, 7, 11, 7, 20, 27) # header"生成于"优先 def test_ai_summary_lines(self) -> None: r = parse_finance_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") assert r.ai_summary is not None assert len(r.ai_summary.splitlines()) >= 5 assert "碳达峰" in r.ai_summary def test_sections_and_ranks(self) -> None: r = parse_finance_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") sections = {e.section for e in r.events} assert sections == {"xwlb", "news", "cninfo"} assert sum(1 for e in r.events if e.section == "xwlb") == 16 assert sum(1 for e in r.events if e.section == "news") == 20 assert sum(1 for e in r.events if e.section == "cninfo") == 20 def test_event_fields(self) -> None: r = parse_finance_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") xwlb = next(e for e in r.events if e.section == "xwlb") assert xwlb.importance == 4 assert xwlb.event_type == "新闻联播" assert xwlb.sentiment == "neutral" assert "张国清" in xwlb.title news = next(e for e in r.events if e.section == "news" and e.source) assert news.source # 新闻板块带来源 def test_stats_keys(self) -> None: r = parse_finance_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") for key in ("pipeline", "sources", "sentiment", "importance", "event_types"): assert key in r.stats, f"缺少 stats.{key}" assert r.stats["pipeline"]["M1 原始文章"] == 758 class TestIntlParse: def test_metadata(self) -> None: r = parse_intl_report(INTL_HTML, "intl_news_daily_20260711_070304.html") assert r.report_date == date(2026, 7, 11) assert r.report_type == "intl" assert r.generated_at == datetime(2026, 7, 11, 7, 3, 30) def test_events_and_source_from_small(self) -> None: r = parse_intl_report(INTL_HTML, "intl_news_daily_20260711_070304.html") assert len(r.events) == 19 first = r.events[0] assert first.section == "intl" assert first.source == "investinglive.com" # 从摘要 [来源] 提取 assert first.url.startswith("https://investinglive.com/") assert first.importance == 4 assert first.event_type == "地缘政治" # 摘要中不应残留 [来源] 标记 assert first.summary is not None and "[investinglive.com]" not in first.summary def test_stats_keys(self) -> None: r = parse_intl_report(INTL_HTML, "intl_news_daily_20260711_070304.html") for key in ("pipeline", "sentiment", "importance", "event_types", "source_dist"): assert key in r.stats assert r.stats["source_dist"][0]["来源"] == "ForexLive" class TestParseReportDispatch: def test_dispatch_finance(self) -> None: r = parse_report(FINANCE_HTML, "finance_news_daily_20260710_0720.html") assert r.report_type == "finance" def test_dispatch_intl(self) -> None: r = parse_report(INTL_HTML, "intl_news_daily_20260711_070304.html") assert r.report_type == "intl" def test_bad_filename_raises(self) -> None: with pytest.raises(ReportParseError): parse_report("", "not_a_report.html")