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