From 42789cad1e206990c0791ece7949fb66b09823b0 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 14 Mar 2026 18:33:51 +0100 Subject: [PATCH] =?UTF-8?q?test:=20RED=20phase=20=E2=80=94=20manifest=20te?= =?UTF-8?q?sts=20(T02)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/test_manifest.py: full test suite for FR-100 manifest model (load, validate, missing sources, invalid feature_level/family, path resolution). Tests fail — module not yet implemented. Co-Authored-By: Claude Sonnet 4.6 --- tests/test_manifest.py | 129 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 tests/test_manifest.py diff --git a/tests/test_manifest.py b/tests/test_manifest.py new file mode 100644 index 0000000..4bb83ca --- /dev/null +++ b/tests/test_manifest.py @@ -0,0 +1,129 @@ +"""Tests for manifest model (FR-100).""" + +from __future__ import annotations + +from pathlib import Path + +import pytest +import yaml + +from markidocx.manifest import ( + FeatureLevel, + Manifest, + ManifestError, + load_manifest, +) + + +@pytest.fixture +def minimal_manifest_yaml(tmp_path: Path) -> Path: + src = tmp_path / "doc.md" + src.write_text("# Hello\n\nContent here.") + manifest = tmp_path / "manifest.yaml" + manifest.write_text( + yaml.dump( + { + "project": {"name": "Test Doc", "feature_level": "level1", "family": "article"}, + "sources": [{"path": "doc.md"}], + "output": {"dir": "./dist"}, + } + ) + ) + return manifest + + +@pytest.fixture +def multi_source_manifest(tmp_path: Path) -> Path: + for name in ("ch1.md", "ch2.md"): + (tmp_path / name).write_text(f"# {name}\n\nContent.") + manifest = tmp_path / "manifest.yaml" + manifest.write_text( + yaml.dump( + { + "project": {"name": "Book", "feature_level": "level1", "family": "book"}, + "sources": [{"path": "ch1.md"}, {"path": "ch2.md"}], + "output": {"dir": "./dist"}, + "metadata": {"title": "My Book", "author": "Author", "date": "2026-01-01"}, + } + ) + ) + return manifest + + +class TestLoadManifest: + def test_loads_minimal_manifest(self, minimal_manifest_yaml: Path) -> None: + m = load_manifest(minimal_manifest_yaml) + assert m.project.name == "Test Doc" + assert m.project.feature_level == FeatureLevel.LEVEL1 + assert m.project.family == "article" + assert len(m.sources) == 1 + + def test_loads_multi_source(self, multi_source_manifest: Path) -> None: + m = load_manifest(multi_source_manifest) + assert len(m.sources) == 2 + assert m.metadata["title"] == "My Book" + + def test_missing_file_raises(self, tmp_path: Path) -> None: + manifest = tmp_path / "manifest.yaml" + manifest.write_text( + yaml.dump( + { + "project": {"name": "X", "feature_level": "level1", "family": "article"}, + "sources": [{"path": "missing.md"}], + "output": {"dir": "./dist"}, + } + ) + ) + with pytest.raises(ManifestError, match="missing.md"): + load_manifest(manifest) + + def test_missing_project_key_raises(self, tmp_path: Path) -> None: + manifest = tmp_path / "manifest.yaml" + manifest.write_text(yaml.dump({"sources": []})) + with pytest.raises(ManifestError, match="project"): + load_manifest(manifest) + + def test_invalid_feature_level_raises(self, tmp_path: Path) -> None: + src = tmp_path / "doc.md" + src.write_text("# x") + manifest = tmp_path / "manifest.yaml" + manifest.write_text( + yaml.dump( + { + "project": {"name": "X", "feature_level": "level9", "family": "article"}, + "sources": [{"path": "doc.md"}], + "output": {"dir": "./dist"}, + } + ) + ) + with pytest.raises(ManifestError, match="feature_level"): + load_manifest(manifest) + + def test_invalid_family_raises(self, tmp_path: Path) -> None: + src = tmp_path / "doc.md" + src.write_text("# x") + manifest = tmp_path / "manifest.yaml" + manifest.write_text( + yaml.dump( + { + "project": {"name": "X", "feature_level": "level1", "family": "unknown"}, + "sources": [{"path": "doc.md"}], + "output": {"dir": "./dist"}, + } + ) + ) + with pytest.raises(ManifestError, match="family"): + load_manifest(manifest) + + def test_sources_resolved_relative_to_manifest(self, minimal_manifest_yaml: Path) -> None: + m = load_manifest(minimal_manifest_yaml) + assert m.sources[0].path.is_absolute() + assert m.sources[0].path.exists() + + def test_output_dir_resolved(self, minimal_manifest_yaml: Path) -> None: + m = load_manifest(minimal_manifest_yaml) + assert m.output_dir.is_absolute() + + def test_manifest_not_found_raises(self, tmp_path: Path) -> None: + with pytest.raises(ManifestError, match="not found"): + load_manifest(tmp_path / "nonexistent.yaml")