返回 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_youtube_top_comments_passthrough_with_field_mapping(self):
49 """YT comments from enrich_with_comments use likes/text; normalize must
50 carry them into metadata as the Reddit-compatible {score, excerpt} shape."""
51 items = [
52 {
53 "video_id": "vid-1",
54 "title": "How to deploy",
55 "url": "https://youtube.com/watch?v=vid-1",
56 "channel_name": "Example",
57 "date": "2026-03-01",
58 "engagement": {"views": 10000, "likes": 500, "comments": 30},
59 "top_comments": [
60 {"author": "Alice", "text": "Best tutorial ever", "likes": 120, "date": "2026-03-02"},
61 {"author": "Bob", "text": "Helped me ship", "likes": 45, "date": "2026-03-03"},
62 {"author": "Carol", "text": "Solid walkthrough", "likes": 7, "date": "2026-03-04"},
63 ],
64 }
65 ]
66 normalized = normalize.normalize_source_items(
67 "youtube", items, "2026-02-15", "2026-03-17",
68 )
69 self.assertEqual(1, len(normalized))
70 top = normalized[0].metadata.get("top_comments")
71 self.assertIsNotNone(top)
72 self.assertEqual(3, len(top))
73 # First comment: likes->score, text->excerpt
74 self.assertEqual(120, top[0]["score"])
75 self.assertEqual("Best tutorial ever", top[0]["excerpt"])
76 self.assertEqual("Alice", top[0]["author"])
77 self.assertEqual("2026-03-02", top[0]["date"])
78 # Preserves ordering from input (already sorted desc upstream)
79 self.assertEqual(45, top[1]["score"])
80 self.assertEqual(7, top[2]["score"])
81
82 def test_instagram_comment_like_count_maps_to_score(self):
83 """U2: IG comments use comment_like_count as the vote; normalize must
84 carry it into the shared `score` field so it participates in ranking."""
85 items = [
86 {
87 "video_id": "ig-1",
88 "text": "reel caption",
89 "url": "https://www.instagram.com/reel/ABC/",
90 "author_name": "example",
91 "date": "2026-03-01",
92 "engagement": {"views": 10000, "likes": 500, "comments": 30},
93 "top_comments": [
94 {"author": "alice", "text": "gold take", "comment_like_count": 120, "date": "2026-03-02"},
95 {"author": "bob", "text": "mid", "comment_like_count": 5, "date": "2026-03-03"},
96 ],
97 }
98 ]
99 normalized = normalize.normalize_source_items(
100 "instagram", items, "2026-02-15", "2026-03-17",
101 )
102 self.assertEqual(1, len(normalized))
103 top = normalized[0].metadata.get("top_comments")
104 self.assertIsNotNone(top)
105 self.assertEqual(120, top[0]["score"])
106 self.assertEqual("gold take", top[0]["excerpt"])
107 self.assertEqual("alice", top[0]["author"])
108
109 def test_youtube_top_comments_empty_list_passes_through_cleanly(self):
110 items = [
111 {
112 "video_id": "vid-2",
113 "title": "Short clip",
114 "url": "https://youtube.com/watch?v=vid-2",
115 "channel_name": "Example",
116 "date": "2026-03-01",
117 "engagement": {"views": 50, "likes": 2},
118 "top_comments": [],
119 }
120 ]
121 normalized = normalize.normalize_source_items(
122 "youtube", items, "2026-02-15", "2026-03-17",
123 )
124 self.assertEqual(1, len(normalized))
125 # Empty list is fine; metadata may have empty top_comments or omit it.
126 top = normalized[0].metadata.get("top_comments", [])
127 self.assertEqual([], top)
128
129 def test_youtube_without_top_comments_key_does_not_crash(self):
130 items = [
131 {
132 "video_id": "vid-3",
133 "title": "No comments fetched",
134 "url": "https://youtube.com/watch?v=vid-3",
135 "channel_name": "Example",
136 "date": "2026-03-01",
137 "engagement": {"views": 100, "likes": 5},
138 }
139 ]
140 normalized = normalize.normalize_source_items(
141 "youtube", items, "2026-02-15", "2026-03-17",
142 )
143 self.assertEqual(1, len(normalized))
144 self.assertEqual([], normalized[0].metadata.get("top_comments", []))
145
146 def test_youtube_top_comments_feed_top_comment_score_signal(self):
147 """Integration: after normalize, signals._top_comment_score should
148 return log1p(first comment score) for YT, proving the full chain."""
149 from lib import signals
150 import math
151 items = [
152 {
153 "video_id": "vid-4",
154 "title": "Viral comment thread",
155 "url": "https://youtube.com/watch?v=vid-4",
156 "channel_name": "Example",
157 "date": "2026-03-01",
158 "engagement": {"views": 1000, "likes": 50, "comments": 10},
159 "top_comments": [
160 {"author": "A", "text": "Legendary", "likes": 9999, "date": "2026-03-02"},
161 ],
162 }
163 ]
164 normalized = normalize.normalize_source_items(
165 "youtube", items, "2026-02-15", "2026-03-17",
166 )
167 self.assertAlmostEqual(math.log1p(9999), signals._top_comment_score(normalized[0]), places=4)
168
169 def test_tiktok_top_comments_passthrough_with_digg_count_mapping(self):
170 """TikTok comments from enrich_with_comments use digg_count/text;
171 normalize must map to the shared {score, excerpt} shape."""
172 items = [
173 {
174 "id": "tt-1",
175 "text": "POV: shipping on Friday",
176 "url": "https://www.tiktok.com/@u/video/tt-1",
177 "author_name": "u",
178 "date": "2026-03-01",
179 "engagement": {"views": 50000, "likes": 2000, "comments": 300},
180 "top_comments": [
181 {"author": "Alice", "text": "dead", "digg_count": 1200, "date": "2026-03-02"},
182 {"author": "Bob", "text": "so real", "digg_count": 400, "date": "2026-03-03"},
183 ],
184 }
185 ]
186 normalized = normalize.normalize_source_items(
187 "tiktok", items, "2026-02-15", "2026-03-17",
188 )
189 self.assertEqual(1, len(normalized))
190 top = normalized[0].metadata.get("top_comments")
191 self.assertEqual(2, len(top))
192 self.assertEqual(1200, top[0]["score"])
193 self.assertEqual("dead", top[0]["excerpt"])
194 self.assertEqual("Alice", top[0]["author"])
195 self.assertEqual(400, top[1]["score"])
196
197 def test_tiktok_without_top_comments_does_not_crash(self):
198 items = [
199 {
200 "id": "tt-2",
201 "text": "plain clip",
202 "url": "https://www.tiktok.com/@u/video/tt-2",
203 "author_name": "u",
204 "date": "2026-03-01",
205 "engagement": {"views": 1000, "likes": 20},
206 }
207 ]
208 normalized = normalize.normalize_source_items(
209 "tiktok", items, "2026-02-15", "2026-03-17",
210 )
211 self.assertEqual([], normalized[0].metadata.get("top_comments", []))
212
213 def test_tiktok_top_comments_feed_top_comment_score_signal(self):
214 from lib import signals
215 import math
216 items = [
217 {
218 "id": "tt-3",
219 "text": "viral",
220 "url": "https://www.tiktok.com/@u/video/tt-3",
221 "author_name": "u",
222 "date": "2026-03-01",
223 "engagement": {"views": 100000, "likes": 5000, "comments": 500},
224 "top_comments": [
225 {"author": "A", "text": "this aged well", "digg_count": 50000, "date": "2026-03-02"},
226 ],
227 }
228 ]
229 normalized = normalize.normalize_source_items(
230 "tiktok", items, "2026-02-15", "2026-03-17",
231 )
232 self.assertAlmostEqual(math.log1p(50000), signals._top_comment_score(normalized[0]), places=4)
233
234 def test_grounding_requires_a_usable_date(self):
235 items = [
236 {
237 "id": "g-1",
238 "title": "Undated result",
239 "url": "https://example.com/undated",
240 "snippet": "No date attached.",
241 }
242 ]
243 normalized = normalize.normalize_source_items(
244 "grounding",
245 items,
246 "2026-02-15",
247 "2026-03-17",
248 )
249 self.assertEqual([], normalized)
250
251 if __name__ == "__main__":
252 unittest.main()
253
253 lines PYTHON