Hugoifier

hugoifier / tests / test_enhance.py
Source Blame History 423 lines
3d0f0d4… lmata 1 """Tests for utils.enhance."""
3d0f0d4… lmata 2 import json
3d0f0d4… lmata 3 import os
3d0f0d4… lmata 4 import tempfile
3d0f0d4… lmata 5 import unittest
3d0f0d4… lmata 6 from unittest.mock import patch
3d0f0d4… lmata 7
3d0f0d4… lmata 8 from hugoifier.utils.enhance import (
3d0f0d4… lmata 9 _chunks,
3d0f0d4… lmata 10 _find_baseof,
3d0f0d4… lmata 11 _parse_ai_json,
3d0f0d4… lmata 12 _parse_frontmatter,
3d0f0d4… lmata 13 _read_body,
3d0f0d4… lmata 14 _read_site_context,
3d0f0d4… lmata 15 _seo_og_tags,
3d0f0d4… lmata 16 _update_frontmatter,
3d0f0d4… lmata 17 alt_text,
3d0f0d4… lmata 18 generate,
3d0f0d4… lmata 19 seo,
3d0f0d4… lmata 20 )
3d0f0d4… lmata 21
3d0f0d4… lmata 22
3d0f0d4… lmata 23 class TestReadSiteContext(unittest.TestCase):
3d0f0d4… lmata 24 def test_reads_title_from_hugo_toml(self):
3d0f0d4… lmata 25 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 26 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 27 f.write('title = "My Test Site"\n')
3d0f0d4… lmata 28 ctx = _read_site_context(tmp)
3d0f0d4… lmata 29 self.assertEqual(ctx["title"], "My Test Site")
3d0f0d4… lmata 30
3d0f0d4… lmata 31 def test_reads_title_from_config_toml(self):
3d0f0d4… lmata 32 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 33 with open(os.path.join(tmp, "config.toml"), "w") as f:
3d0f0d4… lmata 34 f.write('title = "Legacy Config"\n')
3d0f0d4… lmata 35 ctx = _read_site_context(tmp)
3d0f0d4… lmata 36 self.assertEqual(ctx["title"], "Legacy Config")
3d0f0d4… lmata 37
3d0f0d4… lmata 38 def test_hugo_toml_takes_precedence_over_config_toml(self):
3d0f0d4… lmata 39 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 40 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 41 f.write('title = "Hugo"\n')
3d0f0d4… lmata 42 with open(os.path.join(tmp, "config.toml"), "w") as f:
3d0f0d4… lmata 43 f.write('title = "Config"\n')
3d0f0d4… lmata 44 ctx = _read_site_context(tmp)
3d0f0d4… lmata 45 self.assertEqual(ctx["title"], "Hugo")
3d0f0d4… lmata 46
3d0f0d4… lmata 47 def test_reads_description(self):
3d0f0d4… lmata 48 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 49 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 50 f.write('title = "Site"\ndescription = "A fine site"\n')
3d0f0d4… lmata 51 ctx = _read_site_context(tmp)
3d0f0d4… lmata 52 self.assertEqual(ctx["description"], "A fine site")
3d0f0d4… lmata 53
3d0f0d4… lmata 54 def test_finds_content_sections(self):
3d0f0d4… lmata 55 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 56 os.makedirs(os.path.join(tmp, "content", "blog"))
3d0f0d4… lmata 57 os.makedirs(os.path.join(tmp, "content", "about"))
3d0f0d4… lmata 58 ctx = _read_site_context(tmp)
3d0f0d4… lmata 59 self.assertIn("blog", ctx["content_sections"])
3d0f0d4… lmata 60 self.assertIn("about", ctx["content_sections"])
3d0f0d4… lmata 61
3d0f0d4… lmata 62 def test_defaults_when_no_config(self):
3d0f0d4… lmata 63 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 64 ctx = _read_site_context(tmp)
3d0f0d4… lmata 65 self.assertEqual(ctx["title"], "My Hugo Site")
3d0f0d4… lmata 66 self.assertEqual(ctx["description"], "")
3d0f0d4… lmata 67 self.assertEqual(ctx["content_sections"], [])
3d0f0d4… lmata 68
3d0f0d4… lmata 69 def test_handles_missing_content_dir(self):
3d0f0d4… lmata 70 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 71 ctx = _read_site_context(tmp)
3d0f0d4… lmata 72 self.assertEqual(ctx["content_sections"], [])
3d0f0d4… lmata 73 self.assertEqual(ctx["sample_content"], "")
3d0f0d4… lmata 74
3d0f0d4… lmata 75 def test_reads_sample_content(self):
3d0f0d4… lmata 76 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 77 content = os.path.join(tmp, "content")
3d0f0d4… lmata 78 os.makedirs(content)
3d0f0d4… lmata 79 with open(os.path.join(content, "page.md"), "w") as f:
3d0f0d4… lmata 80 f.write("---\ntitle: Test\n---\n\nSome content.\n")
3d0f0d4… lmata 81 ctx = _read_site_context(tmp)
3d0f0d4… lmata 82 self.assertIn("Some content.", ctx["sample_content"])
3d0f0d4… lmata 83
3d0f0d4… lmata 84
3d0f0d4… lmata 85 class TestParseFrontmatter(unittest.TestCase):
3d0f0d4… lmata 86 def test_parses_yaml_frontmatter(self):
3d0f0d4… lmata 87 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 88 path = os.path.join(tmp, "page.md")
3d0f0d4… lmata 89 with open(path, "w") as f:
3d0f0d4… lmata 90 f.write("---\ntitle: Hello\ndate: 2024-01-01\ndraft: false\n---\n\nBody.\n")
3d0f0d4… lmata 91 result = _parse_frontmatter(path)
3d0f0d4… lmata 92 self.assertEqual(result["title"], "Hello")
3d0f0d4… lmata 93 self.assertFalse(result["draft"])
3d0f0d4… lmata 94
3d0f0d4… lmata 95 def test_returns_empty_dict_for_missing_frontmatter(self):
3d0f0d4… lmata 96 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 97 path = os.path.join(tmp, "bare.md")
3d0f0d4… lmata 98 with open(path, "w") as f:
3d0f0d4… lmata 99 f.write("Just some text.\n")
3d0f0d4… lmata 100 self.assertEqual(_parse_frontmatter(path), {})
3d0f0d4… lmata 101
3d0f0d4… lmata 102 def test_returns_empty_dict_for_missing_file(self):
3d0f0d4… lmata 103 self.assertEqual(_parse_frontmatter("/nonexistent/path.md"), {})
3d0f0d4… lmata 104
3d0f0d4… lmata 105 def test_returns_empty_dict_for_empty_frontmatter(self):
3d0f0d4… lmata 106 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 107 path = os.path.join(tmp, "empty_fm.md")
3d0f0d4… lmata 108 with open(path, "w") as f:
3d0f0d4… lmata 109 f.write("---\n\n---\nBody.\n")
3d0f0d4… lmata 110 self.assertEqual(_parse_frontmatter(path), {})
3d0f0d4… lmata 111
3d0f0d4… lmata 112
3d0f0d4… lmata 113 class TestUpdateFrontmatter(unittest.TestCase):
3d0f0d4… lmata 114 def test_adds_new_field(self):
3d0f0d4… lmata 115 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 116 path = os.path.join(tmp, "post.md")
3d0f0d4… lmata 117 with open(path, "w") as f:
3d0f0d4… lmata 118 f.write("---\ntitle: Hello\n---\nBody text here.\n")
3d0f0d4… lmata 119 _update_frontmatter(path, {"description": "A great post"})
3d0f0d4… lmata 120 fm = _parse_frontmatter(path)
3d0f0d4… lmata 121 self.assertEqual(fm["description"], "A great post")
3d0f0d4… lmata 122 self.assertEqual(fm["title"], "Hello")
3d0f0d4… lmata 123
3d0f0d4… lmata 124 def test_preserves_body(self):
3d0f0d4… lmata 125 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 126 path = os.path.join(tmp, "post.md")
3d0f0d4… lmata 127 with open(path, "w") as f:
3d0f0d4… lmata 128 f.write("---\ntitle: Hello\n---\nBody text here.\n")
3d0f0d4… lmata 129 _update_frontmatter(path, {"description": "desc"})
3d0f0d4… lmata 130 body = _read_body(path)
3d0f0d4… lmata 131 self.assertIn("Body text here.", body)
3d0f0d4… lmata 132
3d0f0d4… lmata 133 def test_updates_existing_field(self):
3d0f0d4… lmata 134 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 135 path = os.path.join(tmp, "post.md")
3d0f0d4… lmata 136 with open(path, "w") as f:
3d0f0d4… lmata 137 f.write("---\ntitle: Old Title\n---\nBody.\n")
3d0f0d4… lmata 138 _update_frontmatter(path, {"title": "New Title"})
3d0f0d4… lmata 139 fm = _parse_frontmatter(path)
3d0f0d4… lmata 140 self.assertEqual(fm["title"], "New Title")
3d0f0d4… lmata 141
3d0f0d4… lmata 142 def test_noop_without_frontmatter(self):
3d0f0d4… lmata 143 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 144 path = os.path.join(tmp, "bare.md")
3d0f0d4… lmata 145 with open(path, "w") as f:
3d0f0d4… lmata 146 f.write("No frontmatter here.\n")
3d0f0d4… lmata 147 _update_frontmatter(path, {"description": "test"})
3d0f0d4… lmata 148 with open(path, "r") as f:
3d0f0d4… lmata 149 self.assertEqual(f.read(), "No frontmatter here.\n")
3d0f0d4… lmata 150
3d0f0d4… lmata 151
3d0f0d4… lmata 152 class TestReadBody(unittest.TestCase):
3d0f0d4… lmata 153 def test_extracts_body_after_frontmatter(self):
3d0f0d4… lmata 154 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 155 path = os.path.join(tmp, "post.md")
3d0f0d4… lmata 156 with open(path, "w") as f:
3d0f0d4… lmata 157 f.write("---\ntitle: Test\n---\n\nThe body content.\n")
3d0f0d4… lmata 158 body = _read_body(path)
3d0f0d4… lmata 159 self.assertEqual(body, "The body content.")
3d0f0d4… lmata 160
3d0f0d4… lmata 161 def test_returns_full_content_without_frontmatter(self):
3d0f0d4… lmata 162 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 163 path = os.path.join(tmp, "bare.md")
3d0f0d4… lmata 164 with open(path, "w") as f:
3d0f0d4… lmata 165 f.write("Just text, no frontmatter.\n")
3d0f0d4… lmata 166 body = _read_body(path)
3d0f0d4… lmata 167 self.assertIn("Just text", body)
3d0f0d4… lmata 168
3d0f0d4… lmata 169 def test_returns_empty_for_missing_file(self):
3d0f0d4… lmata 170 self.assertEqual(_read_body("/nonexistent/path.md"), "")
3d0f0d4… lmata 171
3d0f0d4… lmata 172
3d0f0d4… lmata 173 class TestFindBaseof(unittest.TestCase):
3d0f0d4… lmata 174 def test_finds_baseof_in_layouts(self):
3d0f0d4… lmata 175 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 176 baseof = os.path.join(tmp, "layouts", "_default", "baseof.html")
3d0f0d4… lmata 177 os.makedirs(os.path.dirname(baseof))
3d0f0d4… lmata 178 with open(baseof, "w") as f:
3d0f0d4… lmata 179 f.write("<html></html>")
3d0f0d4… lmata 180 self.assertEqual(_find_baseof(tmp), baseof)
3d0f0d4… lmata 181
3d0f0d4… lmata 182 def test_finds_baseof_in_themes(self):
3d0f0d4… lmata 183 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 184 baseof = os.path.join(tmp, "themes", "mytheme", "layouts", "_default", "baseof.html")
3d0f0d4… lmata 185 os.makedirs(os.path.dirname(baseof))
3d0f0d4… lmata 186 with open(baseof, "w") as f:
3d0f0d4… lmata 187 f.write("<html></html>")
3d0f0d4… lmata 188 self.assertEqual(_find_baseof(tmp), baseof)
3d0f0d4… lmata 189
3d0f0d4… lmata 190 def test_prefers_layouts_over_themes(self):
3d0f0d4… lmata 191 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 192 layouts_baseof = os.path.join(tmp, "layouts", "_default", "baseof.html")
3d0f0d4… lmata 193 os.makedirs(os.path.dirname(layouts_baseof))
3d0f0d4… lmata 194 with open(layouts_baseof, "w") as f:
3d0f0d4… lmata 195 f.write("<html>layouts</html>")
3d0f0d4… lmata 196 theme_baseof = os.path.join(tmp, "themes", "t", "layouts", "_default", "baseof.html")
3d0f0d4… lmata 197 os.makedirs(os.path.dirname(theme_baseof))
3d0f0d4… lmata 198 with open(theme_baseof, "w") as f:
3d0f0d4… lmata 199 f.write("<html>theme</html>")
3d0f0d4… lmata 200 self.assertEqual(_find_baseof(tmp), layouts_baseof)
3d0f0d4… lmata 201
3d0f0d4… lmata 202 def test_returns_none_when_missing(self):
3d0f0d4… lmata 203 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 204 self.assertIsNone(_find_baseof(tmp))
3d0f0d4… lmata 205
3d0f0d4… lmata 206
3d0f0d4… lmata 207 class TestParseAiJson(unittest.TestCase):
3d0f0d4… lmata 208 def test_parses_valid_json(self):
3d0f0d4… lmata 209 result = _parse_ai_json('{"key": "value"}')
3d0f0d4… lmata 210 self.assertEqual(result, {"key": "value"})
3d0f0d4… lmata 211
3d0f0d4… lmata 212 def test_parses_fenced_json(self):
3d0f0d4… lmata 213 result = _parse_ai_json('```json\n{"key": "value"}\n```')
3d0f0d4… lmata 214 self.assertEqual(result, {"key": "value"})
3d0f0d4… lmata 215
3d0f0d4… lmata 216 def test_parses_json_embedded_in_prose(self):
3d0f0d4… lmata 217 result = _parse_ai_json('Here is the result:\n{"title": "Hello"}\nDone.')
3d0f0d4… lmata 218 self.assertEqual(result, {"title": "Hello"})
3d0f0d4… lmata 219
3d0f0d4… lmata 220 def test_returns_none_for_invalid(self):
3d0f0d4… lmata 221 self.assertIsNone(_parse_ai_json("not json at all"))
3d0f0d4… lmata 222
3d0f0d4… lmata 223 def test_returns_none_for_json_list(self):
3d0f0d4… lmata 224 self.assertIsNone(_parse_ai_json('[1, 2, 3]'))
3d0f0d4… lmata 225
3d0f0d4… lmata 226 def test_returns_none_for_empty_string(self):
3d0f0d4… lmata 227 self.assertIsNone(_parse_ai_json(""))
3d0f0d4… lmata 228
3d0f0d4… lmata 229 def test_parses_fenced_json_without_language(self):
3d0f0d4… lmata 230 result = _parse_ai_json('```\n{"a": 1}\n```')
3d0f0d4… lmata 231 self.assertEqual(result, {"a": 1})
3d0f0d4… lmata 232
3d0f0d4… lmata 233
3d0f0d4… lmata 234 class TestChunks(unittest.TestCase):
3d0f0d4… lmata 235 def test_splits_evenly(self):
3d0f0d4… lmata 236 result = list(_chunks([1, 2, 3, 4], 2))
3d0f0d4… lmata 237 self.assertEqual(result, [[1, 2], [3, 4]])
3d0f0d4… lmata 238
3d0f0d4… lmata 239 def test_handles_remainder(self):
3d0f0d4… lmata 240 result = list(_chunks([1, 2, 3, 4, 5], 2))
3d0f0d4… lmata 241 self.assertEqual(result, [[1, 2], [3, 4], [5]])
3d0f0d4… lmata 242
3d0f0d4… lmata 243 def test_empty_list(self):
3d0f0d4… lmata 244 result = list(_chunks([], 3))
3d0f0d4… lmata 245 self.assertEqual(result, [])
3d0f0d4… lmata 246
3d0f0d4… lmata 247 def test_chunk_larger_than_list(self):
3d0f0d4… lmata 248 result = list(_chunks([1, 2], 10))
3d0f0d4… lmata 249 self.assertEqual(result, [[1, 2]])
3d0f0d4… lmata 250
3d0f0d4… lmata 251 def test_chunk_size_one(self):
3d0f0d4… lmata 252 result = list(_chunks([1, 2, 3], 1))
3d0f0d4… lmata 253 self.assertEqual(result, [[1], [2], [3]])
3d0f0d4… lmata 254
3d0f0d4… lmata 255
3d0f0d4… lmata 256 class TestSeoOgTags(unittest.TestCase):
3d0f0d4… lmata 257 def test_injects_og_tags(self):
3d0f0d4… lmata 258 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 259 baseof = os.path.join(tmp, "layouts", "_default", "baseof.html")
3d0f0d4… lmata 260 os.makedirs(os.path.dirname(baseof))
3d0f0d4… lmata 261 with open(baseof, "w") as f:
3d0f0d4… lmata 262 f.write("<html><head><title>Test</title></head><body></body></html>")
3d0f0d4… lmata 263 result = _seo_og_tags(tmp)
3d0f0d4… lmata 264 self.assertIn("OG tags", result)
3d0f0d4… lmata 265 with open(baseof, "r") as f:
3d0f0d4… lmata 266 html = f.read()
3d0f0d4… lmata 267 self.assertIn("og:title", html)
3d0f0d4… lmata 268 self.assertIn("og:description", html)
3d0f0d4… lmata 269 self.assertIn("og:url", html)
3d0f0d4… lmata 270
3d0f0d4… lmata 271 def test_skips_if_already_present(self):
3d0f0d4… lmata 272 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 273 baseof = os.path.join(tmp, "layouts", "_default", "baseof.html")
3d0f0d4… lmata 274 os.makedirs(os.path.dirname(baseof))
3d0f0d4… lmata 275 with open(baseof, "w") as f:
3d0f0d4… lmata 276 f.write('<html><head><meta property="og:title" content="x" /></head></html>')
3d0f0d4… lmata 277 result = _seo_og_tags(tmp)
3d0f0d4… lmata 278 self.assertIn("already present", result)
3d0f0d4… lmata 279
3d0f0d4… lmata 280 def test_returns_message_when_no_baseof(self):
3d0f0d4… lmata 281 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 282 result = _seo_og_tags(tmp)
3d0f0d4… lmata 283 self.assertIn("No baseof.html found", result)
3d0f0d4… lmata 284
3d0f0d4… lmata 285
3d0f0d4… lmata 286 class TestGenerate(unittest.TestCase):
3d0f0d4… lmata 287 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 288 def test_creates_content_files(self, mock_ai):
3d0f0d4… lmata 289 ai_response = json.dumps({
3d0f0d4… lmata 290 "blog/first-post.md": "---\ntitle: First Post\ndate: 2026-03-17\ndescription: A post\n---\n\nHello world.\n",
3d0f0d4… lmata 291 "blog/second-post.md": "---\ntitle: Second Post\ndate: 2026-03-17\ndescription: Another post\n---\n\nGoodbye world.\n",
3d0f0d4… lmata 292 })
3d0f0d4… lmata 293 mock_ai.return_value = ai_response
3d0f0d4… lmata 294
3d0f0d4… lmata 295 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 296 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 297 f.write('title = "Test"\n')
3d0f0d4… lmata 298 result = generate(tmp, prompt="Write blog posts")
3d0f0d4… lmata 299 self.assertIn("Generated 2", result)
3d0f0d4… lmata 300 self.assertTrue(os.path.exists(os.path.join(tmp, "content", "blog", "first-post.md")))
3d0f0d4… lmata 301 self.assertTrue(os.path.exists(os.path.join(tmp, "content", "blog", "second-post.md")))
3d0f0d4… lmata 302 with open(os.path.join(tmp, "content", "blog", "first-post.md"), "r") as f:
3d0f0d4… lmata 303 self.assertIn("Hello world.", f.read())
3d0f0d4… lmata 304
3d0f0d4… lmata 305 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 306 def test_handles_invalid_ai_response(self, mock_ai):
3d0f0d4… lmata 307 mock_ai.return_value = "not valid json"
3d0f0d4… lmata 308 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 309 result = generate(tmp, prompt="Write posts")
3d0f0d4… lmata 310 self.assertIn("Could not generate content", result)
3d0f0d4… lmata 311
3d0f0d4… lmata 312 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 313 def test_from_file_reads_example(self, mock_ai):
3d0f0d4… lmata 314 ai_response = json.dumps({
3d0f0d4… lmata 315 "page.md": "---\ntitle: New Page\ndate: 2026-03-17\ndescription: New\n---\n\nContent.\n",
3d0f0d4… lmata 316 })
3d0f0d4… lmata 317 mock_ai.return_value = ai_response
3d0f0d4… lmata 318
3d0f0d4… lmata 319 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 320 example = os.path.join(tmp, "example.md")
3d0f0d4… lmata 321 with open(example, "w") as f:
3d0f0d4… lmata 322 f.write("---\ntitle: Example\n---\n\nExample content.\n")
3d0f0d4… lmata 323 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 324 f.write('title = "Test"\n')
3d0f0d4… lmata 325 result = generate(tmp, from_file=example)
3d0f0d4… lmata 326 self.assertIn("Generated 1", result)
3d0f0d4… lmata 327 # Verify call_ai was called with the example content
3d0f0d4… lmata 328 prompt_arg = mock_ai.call_args[0][0]
3d0f0d4… lmata 329 self.assertIn("Example content.", prompt_arg)
3d0f0d4… lmata 330
3d0f0d4… lmata 331
3d0f0d4… lmata 332 class TestSeo(unittest.TestCase):
3d0f0d4… lmata 333 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 334 def test_finds_missing_descriptions(self, mock_ai):
3d0f0d4… lmata 335 mock_ai.return_value = json.dumps({"My Post": "A short description."})
3d0f0d4… lmata 336 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 337 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 338 f.write('title = "Test"\n')
3d0f0d4… lmata 339 content = os.path.join(tmp, "content")
3d0f0d4… lmata 340 os.makedirs(content)
3d0f0d4… lmata 341 path = os.path.join(content, "post.md")
3d0f0d4… lmata 342 with open(path, "w") as f:
3d0f0d4… lmata 343 f.write("---\ntitle: My Post\n---\n\nSome body text.\n")
3d0f0d4… lmata 344 result = seo(tmp)
3d0f0d4… lmata 345 self.assertIn("Added meta descriptions to 1", result)
3d0f0d4… lmata 346 fm = _parse_frontmatter(path)
3d0f0d4… lmata 347 self.assertEqual(fm["description"], "A short description.")
3d0f0d4… lmata 348
3d0f0d4… lmata 349 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 350 def test_skips_files_with_descriptions(self, mock_ai):
3d0f0d4… lmata 351 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 352 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 353 f.write('title = "Test"\n')
3d0f0d4… lmata 354 content = os.path.join(tmp, "content")
3d0f0d4… lmata 355 os.makedirs(content)
3d0f0d4… lmata 356 with open(os.path.join(content, "post.md"), "w") as f:
3d0f0d4… lmata 357 f.write("---\ntitle: My Post\ndescription: Already set\n---\n\nBody.\n")
3d0f0d4… lmata 358 result = seo(tmp)
3d0f0d4… lmata 359 self.assertIn("already have descriptions", result)
3d0f0d4… lmata 360 mock_ai.assert_not_called()
3d0f0d4… lmata 361
3d0f0d4… lmata 362 def test_handles_missing_content_dir(self):
3d0f0d4… lmata 363 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 364 result = seo(tmp)
3d0f0d4… lmata 365 self.assertIn("No content/", result)
3d0f0d4… lmata 366
3d0f0d4… lmata 367
3d0f0d4… lmata 368 class TestAltText(unittest.TestCase):
3d0f0d4… lmata 369 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 370 def test_finds_images_without_alt(self, mock_ai):
3d0f0d4… lmata 371 mock_ai.return_value = json.dumps({"logo.png": "Company logo"})
3d0f0d4… lmata 372 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 373 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 374 f.write('title = "Test"\n')
3d0f0d4… lmata 375 layouts = os.path.join(tmp, "layouts")
3d0f0d4… lmata 376 os.makedirs(layouts)
3d0f0d4… lmata 377 tpl = os.path.join(layouts, "index.html")
3d0f0d4… lmata 378 with open(tpl, "w") as f:
3d0f0d4… lmata 379 f.write('<html><body><img src="logo.png"></body></html>')
3d0f0d4… lmata 380 result = alt_text(tmp)
3d0f0d4… lmata 381 self.assertIn("Added alt text to 1", result)
3d0f0d4… lmata 382 with open(tpl, "r") as f:
3d0f0d4… lmata 383 html = f.read()
3d0f0d4… lmata 384 self.assertIn('alt="Company logo"', html)
3d0f0d4… lmata 385
3d0f0d4… lmata 386 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 387 def test_skips_images_with_alt(self, mock_ai):
3d0f0d4… lmata 388 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 389 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 390 f.write('title = "Test"\n')
3d0f0d4… lmata 391 layouts = os.path.join(tmp, "layouts")
3d0f0d4… lmata 392 os.makedirs(layouts)
3d0f0d4… lmata 393 tpl = os.path.join(layouts, "index.html")
3d0f0d4… lmata 394 with open(tpl, "w") as f:
3d0f0d4… lmata 395 f.write('<html><body><img src="logo.png" alt="A logo"></body></html>')
3d0f0d4… lmata 396 result = alt_text(tmp)
3d0f0d4… lmata 397 self.assertIn("All images already have alt text", result)
3d0f0d4… lmata 398 mock_ai.assert_not_called()
3d0f0d4… lmata 399
3d0f0d4… lmata 400 @patch("hugoifier.utils.enhance.call_ai")
3d0f0d4… lmata 401 def test_finds_images_with_empty_alt(self, mock_ai):
3d0f0d4… lmata 402 mock_ai.return_value = json.dumps({"photo.jpg": "A photo"})
3d0f0d4… lmata 403 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 404 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 405 f.write('title = "Test"\n')
3d0f0d4… lmata 406 layouts = os.path.join(tmp, "layouts")
3d0f0d4… lmata 407 os.makedirs(layouts)
3d0f0d4… lmata 408 tpl = os.path.join(layouts, "page.html")
3d0f0d4… lmata 409 with open(tpl, "w") as f:
3d0f0d4… lmata 410 f.write('<html><body><img src="photo.jpg" alt=""></body></html>')
3d0f0d4… lmata 411 result = alt_text(tmp)
3d0f0d4… lmata 412 self.assertIn("Added alt text to 1", result)
3d0f0d4… lmata 413
3d0f0d4… lmata 414 def test_no_templates_returns_message(self):
3d0f0d4… lmata 415 with tempfile.TemporaryDirectory() as tmp:
3d0f0d4… lmata 416 with open(os.path.join(tmp, "hugo.toml"), "w") as f:
3d0f0d4… lmata 417 f.write('title = "Test"\n')
3d0f0d4… lmata 418 result = alt_text(tmp)
3d0f0d4… lmata 419 self.assertIn("All images already have alt text", result)
3d0f0d4… lmata 420
3d0f0d4… lmata 421
3d0f0d4… lmata 422 if __name__ == "__main__":
3d0f0d4… lmata 423 unittest.main()

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button