generated from coulomb/repo-seed
Implements full LEVEL3 feature set: cross-references (xref.py), numbered figures (figures.py), auto-diagrams (diagrams.py), bibliography/citations (bibliography.py), LEVEL3 capability detection (level3.py), and structured error/warning records (errors.py). Builder, importer, and differ updated for LEVEL3 round-trip support. REST and MCP interfaces updated with structured warning records. 259 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
272 lines
9.3 KiB
Python
272 lines
9.3 KiB
Python
"""Tests for LEVEL3 plumbing — feature-level gating & disclosure (FR-537–539)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import textwrap
|
||
from pathlib import Path
|
||
|
||
from markidocx.level3 import (
|
||
Level3Support,
|
||
ProcessorDependency,
|
||
capabilities_entry,
|
||
check_level3_support,
|
||
)
|
||
from markidocx.manifest import FeatureLevel, load_manifest
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Level3 support detection (FR-537, FR-538)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestCheckLevel3Support:
|
||
def test_returns_level3_support(self) -> None:
|
||
support = check_level3_support()
|
||
assert isinstance(support, Level3Support)
|
||
|
||
def test_always_available(self) -> None:
|
||
support = check_level3_support()
|
||
assert support.available is True
|
||
|
||
def test_dependencies_are_processor_dependency_instances(self) -> None:
|
||
support = check_level3_support()
|
||
for dep in support.dependencies:
|
||
assert isinstance(dep, ProcessorDependency)
|
||
assert dep.name in ("mmdc", "dot", "plantuml")
|
||
assert isinstance(dep.available, bool)
|
||
assert dep.description
|
||
|
||
def test_partial_when_no_diagram_tools(self, monkeypatch) -> None:
|
||
"""When no diagram tool is found, partial=True and missing_coverage is populated."""
|
||
import shutil
|
||
|
||
monkeypatch.setattr(shutil, "which", lambda _cmd: None)
|
||
support = check_level3_support()
|
||
assert support.partial is True
|
||
assert len(support.missing_coverage) > 0
|
||
assert any("diagram" in m for m in support.missing_coverage)
|
||
|
||
def test_not_partial_when_diagram_tool_present(self, monkeypatch) -> None:
|
||
"""When at least one diagram tool is found, partial=False."""
|
||
import shutil
|
||
|
||
def fake_which(cmd: str) -> str | None:
|
||
return "/usr/bin/mmdc" if cmd == "mmdc" else None
|
||
|
||
monkeypatch.setattr(shutil, "which", fake_which)
|
||
support = check_level3_support()
|
||
assert support.partial is False
|
||
assert support.missing_coverage == []
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# capabilities_entry (FR-537)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestCapabilitiesEntry:
|
||
def test_returns_dict_with_level(self) -> None:
|
||
entry = capabilities_entry()
|
||
assert entry["level"] == "level3"
|
||
|
||
def test_available_is_true(self) -> None:
|
||
entry = capabilities_entry()
|
||
assert entry["available"] is True
|
||
|
||
def test_has_dependencies_list(self) -> None:
|
||
entry = capabilities_entry()
|
||
assert isinstance(entry["dependencies"], list)
|
||
for dep in entry["dependencies"]:
|
||
assert "name" in dep
|
||
assert "available" in dep
|
||
assert "description" in dep
|
||
|
||
def test_has_partial_and_missing_coverage(self) -> None:
|
||
entry = capabilities_entry()
|
||
assert "partial" in entry
|
||
assert "missing_coverage" in entry
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Manifest accepts feature_level: level3 (FR-537)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestManifestLevel3:
|
||
def test_level3_accepted(self, tmp_path: Path) -> None:
|
||
(tmp_path / "doc.md").write_text("# Hello", encoding="utf-8")
|
||
(tmp_path / "manifest.yaml").write_text(
|
||
textwrap.dedent("""\
|
||
project:
|
||
name: test
|
||
feature_level: level3
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
"""),
|
||
encoding="utf-8",
|
||
)
|
||
m = load_manifest(tmp_path / "manifest.yaml")
|
||
assert m.project.feature_level == FeatureLevel.LEVEL3
|
||
|
||
def test_level3_routes_to_level3_processing(self, tmp_path: Path) -> None:
|
||
"""Building with feature_level: level3 succeeds (processing path reached)."""
|
||
from markidocx.builder import build_document
|
||
|
||
(tmp_path / "doc.md").write_text("# Hello\n\nContent.", encoding="utf-8")
|
||
(tmp_path / "manifest.yaml").write_text(
|
||
textwrap.dedent("""\
|
||
project:
|
||
name: test-l3
|
||
feature_level: level3
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
"""),
|
||
encoding="utf-8",
|
||
)
|
||
m = load_manifest(tmp_path / "manifest.yaml")
|
||
result = build_document(m)
|
||
assert result.success
|
||
assert result.feature_level == "level3"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# partial_level3 flag and processor-dependency disclosure (FR-538, FR-539)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestPartialLevel3Flag:
|
||
def test_partial_level3_set_when_no_diagram_tools(
|
||
self, tmp_path: Path, monkeypatch
|
||
) -> None:
|
||
import shutil
|
||
|
||
from markidocx.builder import build_document
|
||
|
||
monkeypatch.setattr(shutil, "which", lambda _cmd: None)
|
||
(tmp_path / "doc.md").write_text("# Hello\n\nContent.", encoding="utf-8")
|
||
(tmp_path / "manifest.yaml").write_text(
|
||
textwrap.dedent("""\
|
||
project:
|
||
name: test-partial
|
||
feature_level: level3
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
"""),
|
||
encoding="utf-8",
|
||
)
|
||
m = load_manifest(tmp_path / "manifest.yaml")
|
||
result = build_document(m)
|
||
assert result.success
|
||
assert result.partial_level3 is True
|
||
assert len(result.missing_coverage) > 0
|
||
|
||
def test_partial_level3_false_for_level1(self, tmp_path: Path) -> None:
|
||
from markidocx.builder import build_document
|
||
|
||
(tmp_path / "doc.md").write_text("# Hello\n\nContent.", encoding="utf-8")
|
||
(tmp_path / "manifest.yaml").write_text(
|
||
textwrap.dedent("""\
|
||
project:
|
||
name: test-l1
|
||
feature_level: level1
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
"""),
|
||
encoding="utf-8",
|
||
)
|
||
m = load_manifest(tmp_path / "manifest.yaml")
|
||
result = build_document(m)
|
||
assert result.partial_level3 is False
|
||
assert result.missing_coverage == []
|
||
|
||
def test_dependency_warning_emitted_for_unavailable_tool(
|
||
self, tmp_path: Path, monkeypatch
|
||
) -> None:
|
||
import shutil
|
||
|
||
from markidocx.builder import build_document
|
||
from markidocx.errors import Severity
|
||
|
||
monkeypatch.setattr(shutil, "which", lambda _cmd: None)
|
||
(tmp_path / "doc.md").write_text("# Hello", encoding="utf-8")
|
||
(tmp_path / "manifest.yaml").write_text(
|
||
textwrap.dedent("""\
|
||
project:
|
||
name: t
|
||
feature_level: level3
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
"""),
|
||
encoding="utf-8",
|
||
)
|
||
m = load_manifest(tmp_path / "manifest.yaml")
|
||
result = build_document(m)
|
||
dep_warnings = [
|
||
w for w in result.warning_records
|
||
if w.reason == "processor-dependency-unavailable"
|
||
]
|
||
assert dep_warnings, "Expected processor-dependency-unavailable warning"
|
||
assert all(w.severity == Severity.WARNING for w in dep_warnings)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# REST capabilities includes level3 (FR-537)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestRestCapabilitiesLevel3:
|
||
def test_capabilities_includes_level3(self) -> None:
|
||
from fastapi.testclient import TestClient
|
||
|
||
from markidocx.rest import create_app
|
||
|
||
client = TestClient(create_app())
|
||
resp = client.get("/capabilities")
|
||
assert resp.status_code == 200
|
||
body = resp.json()
|
||
outputs = body["outputs"]
|
||
assert "level3" in outputs
|
||
assert outputs["level3"]["level"] == "level3"
|
||
assert outputs["level3"]["available"] is True
|
||
assert "dependencies" in outputs["level3"]
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# MCP validate_project includes level3 in context (FR-537)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestMcpLevel3:
|
||
def test_validate_project_includes_level3(self) -> None:
|
||
from markidocx.mcp_server import validate_project
|
||
|
||
manifest_yaml = textwrap.dedent("""\
|
||
project:
|
||
name: test
|
||
feature_level: level3
|
||
family: article
|
||
sources:
|
||
- path: doc.md
|
||
output:
|
||
dir: ./dist
|
||
""")
|
||
result = validate_project(manifest_yaml)
|
||
assert result["status"] == "ok"
|
||
assert result["feature_level"] == "level3"
|
||
assert "level3" in result["context"]
|
||
assert result["context"]["level3"]["available"] is True
|