返回 last30days-skill
test_normalize_v3.py
根目录 / tests / test_normalize_v3.py
1 import unittest
2
3 from lib import normalize
4
5
6 class NormalizeV3Tests(unittest.TestCase):
7 def test_youtube_evergreen_fallback_keeps_older_items_when_recent_pool_is_empty(self):
8 items = [
9 {
10 "video_id": "vid-1",
11 "title": "Deploy to Fly.io tutorial",
12 "url": "https://youtube.com/watch?v=vid-1",
13 "channel_name": "Example",
14 "date": "2026-01-10",
15 "engagement": {"views": 1000, "likes": 50, "comments": 10},
16 }
17 ]
18 normalized = normalize.normalize_source_items(
19 "youtube",
20 items,
21 "2026-02-15",
22 "2026-03-17",
23 freshness_mode="evergreen_ok",
24 )
25 self.assertEqual(1, len(normalized))
26 self.assertEqual("2026-01-10", normalized[0].published_at)
27
28 def test_grounding_still_drops_older_items_in_evergreen_mode(self):
29 items = [
30 {
31 "id": "g-1",
32 "title": "Fly.io guide",
33 "url": "https://example.com/fly-guide",
34 "date": "2026-01-08",
35 "date_confidence": "high",
36 "snippet": "Step-by-step guide.",
37 }
38 ]
39 normalized = normalize.normalize_source_items(
40 "grounding",
41 items,
42 "2026-02-15",
43 "2026-03-17",
44 freshness_mode="evergreen_ok",
45 )
46 self.assertEqual([], normalized)
47
48 def test_arxiv_keeps_adapter_valid_items_older_than_report_window(self):
49 items = [
50 {
51 "id": "http://arxiv.org/abs/2509.00001v1",
52 "title": "Reliable Agent Memory",
53 "url": "https://arxiv.org/abs/2509.00001v1",
54 "summary": "A study of memory systems for coding agents.",
55 "author": "Ada Lovelace",
56 "authors": ["Ada Lovelace"],
57 "date": "2025-09-04",
58 "relevance": 0.9,
59 }
60 ]
61
62 normalized = normalize.normalize_source_items(
63 "arxiv",
64 items,
65 "2026-07-06",
66 "2026-08-05",
67 )
68
69 self.assertEqual(1, len(normalized))
70 self.assertEqual("Reliable Agent Memory", normalized[0].title)
71 self.assertEqual("https://arxiv.org/abs/2509.00001v1", normalized[0].url)
72 self.assertEqual("2025-09-04", normalized[0].published_at)
73
74 def test_youtube_top_comments_passthrough_with_field_mapping(self):
75 """YT comments from enrich_with_comments use likes/text; normalize must
76 carry them into metadata as the Reddit-compatible {score, excerpt} shape."""
77 items = [
78 {
79 "video_id": "vid-1",
80 "title": "How to deploy",
81 "url": "https://youtube.com/watch?v=vid-1",
82 "channel_name": "Example",
83 "date": "2026-03-01",
84 "engagement": {"views": 10000, "likes": 500, "comments": 30},
85 "top_comments": [
86 {"author": "Alice", "text": "Best tutorial ever", "likes": 120, "date": "2026-03-02"},
87 {"author": "Bob", "text": "Helped me ship", "likes": 45, "date": "2026-03-03"},
88 {"author": "Carol", "text": "Solid walkthrough", "likes": 7, "date": "2026-03-04"},
89 ],
90 }
91 ]
92 normalized = normalize.normalize_source_items(
93 "youtube", items, "2026-02-15", "2026-03-17",
94 )
95 self.assertEqual(1, len(normalized))
96 top = normalized[0].metadata.get("top_comments")
97 self.assertIsNotNone(top)
98 self.assertEqual(3, len(top))
99 # First comment: likes->score, text->excerpt
100 self.assertEqual(120, top[0]["score"])
101 self.assertEqual("Best tutorial ever", top[0]["excerpt"])
102 self.assertEqual("Alice", top[0]["author"])
103 self.assertEqual("2026-03-02", top[0]["date"])
104 # Preserves ordering from input (already sorted desc upstream)
105 self.assertEqual(45, top[1]["score"])
106 self.assertEqual(7, top[2]["score"])
107
108 def test_instagram_comment_like_count_maps_to_score(self):
109 """U2: IG comments use comment_like_count as the vote; normalize must
110 carry it into the shared `score` field so it participates in ranking."""
111 items = [
112 {
113 "video_id": "ig-1",
114 "text": "reel caption",
115 "url": "https://www.instagram.com/reel/ABC/",
116 "author_name": "example",
117 "date": "2026-03-01",
118 "engagement": {"views": 10000, "likes": 500, "comments": 30},
119 "top_comments": [
120 {"author": "alice", "text": "gold take", "comment_like_count": 120, "date": "2026-03-02"},
121 {"author": "bob", "text": "mid", "comment_like_count": 5, "date": "2026-03-03"},
122 ],
123 }
124 ]
125 normalized = normalize.normalize_source_items(
126 "instagram", items, "2026-02-15", "2026-03-17",
127 )
128 self.assertEqual(1, len(normalized))
129 top = normalized[0].metadata.get("top_comments")
130 self.assertIsNotNone(top)
131 self.assertEqual(120, top[0]["score"])
132 self.assertEqual("gold take", top[0]["excerpt"])
133 self.assertEqual("alice", top[0]["author"])
134
135 def test_youtube_top_comments_empty_list_passes_through_cleanly(self):
136 items = [
137 {
138 "video_id": "vid-2",
139 "title": "Short clip",
140 "url": "https://youtube.com/watch?v=vid-2",
141 "channel_name": "Example",
142 "date": "2026-03-01",
143 "engagement": {"views": 50, "likes": 2},
144 "top_comments": [],
145 }
146 ]
147 normalized = normalize.normalize_source_items(
148 "youtube", items, "2026-02-15", "2026-03-17",
149 )
150 self.assertEqual(1, len(normalized))
151 # Empty list is fine; metadata may have empty top_comments or omit it.
152 top = normalized[0].metadata.get("top_comments", [])
153 self.assertEqual([], top)
154
155 def test_youtube_without_top_comments_key_does_not_crash(self):
156 items = [
157 {
158 "video_id": "vid-3",
159 "title": "No comments fetched",
160 "url": "https://youtube.com/watch?v=vid-3",
161 "channel_name": "Example",
162 "date": "2026-03-01",
163 "engagement": {"views": 100, "likes": 5},
164 }
165 ]
166 normalized = normalize.normalize_source_items(
167 "youtube", items, "2026-02-15", "2026-03-17",
168 )
169 self.assertEqual(1, len(normalized))
170 self.assertEqual([], normalized[0].metadata.get("top_comments", []))
171
172 def test_youtube_top_comments_feed_top_comment_score_signal(self):
173 """Integration: after normalize, signals._top_comment_score should
174 return log1p(first comment score) for YT, proving the full chain."""
175 from lib import signals
176 import math
177 items = [
178 {
179 "video_id": "vid-4",
180 "title": "Viral comment thread",
181 "url": "https://youtube.com/watch?v=vid-4",
182 "channel_name": "Example",
183 "date": "2026-03-01",
184 "engagement": {"views": 1000, "likes": 50, "comments": 10},
185 "top_comments": [
186 {"author": "A", "text": "Legendary", "likes": 9999, "date": "2026-03-02"},
187 ],
188 }
189 ]
190 normalized = normalize.normalize_source_items(
191 "youtube", items, "2026-02-15", "2026-03-17",
192 )
193 self.assertAlmostEqual(math.log1p(9999), signals._top_comment_score(normalized[0]), places=4)
194
195 def test_tiktok_top_comments_passthrough_with_digg_count_mapping(self):
196 """TikTok comments from enrich_with_comments use digg_count/text;
197 normalize must map to the shared {score, excerpt} shape."""
198 items = [
199 {
200 "id": "tt-1",
201 "text": "POV: shipping on Friday",
202 "url": "https://www.tiktok.com/@u/video/tt-1",
203 "author_name": "u",
204 "date": "2026-03-01",
205 "engagement": {"views": 50000, "likes": 2000, "comments": 300},
206 "top_comments": [
207 {"author": "Alice", "text": "dead", "digg_count": 1200, "date": "2026-03-02"},
208 {"author": "Bob", "text": "so real", "digg_count": 400, "date": "2026-03-03"},
209 ],
210 }
211 ]
212 normalized = normalize.normalize_source_items(
213 "tiktok", items, "2026-02-15", "2026-03-17",
214 )
215 self.assertEqual(1, len(normalized))
216 top = normalized[0].metadata.get("top_comments")
217 self.assertEqual(2, len(top))
218 self.assertEqual(1200, top[0]["score"])
219 self.assertEqual("dead", top[0]["excerpt"])
220 self.assertEqual("Alice", top[0]["author"])
221 self.assertEqual(400, top[1]["score"])
222
223 def test_tiktok_without_top_comments_does_not_crash(self):
224 items = [
225 {
226 "id": "tt-2",
227 "text": "plain clip",
228 "url": "https://www.tiktok.com/@u/video/tt-2",
229 "author_name": "u",
230 "date": "2026-03-01",
231 "engagement": {"views": 1000, "likes": 20},
232 }
233 ]
234 normalized = normalize.normalize_source_items(
235 "tiktok", items, "2026-02-15", "2026-03-17",
236 )
237 self.assertEqual([], normalized[0].metadata.get("top_comments", []))
238
239 def test_tiktok_top_comments_feed_top_comment_score_signal(self):
240 from lib import signals
241 import math
242 items = [
243 {
244 "id": "tt-3",
245 "text": "viral",
246 "url": "https://www.tiktok.com/@u/video/tt-3",
247 "author_name": "u",
248 "date": "2026-03-01",
249 "engagement": {"views": 100000, "likes": 5000, "comments": 500},
250 "top_comments": [
251 {"author": "A", "text": "this aged well", "digg_count": 50000, "date": "2026-03-02"},
252 ],
253 }
254 ]
255 normalized = normalize.normalize_source_items(
256 "tiktok", items, "2026-02-15", "2026-03-17",
257 )
258 self.assertAlmostEqual(math.log1p(50000), signals._top_comment_score(normalized[0]), places=4)
259
260 def test_grounding_requires_a_usable_date(self):
261 items = [
262 {
263 "id": "g-1",
264 "title": "Undated result",
265 "url": "https://example.com/undated",
266 "snippet": "No date attached.",
267 }
268 ]
269 normalized = normalize.normalize_source_items(
270 "grounding",
271 items,
272 "2026-02-15",
273 "2026-03-17",
274 )
275 self.assertEqual([], normalized)
276
277 def test_youtube_keeps_older_items_when_date_window_is_empty_even_without_evergreen(self):
278 """#1043: search kept out-of-window videos so transcripts could run; normalize must not drop them."""
279 items = [
280 {
281 "video_id": "vid-old",
282 "title": "Informa TechTarget overview",
283 "url": "https://youtube.com/watch?v=vid-old",
284 "channel_name": "Example",
285 "date": "2025-01-10",
286 "transcript_snippet": "Fetched transcript about Informa TechTarget.",
287 "engagement": {"views": 1000, "likes": 50, "comments": 10},
288 }
289 ]
290 normalized = normalize.normalize_source_items(
291 "youtube",
292 items,
293 "2026-02-15",
294 "2026-03-17",
295 freshness_mode="balanced_recent",
296 )
297 self.assertEqual(1, len(normalized))
298 self.assertEqual("vid-old", normalized[0].item_id)
299 self.assertIn("Fetched transcript", normalized[0].snippet)
300
301 def test_youtube_empty_window_fallback_keeps_only_transcribed_items(self):
302 """#1043 rescue is transcript-backed evidence, not stale metadata-only videos."""
303 items = [
304 {
305 "video_id": "vid-meta",
306 "title": "Old video without captions",
307 "url": "https://youtube.com/watch?v=vid-meta",
308 "channel_name": "Example",
309 "date": "2025-01-10",
310 "engagement": {"views": 1000, "likes": 50, "comments": 10},
311 },
312 {
313 "video_id": "vid-old",
314 "title": "Informa TechTarget overview",
315 "url": "https://youtube.com/watch?v=vid-old",
316 "channel_name": "Example",
317 "date": "2025-01-10",
318 "transcript_snippet": "Fetched transcript about Informa TechTarget.",
319 "engagement": {"views": 1000, "likes": 50, "comments": 10},
320 },
321 ]
322 normalized = normalize.normalize_source_items(
323 "youtube",
324 items,
325 "2026-02-15",
326 "2026-03-17",
327 freshness_mode="balanced_recent",
328 )
329 self.assertEqual(1, len(normalized))
330 self.assertEqual("vid-old", normalized[0].item_id)
331
332 def test_youtube_empty_window_fallback_drops_all_transcript_free_videos(self):
333 items = [
334 {
335 "video_id": "vid-meta",
336 "title": "Old video without captions",
337 "url": "https://youtube.com/watch?v=vid-meta",
338 "channel_name": "Example",
339 "date": "2025-01-10",
340 }
341 ]
342 normalized = normalize.normalize_source_items(
343 "youtube",
344 items,
345 "2026-02-15",
346 "2026-03-17",
347 freshness_mode="strict_recent",
348 )
349 self.assertEqual([], normalized)
350
351
352 if __name__ == "__main__":
353 unittest.main()
354
354 lines PYTHON