Files
marki-docx/tests/test_manifest.py
Bernd Worsch 1f3dddf7d6 feat: WP-0001 + WP-0002 complete — LEVEL1 core + service interfaces
WP-0001 (Foundation & LEVEL1 Core):
- manifest model (FR-100), MD→DOCX builder (FR-200), DOCX→MD importer
  (FR-300/400), template family registry (FR-600), drift detector (FR-700),
  CLI wiring, pre-commit config, CI skeleton, regression harness

WP-0002 (Service Interfaces & Workflow Orchestration):
- REST service via FastAPI (FR-900): /health, /version, /capabilities,
  /templates, /styles, /validate, /build, /import, /compare,
  /templates/register, /workflows/{name}, /evidence/{run_id}
- Evidence & report store (FR-1400): JSON-backed, per-run, retrievable
  through all interfaces, classification (pass/warnings/failed)
- Composite workflow orchestration (FR-1300): single-file-roundtrip,
  multi-file-roundtrip, release-regression, family-switch-build
- MCP server via FastMCP (FR-1000): all tools + resources
- CLI additions: `markidocx serve`, `markidocx workflow`, `markidocx mcp`
- Interface parity tests: CLI / REST / MCP produce equivalent results

135 tests passing, ruff + mypy clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 07:46:31 +00:00

128 lines
4.3 KiB
Python

"""Tests for manifest model (FR-100)."""
from __future__ import annotations
from pathlib import Path
import pytest
import yaml
from markidocx.manifest import (
FeatureLevel,
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")