test: RED phase — manifest tests (T02)

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 18:33:51 +01:00
parent e623239595
commit 42789cad1e

129
tests/test_manifest.py Normal file
View File

@@ -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")