- 新增常驻调度器 daily/scheduler.py + run-scheduler.ps1(定时生成/推送) - 新增 daily/bridge_manager.py:Windows 兼容的 Cursor SDK 桥接 - 新增 skills/daily-featured-pick 首推 Skill 与叙事轴/去重逻辑 - 新闻抓取窗口、GitHub 搜索、企微 delta 模式等多项改进 - 补充设计文档与 superpowers 计划/规范 - 新增对应测试(scheduler、featured_pick、github_search、news_fetch_window 等) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
157 lines
6.0 KiB
Python
157 lines
6.0 KiB
Python
"""Tests for news time window filtering."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import unittest
|
|
from datetime import datetime, timezone, timedelta
|
|
from unittest.mock import patch
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from daily.news.fetch import _cutoff_datetime, _parse_datetime, _within_window
|
|
|
|
|
|
class NewsWindowTests(unittest.TestCase):
|
|
def test_cutoff_floor_today_excludes_yesterday_even_within_24h(self):
|
|
tz = ZoneInfo("Asia/Shanghai")
|
|
# 2026-07-09 09:00 CST = 2026-07-09 01:00 UTC
|
|
fixed = datetime(2026, 7, 9, 1, 0, tzinfo=timezone.utc)
|
|
with patch("daily.news.fetch._now_utc", return_value=fixed):
|
|
with patch.dict(os.environ, {"DAILY_AI_NEWS_HOURS": "24"}, clear=False):
|
|
cutoff = _cutoff_datetime(floor_today=True)
|
|
start_today_cst = datetime(2026, 7, 9, 0, 0, tzinfo=tz).astimezone(timezone.utc)
|
|
self.assertEqual(cutoff, start_today_cst)
|
|
yesterday = datetime(2026, 7, 8, 20, 0, tzinfo=tz).astimezone(timezone.utc)
|
|
self.assertFalse(_within_window({"published": yesterday.isoformat()}, cutoff))
|
|
|
|
def test_cutoff_rolling_only_includes_last_24h(self):
|
|
fixed = datetime(2026, 7, 9, 12, 0, tzinfo=timezone.utc)
|
|
with patch("daily.news.fetch._now_utc", return_value=fixed):
|
|
with patch.dict(os.environ, {"DAILY_AI_NEWS_HOURS": "24"}, clear=False):
|
|
cutoff = _cutoff_datetime(floor_today=False)
|
|
self.assertEqual(cutoff, fixed - timedelta(hours=24))
|
|
|
|
def test_within_window_rejects_missing_datetime(self):
|
|
cutoff = datetime(2026, 7, 9, 0, 0, tzinfo=timezone.utc)
|
|
self.assertFalse(_within_window({"title": "x", "link": "https://a.com"}, cutoff))
|
|
|
|
def test_parse_date_only_uses_local_noon(self):
|
|
with patch.dict(os.environ, {"DAILY_AI_NEWS_TZ": "Asia/Shanghai"}, clear=False):
|
|
dt = _parse_datetime("2026-07-09")
|
|
self.assertIsNotNone(dt)
|
|
assert dt is not None
|
|
local = dt.astimezone(ZoneInfo("Asia/Shanghai"))
|
|
self.assertEqual(local.hour, 12)
|
|
|
|
|
|
class NewsFormatTests(unittest.TestCase):
|
|
def test_ai_news_lines_use_desc_as_link_text(self):
|
|
from daily.format_wecom import _ai_news_lines
|
|
|
|
lines = _ai_news_lines(
|
|
[
|
|
{
|
|
"title": "English Title",
|
|
"link": "https://example.com/a",
|
|
"source_name": "Src",
|
|
"published_fmt": "07-11",
|
|
"desc_short": "中文摘要一句",
|
|
}
|
|
]
|
|
)
|
|
self.assertEqual(len(lines), 1)
|
|
self.assertIn("[中文摘要一句](https://example.com/a)", lines[0])
|
|
self.assertNotIn("English Title", lines[0])
|
|
self.assertNotIn("> ", lines[0])
|
|
|
|
def test_ai_news_lines_fallback_to_title(self):
|
|
from daily.format_wecom import _ai_news_lines
|
|
|
|
lines = _ai_news_lines(
|
|
[
|
|
{
|
|
"title": "仅标题",
|
|
"link": "https://example.com/b",
|
|
"source_name": "Src",
|
|
"published_fmt": "",
|
|
"desc_short": "",
|
|
}
|
|
]
|
|
)
|
|
self.assertIn("[仅标题](https://example.com/b)", lines[0])
|
|
|
|
|
|
class NewsSummaryTests(unittest.TestCase):
|
|
def test_brief_news_summary_no_ellipsis(self):
|
|
from daily.news.fetch import brief_news_summary
|
|
|
|
text = (
|
|
"Meta told Dylan Byers, of Puck News, that the company removed "
|
|
"the controversial AI feature after user backlash on Instagram."
|
|
)
|
|
out = brief_news_summary(text, limit=72)
|
|
self.assertNotIn("...", out)
|
|
self.assertLessEqual(len(out), 72)
|
|
self.assertTrue(out.startswith("Meta told"))
|
|
|
|
def test_brief_news_summary_filters_junk(self):
|
|
from daily.news.fetch import brief_news_summary
|
|
|
|
self.assertEqual(brief_news_summary("点击查看原文>"), "")
|
|
self.assertEqual(brief_news_summary("Article URL: https://example.com"), "")
|
|
|
|
def test_sync_wecom_news_rows_after_localize(self):
|
|
from daily.news.fetch import _to_wecom_news_row, sync_wecom_news_rows
|
|
|
|
row = _to_wecom_news_row(
|
|
{
|
|
"title": "t",
|
|
"link": "https://a.com/x",
|
|
"source_name": "s",
|
|
"published_fmt": "07-11",
|
|
"summary": "Short english stub that was truncated early...",
|
|
}
|
|
)
|
|
flat = [
|
|
{
|
|
"link": "https://a.com/x",
|
|
"summary": "苹果指控 OpenAI 窃取硬件商业机密,诉讼称 misconduct 涉及多名前员工。",
|
|
}
|
|
]
|
|
sync_wecom_news_rows([row], flat)
|
|
self.assertNotIn("...", row["desc_short"])
|
|
self.assertIn("苹果", row["desc_short"])
|
|
|
|
|
|
def test_finalize_wecom_news_forces_chinese(self):
|
|
from daily.news.fetch import finalize_wecom_news_items
|
|
|
|
items = [
|
|
{
|
|
"link": "https://a.com/1",
|
|
"desc_short": "Meta removed the feature after backlash.",
|
|
"summary_plain": "Meta removed the feature after backlash.",
|
|
}
|
|
]
|
|
with patch(
|
|
"daily.localize.localize_brief_descriptions",
|
|
return_value={"wecom-news:https://a.com/1": "Meta 在舆论压力下移除了该功能"},
|
|
):
|
|
finalize_wecom_news_items(items, force_chinese=True)
|
|
self.assertIn("Meta", items[0]["desc_short"])
|
|
self.assertNotIn("backlash", items[0]["desc_short"])
|
|
|
|
|
|
class NewsPickTests(unittest.TestCase):
|
|
def test_pick_and_backfill_to_limit(self):
|
|
from daily.news.fetch import _fill_picked_to_limit, _pick_news_items
|
|
|
|
flat = [
|
|
{"link": f"https://a.com/{i}", "title": f"t{i}", "category_id": "media", "summary": "s"}
|
|
for i in range(12)
|
|
]
|
|
picked = _pick_news_items(flat, 10, ("media",))
|
|
self.assertEqual(len(picked), 10)
|
|
picked = _fill_picked_to_limit(picked[:3], [flat], 10)
|
|
self.assertEqual(len(picked), 10)
|