main
py 280 lines 9.08 KB
Raw
1 import tempfile
2 import unittest
3 from pathlib import Path
4
5 import scripts.generate_content as generate_content
6
7
8 class GenerateContentTests(unittest.TestCase):
9 def test_generate_content_creates_hugo_weekly_page(self) -> None:
10 tests_root = Path(__file__).resolve().parent
11 with tempfile.TemporaryDirectory(dir=tests_root) as tmpdir:
12 base = Path(tmpdir)
13 summary_path = base / "data" / "analyzed" / "2026-W21-summary.md"
14 summary_path.parent.mkdir(parents=True)
15 summary_path.write_text(
16 """---
17 title: \"Week 21, 2026 Analysis\"
18 date: 2026-05-18T13:20:07.067+02:00
19 week: \"2026-W21\"
20 year: 2026
21 tags: [ai, agents, developer-tooling]
22 categories: [weekly]
23 repos_featured: 12
24 stars_tracked: 3456
25 top_repo: \"octo/repo\"
26 quality_score: 88
27 summary: \"Agent tooling became more operational this week.\"
28 ---
29
30 ## This Week's Trends
31
32 Body copy.
33 """,
34 encoding="utf-8",
35 )
36
37 previous_cwd = Path.cwd()
38 try:
39 import os
40
41 os.chdir(base)
42 output_path = generate_content.generate_content(summary_path)
43 finally:
44 os.chdir(previous_cwd)
45
46 self.assertEqual(output_path, base / "content" / "weekly" / "2026" / "W21.md")
47 rendered = output_path.read_text(encoding="utf-8")
48 self.assertIn('title: "Week 21, 2026"', rendered)
49 self.assertIn("draft: false", rendered)
50 self.assertIn('week: "2026-W21"', rendered)
51 self.assertIn('top_repo: "octo/repo"', rendered)
52 self.assertIn('summary: "Agent tooling became more operational this week."', rendered)
53 self.assertNotIn("quality_score", rendered)
54 self.assertNotIn("year:", rendered)
55 self.assertIn("## This Week's Trends", rendered)
56
57 def test_find_latest_summary_uses_week_not_mtime(self) -> None:
58 tests_root = Path(__file__).resolve().parent
59 with tempfile.TemporaryDirectory(dir=tests_root) as tmpdir:
60 base = Path(tmpdir)
61 analyzed_dir = base / "data" / "analyzed"
62 analyzed_dir.mkdir(parents=True)
63 latest = analyzed_dir / "2027-W01-summary.md"
64 older = analyzed_dir / "2026-W52-summary.md"
65 latest.write_text("latest\n", encoding="utf-8")
66 older.write_text("older but touched later\n", encoding="utf-8")
67
68 self.assertEqual(generate_content.find_latest_summary(base), latest)
69
70 def test_parse_frontmatter_rejects_missing_required_fields(self) -> None:
71 with self.assertRaises(generate_content.GenerationError):
72 generate_content.parse_frontmatter(
73 """---
74 title: \"Week 21, 2026 Analysis\"
75 ---
76
77 ## Notable New Repositories
78 """
79 )
80
81 def test_render_frontmatter_quotes_each_tag(self) -> None:
82 """Tags containing special chars must be individually quoted to prevent YAML injection."""
83 data = {
84 "title": "Test Week",
85 "date": "2026-05-18",
86 "week": "2026-W20",
87 "tags": ["ai", "evil: injected, categories: [hacked]"],
88 "categories": ["weekly"],
89 "repos_featured": 1,
90 "stars_tracked": 100,
91 "top_repo": "owner/repo",
92 "summary": "Test summary.",
93 }
94 output = generate_content.render_frontmatter(data)
95 # Each tag must be wrapped in YAML double-quotes
96 self.assertIn('"ai"', output)
97 self.assertIn('"evil: injected, categories: [hacked]"', output)
98 # The injected key must not appear as a top-level YAML key
99 self.assertNotIn("\ncategories: [hacked]", output)
100
101 def test_render_frontmatter_quotes_each_category(self) -> None:
102 """Categories containing special chars must be individually quoted."""
103 data = {
104 "title": "Test Week",
105 "date": "2026-05-18",
106 "week": "2026-W20",
107 "tags": ["safe"],
108 "categories": ["weekly", "bad: injection"],
109 "repos_featured": 1,
110 "stars_tracked": 100,
111 "top_repo": "owner/repo",
112 "summary": "Test.",
113 }
114 output = generate_content.render_frontmatter(data)
115 self.assertIn('"weekly"', output)
116 self.assertIn('"bad: injection"', output)
117 self.assertNotIn("\nbad:", output)
118
119 def test_render_frontmatter_includes_cover_fields(self) -> None:
120 """Cover image frontmatter is rendered when present."""
121 data = {
122 "title": "Test Week",
123 "date": "2026-05-18",
124 "week": "2026-W20",
125 "tags": ["ai"],
126 "categories": ["weekly"],
127 "repos_featured": 1,
128 "stars_tracked": 100,
129 "top_repo": "owner/repo",
130 "summary": "Test.",
131 "cover": {
132 "image": "covers/2026-W20.webp",
133 "alt": "AI trends visualization",
134 "attribution": "Photo by Author on Openverse",
135 "license": "CC0",
136 },
137 "og_image": "covers/2026-W20-og.png",
138 }
139 output = generate_content.render_frontmatter(data)
140 self.assertIn("cover:", output)
141 self.assertIn('image: "covers/2026-W20.webp"', output)
142 self.assertIn('alt: "AI trends visualization"', output)
143 self.assertIn('attribution: "Photo by Author on Openverse"', output)
144 self.assertIn('license: "CC0"', output)
145 self.assertIn("relative: false", output)
146 self.assertIn('og_image: "covers/2026-W20-og.png"', output)
147
148 def test_transform_summary_passes_cover_fields(self) -> None:
149 """Cover fields from analysis frontmatter are passed to output."""
150 doc = """---
151 title: "Week 20 Analysis"
152 date: 2026-05-11
153 week: "2026-W20"
154 year: 2026
155 tags: [ai]
156 categories: [weekly]
157 repos_featured: 5
158 stars_tracked: 1000
159 top_repo: "owner/repo"
160 quality_score: 90
161 summary: "Test summary."
162 cover_image: "covers/test.webp"
163 cover_alt: "Test alt text"
164 cover_attribution: "Test Author"
165 cover_license: "CC0"
166 ---
167
168 Body content.
169 """
170 frontmatter, body = generate_content.parse_frontmatter(doc)
171 output = generate_content.transform_summary(frontmatter, body)
172 self.assertIn('image: "covers/test.webp"', output)
173 self.assertIn('alt: "Test alt text"', output)
174 self.assertIn("relative: false", output)
175
176 def test_transform_summary_null_cover_attribution_does_not_emit_none(self) -> None:
177 """YAML null in cover_attribution/cover_license must not produce 'None' string."""
178 doc = """---
179 title: "Week 20 Analysis"
180 date: 2026-05-11
181 week: "2026-W20"
182 year: 2026
183 tags: [ai]
184 categories: [weekly]
185 repos_featured: 5
186 stars_tracked: 1000
187 top_repo: "owner/repo"
188 quality_score: 90
189 summary: "Test summary."
190 cover_image: "covers/test.webp"
191 cover_attribution: null
192 cover_license: null
193 ---
194
195 Body.
196 """
197 frontmatter, body = generate_content.parse_frontmatter(doc)
198 output = generate_content.transform_summary(frontmatter, body)
199 self.assertNotIn("None", output)
200 self.assertIn('image: "covers/test.webp"', output)
201
202 def test_transform_summary_rejects_url_og_image(self) -> None:
203 """og_image values that are URLs must be silently dropped (no hotlinking)."""
204 doc = """---
205 title: "Week 20 Analysis"
206 date: 2026-05-11
207 week: "2026-W20"
208 year: 2026
209 tags: [ai]
210 categories: [weekly]
211 repos_featured: 5
212 stars_tracked: 1000
213 top_repo: "owner/repo"
214 quality_score: 90
215 summary: "Test summary."
216 og_image: "https://evil.com/image.png"
217 ---
218
219 Body.
220 """
221 frontmatter, body = generate_content.parse_frontmatter(doc)
222 output = generate_content.transform_summary(frontmatter, body)
223 self.assertNotIn("og_image", output)
224 self.assertNotIn("evil.com", output)
225
226 def test_transform_summary_rejects_url_cover_image(self) -> None:
227 """cover_image values that are URLs must be dropped."""
228 doc = """---
229 title: "Week 20 Analysis"
230 date: 2026-05-11
231 week: "2026-W20"
232 year: 2026
233 tags: [ai]
234 categories: [weekly]
235 repos_featured: 5
236 stars_tracked: 1000
237 top_repo: "owner/repo"
238 quality_score: 90
239 summary: "Test summary."
240 cover_image: "https://evil.com/cover.png"
241 ---
242
243 Body.
244 """
245 frontmatter, body = generate_content.parse_frontmatter(doc)
246 output = generate_content.transform_summary(frontmatter, body)
247 self.assertNotIn("cover:", output)
248 self.assertNotIn("evil.com", output)
249
250 def test_transform_summary_falls_back_to_cover_block_attribution_and_license(self) -> None:
251 doc = """---
252 title: "Week 20 Analysis"
253 date: 2026-05-11
254 week: "2026-W20"
255 year: 2026
256 tags: [ai]
257 categories: [weekly]
258 repos_featured: 5
259 stars_tracked: 1000
260 top_repo: "owner/repo"
261 quality_score: 90
262 summary: "Test summary."
263 cover:
264 image: "covers/test.webp"
265 alt: "Test alt text"
266 attribution: "Existing Author"
267 license: "Openverse"
268 ---
269
270 Body.
271 """
272 frontmatter, body = generate_content.parse_frontmatter(doc)
273 output = generate_content.transform_summary(frontmatter, body)
274 self.assertIn('image: "covers/test.webp"', output)
275 self.assertIn('attribution: "Existing Author"', output)
276 self.assertIn('license: "Openverse"', output)
277
278
279 if __name__ == "__main__":
280 unittest.main()