# tests/test_generate_golden.py """黄金文件/确定性回归: 冻结时间、mock 网络与 LLM, 验证 generate_report 在同一输入下产出逐字节一致(拆分 _collect/_select/_render 不得改变行为)。""" from __future__ import annotations import unittest from datetime import datetime, timedelta, timezone from pathlib import Path from unittest import mock import daily.generate as g FIXED_NOW = datetime(2026, 7, 18, 8, 50, tzinfo=timezone(timedelta(hours=8))) def _feed() -> dict: return { "updatedAt": "2026-07-18T08:00:00", "topTrending": [ {"id": "a", "source": "src", "title": "alpha", "description": "desc a", "installs": 100, "link": "https://x/a"}, {"id": "b", "source": "src", "title": "beta", "description": "desc b", "installs": 90, "link": "https://x/b"}, ], "topHot": [ {"id": "c", "source": "src", "title": "gamma", "description": "desc c", "installs": 80, "link": "https://x/c"}, ], } def _patches(tmp: Path): """集中 mock 所有外部边界: 网络/文件/时间/LLM。返回 patcher 列表。""" return [ mock.patch.object(g, "_now_cst", return_value=FIXED_NOW), mock.patch.object(g, "load_feed", return_value=_feed()), mock.patch.object(g, "_load_snapshot", return_value=set()), mock.patch.object(g, "_save_snapshot", lambda *a, **k: None), mock.patch.object(g, "load_boards", return_value=( _feed()["topTrending"], _feed()["topHot"])), mock.patch.object(g, "fetch_github_trending", return_value=[]), mock.patch.object(g, "fetch_emerging_repos", return_value=[]), mock.patch.object(g, "fetch_topic_hot_repos", return_value=("topic", [])), mock.patch.object(g, "fetch_ai_news", return_value={ "enabled": False, "categories": [], "flat": [], "stats": {}}), mock.patch.object(g, "fetch_cn_ai_news", return_value={ "enabled": False, "categories": [], "flat": [], "stats": {}}), mock.patch.object(g, "is_research_mode", return_value=False), mock.patch.object(g, "is_agent_mode", return_value=False), mock.patch.object(g, "run_editorial", return_value=None), mock.patch.object(g, "cursor_editor_enabled", return_value=False), mock.patch.object(g, "record_pushed_links", lambda *a, **k: None), mock.patch.object(g, "save_json", lambda *a, **k: None), mock.patch.object(g, "load_recent_shown_keys", return_value={ "skills_trending": set(), "skills_hot": set(), "github_trending": set(), "github_emerging": set(), "github_topic": set()}), mock.patch.object(g, "OUTPUT_DIR", tmp), mock.patch.object(g, "_localize_descriptions_in_place", lambda *a, **k: None), # featured_pick / push_gate 有独立测试;此处冻结以免触网(LLM)与读盘。 mock.patch.object(g, "apply_featured_pick", return_value={ "title": "alpha", "url": "https://x/a", "command": "npx skills add src/alpha", "why": "昨日 star 增速第一"}), mock.patch.object(g, "evaluate_push_gate", return_value=mock.Mock( should_push=False, silent=True, reasons=["golden-mock"])), ] class GenerateGoldenTests(unittest.TestCase): def _run(self, tmp: Path) -> tuple[str, str]: patches = _patches(tmp) for p in patches: p.start() try: markdown, wecom_md, _md, _we = g.generate_report() return markdown, wecom_md finally: for p in patches: try: p.stop() except Exception: pass def test_deterministic_same_input_same_output(self): import tempfile with tempfile.TemporaryDirectory() as d1, tempfile.TemporaryDirectory() as d2: md1, we1 = self._run(Path(d1)) md2, we2 = self._run(Path(d2)) self.assertEqual(md1, md2, "完整版 markdown 在相同输入下必须逐字节一致") self.assertEqual(we1, we2, "企微 wecom_md 在相同输入下必须逐字节一致") def test_output_contains_core_sections(self): import tempfile with tempfile.TemporaryDirectory() as d: markdown, wecom_md = self._run(Path(d)) self.assertIn("# 早报 · 2026-07-18", markdown) self.assertIn("主题聚类", markdown) self.assertIsInstance(wecom_md, str) self.assertTrue(len(wecom_md) > 0) if __name__ == "__main__": unittest.main()