test: add cover/frontmatter acceptance criteria validation tests (#473)

* test: add cover/frontmatter acceptance criteria validation tests Add tests verifying issue #358 acceptance criteria: - OG image template uses 1200x630 dimensions - Cover card uses 800x400 base with responsive srcsets (1600x800 2x) - Cover template only renders locally-hosted resources (no hotlinking) - Image registry exists at data/image-registry.json Closes #358 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address review comments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed Jun 14, 2026 at 10:23 UTC 140edee654fa4a671c6632b42cca3ecec4df4579
1 file changed +56
tests/test_article_visuals.py
+56
@@ -135,3 +135,59 @@ def test_article_visuals_css_is_responsive() -> None:
135 css = _read(ROOT / "assets/css/extended/article-visuals.css")
136 assert "@media (max-width: 768px)" in css
137 assert "aspect-ratio" in css # reserve space -> no CLS
138 +
139 +
140 +# -----------------------------------------------------------------------
141 +# Cover image pipeline validation (issue #358 acceptance criteria)
142 +# -----------------------------------------------------------------------
143 +
144 +
145 +def test_og_image_template_uses_1200x630() -> None:
146 + """OG image must be resized to 1200x630 per issue #358 criterion 3."""
147 + og = _read(ROOT / "layouts/partials/templates/opengraph.html")
148 + assert "1200x630" in og, "OG template must resize to 1200x630"
149 +
150 +
151 +def test_cover_image_orchestrator_uses_800x400_with_srcset() -> None:
152 + """Cover image orchestrator must emit responsive 800x400/1600x800 local cover variants."""
153 + orchestrator = _read(VIS / "article-cover.html")
154 + assert "800x400" in orchestrator, "Cover image flow must produce 800x400 base size"
155 + assert "1600x800" in orchestrator, "Cover image flow must produce 1600x800 for 2x srcset"
156 + assert "srcset" in orchestrator, "Cover image flow must use responsive srcset"
157 +
158 +
159 +def test_cover_only_renders_local_resources() -> None:
160 + """Cover template must only render locally-hosted images (no hotlinking)."""
161 + cover = _read(VIS / "article-cover.html")
162 + assert "$page.Resources.GetMatch" in cover, "Cover lookup must resolve page-bundle resources"
163 + assert "resources.Get" in cover, "Cover lookup must resolve global Hugo resources"
164 + assert 'eq $candidate.ResourceType "image"' in cover, "Only Hugo image resources should render"
165 + assert ".RelPermalink" in cover, "Rendered cover URLs must use local RelPermalink paths"
166 + assert 'replaceRE `(?i)<img[^>]*>` ""' in cover, "Attribution must strip rendered img tags"
167 +
168 +
169 +def test_image_registry_exists_with_required_fields() -> None:
170 + """data/image-registry.json must exist with required schema fields."""
171 + import json
172 +
173 + registry_path = ROOT / "data" / "image-registry.json"
174 + schema_path = ROOT / "data" / "image-registry.schema.json"
175 +
176 + assert registry_path.exists(), "Image registry must exist at data/image-registry.json"
177 + assert schema_path.exists(), "Image registry schema must exist at data/image-registry.schema.json"
178 +
179 + schema = json.loads(schema_path.read_text(encoding="utf-8"))
180 + assert schema.get("type") == "object", "Image registry schema must define an object"
181 + assert "images" in schema.get("required", []), "Image registry schema must require an images array"
182 + images_schema = schema.get("properties", {}).get("images", {})
183 + assert images_schema.get("type") == "array", "Image registry schema must define images as an array"
184 + item_properties = images_schema.get("items", {}).get("properties", {})
185 + required_fields = {"filename", "source_url", "license", "attribution", "added_by"}
186 + assert required_fields.issubset(item_properties), (
187 + f"Image registry schema missing required fields: {sorted(required_fields - set(item_properties))}"
188 + )
189 +
190 + data = json.loads(registry_path.read_text(encoding="utf-8"))
191 + assert isinstance(data, dict), "Image registry must be a JSON object"
192 + assert data.get("$schema") == "./image-registry.schema.json"
193 + assert isinstance(data.get("images"), list), "Image registry must declare an images array"