124 lines
3.7 KiB
Python
124 lines
3.7 KiB
Python
"""测试早报四模块编排:gather + insights 串行 + sentinel 拼装 + 失败隔离"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import patch, AsyncMock
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
from src.main import _run_morning_push
|
|
|
|
|
|
def _insights_meta() -> dict:
|
|
return {
|
|
"title": "📰 AI Daily 每日精选 | 2026-05-22",
|
|
"lead": "lead text",
|
|
"highlights": ["a"],
|
|
"profile": "morning",
|
|
"date": "2026-05-22",
|
|
"excerpt": "",
|
|
"seotitle": "",
|
|
"seodescription": "",
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_assembles_all_four_sections(sample_config):
|
|
sample_config["filter"]["push_context_days"] = 5
|
|
|
|
sent = {}
|
|
|
|
async def fake_send(content, push_cfg, title=None, metadata=None):
|
|
sent["content"] = content
|
|
sent["metadata"] = metadata
|
|
|
|
saved = {}
|
|
|
|
def fake_save(
|
|
filepath,
|
|
content,
|
|
source_count,
|
|
total_entries,
|
|
profile="default",
|
|
metadata=None,
|
|
):
|
|
saved["profile"] = profile
|
|
saved["content"] = content
|
|
saved["metadata"] = metadata
|
|
|
|
with patch(
|
|
"src.main.run_rss_section", new=AsyncMock(return_value=("R", None, None))
|
|
), patch(
|
|
"src.main.run_github_section", new=AsyncMock(return_value=("G", None))
|
|
), patch(
|
|
"src.main.run_hackernews_section", new=AsyncMock(return_value=("H", None))
|
|
), patch(
|
|
"src.main.run_insights_section",
|
|
new=AsyncMock(return_value=("I", _insights_meta(), None)),
|
|
), patch(
|
|
"src.main.send_to_platforms", new=AsyncMock(side_effect=fake_send)
|
|
), patch(
|
|
"src.main.save_push_file", side_effect=fake_save
|
|
):
|
|
await _run_morning_push(sample_config)
|
|
|
|
assert "SECTION:rss" in sent["content"]
|
|
assert "SECTION:github" in sent["content"]
|
|
assert "SECTION:hackernews" in sent["content"]
|
|
assert "SECTION:insights" in sent["content"]
|
|
assert sent["metadata"]["profile"] == "morning"
|
|
assert saved["profile"] == "morning"
|
|
assert saved["metadata"]["lead"] == "lead text"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_rss_failure_raises_to_caller(sample_config):
|
|
sample_config["filter"]["push_context_days"] = 5
|
|
|
|
with patch(
|
|
"src.main.run_rss_section",
|
|
new=AsyncMock(return_value=("", None, "compose_digest 失败")),
|
|
), patch(
|
|
"src.main.run_github_section", new=AsyncMock(return_value=("G", None))
|
|
), patch(
|
|
"src.main.run_hackernews_section", new=AsyncMock(return_value=("H", None))
|
|
), patch(
|
|
"src.main.notify_llm_errors", new=AsyncMock()
|
|
):
|
|
with pytest.raises(RuntimeError):
|
|
await _run_morning_push(sample_config)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_section_failure_degrades_to_omission(sample_config):
|
|
sample_config["filter"]["push_context_days"] = 5
|
|
|
|
sent = {}
|
|
|
|
async def fake_send(content, push_cfg, title=None, metadata=None):
|
|
sent["content"] = content
|
|
|
|
with patch(
|
|
"src.main.run_rss_section", new=AsyncMock(return_value=("R", None, None))
|
|
), patch(
|
|
"src.main.run_github_section", new=AsyncMock(return_value=("", "gh down"))
|
|
), patch(
|
|
"src.main.run_hackernews_section", new=AsyncMock(return_value=("H", None))
|
|
), patch(
|
|
"src.main.run_insights_section",
|
|
new=AsyncMock(return_value=("I", _insights_meta(), None)),
|
|
), patch(
|
|
"src.main.notify_llm_errors", new=AsyncMock()
|
|
), patch(
|
|
"src.main.send_to_platforms", new=AsyncMock(side_effect=fake_send)
|
|
), patch(
|
|
"src.main.save_push_file"
|
|
):
|
|
await _run_morning_push(sample_config)
|
|
|
|
assert "SECTION:rss" in sent["content"]
|
|
assert "SECTION:github" not in sent["content"]
|
|
assert "SECTION:hackernews" in sent["content"]
|