Files
marki-docx/tests/test_styles.py
Bernd Worsch 9fe64bcd7f feat: WP-0007 — Interface Completeness & Evidence
T01: markidocx inspect (FR-806) and markidocx test (FR-810) CLI commands
T02: markidocx evidence get/list CLI commands (FR-1409, FR-814)
T03: list_styles() / GET /styles / MCP list_styles with real style data (FR-907)
T04: Evidence assembly — EvidenceSet summary via REST and MCP (FR-1406–1408)
T05: LEVEL3 edge-case tests — diagram mutation, renderer version check,
     bibliography duplicate keys / missing refs / special chars (FR-534, FR-538, FR-542)
T06: markidocx template extract + Word-first round-trip regression test (FR-606)

New: differ._compare_diagram_blocks tracks fenced diagram source drift (FR-534)
New: diagrams.check_renderer_version emits warning for outdated renderers (FR-538)
New: bibliography.validate_citations detects duplicate keys and missing entries (FR-542)
New: templates.extract_template / TemplateExtractionResult / list_styles / StyleEntry
New: REST POST /template/extract; MCP extract_template tool

278 tests pass, ruff+mypy clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:30:09 +00:00

148 lines
5.1 KiB
Python

"""Tests for T03 — style listing across CLI, REST, MCP (FR-907)."""
from __future__ import annotations
import json
import pytest
from fastapi.testclient import TestClient
import markidocx.mcp_server as mcp_module
from markidocx.rest import create_app
@pytest.fixture()
def rest_client() -> TestClient:
return TestClient(create_app())
class TestListStylesFunction:
def test_returns_non_empty_list(self) -> None:
from markidocx.templates import list_styles
entries = list_styles()
assert len(entries) > 0
def test_standard_heading_styles_present(self) -> None:
from markidocx.templates import list_styles
entries = list_styles(family="article")
names = {e.name for e in entries}
# Standard Word styles that python-docx always creates
assert any("Heading" in n or "Normal" in n for n in names)
def test_family_field_matches_requested(self) -> None:
from markidocx.templates import list_styles
for family in ("article", "book", "website"):
entries = list_styles(family=family)
assert all(e.family == family for e in entries)
def test_sorted_by_type_then_name(self) -> None:
from markidocx.templates import list_styles
entries = list_styles()
pairs = [(e.type, e.name) for e in entries]
assert pairs == sorted(pairs)
def test_style_entry_fields(self) -> None:
from markidocx.templates import list_styles
entries = list_styles()
for e in entries:
assert isinstance(e.name, str) and e.name
assert isinstance(e.style_id, str) and e.style_id
assert e.type in ("paragraph", "character", "table", "numbering")
assert isinstance(e.built_in, bool)
def test_each_built_in_family_has_styles(self) -> None:
from markidocx.templates import list_styles
for family in ("article", "book", "website"):
entries = list_styles(family=family)
assert len(entries) > 0, f"No styles for family {family!r}"
class TestRestStylesEndpoint:
def test_get_styles_returns_list(self, rest_client: TestClient) -> None:
resp = rest_client.get("/styles")
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "ok"
assert isinstance(data["outputs"], list)
assert len(data["outputs"]) > 0
def test_get_styles_with_family(self, rest_client: TestClient) -> None:
resp = rest_client.get("/styles?family=book")
assert resp.status_code == 200
data = resp.json()
assert all(e["family"] == "book" for e in data["outputs"])
def test_get_styles_has_required_fields(self, rest_client: TestClient) -> None:
resp = rest_client.get("/styles")
data = resp.json()
for entry in data["outputs"]:
assert "name" in entry
assert "style_id" in entry
assert "type" in entry
assert "family" in entry
assert "built_in" in entry
class TestMcpListStyles:
def test_list_styles_returns_non_empty(self) -> None:
result = mcp_module.list_styles()
assert isinstance(result, list)
assert len(result) > 0
def test_list_styles_with_family(self) -> None:
result = mcp_module.list_styles(family="website")
assert all(e["family"] == "website" for e in result)
class TestCliTemplateStyles:
def test_template_styles_command(self) -> None:
from typer.testing import CliRunner
from markidocx.cli import app
runner = CliRunner()
result = runner.invoke(app, ["template", "styles"])
assert result.exit_code == 0
def test_template_styles_json(self) -> None:
from typer.testing import CliRunner
from markidocx.cli import app
runner = CliRunner()
result = runner.invoke(app, ["template", "styles", "--json"])
assert result.exit_code == 0
data = json.loads(result.output.strip())
assert isinstance(data, list)
assert len(data) > 0
assert "name" in data[0]
def test_template_styles_family_filter(self) -> None:
from typer.testing import CliRunner
from markidocx.cli import app
runner = CliRunner()
result = runner.invoke(app, ["template", "styles", "--family", "book", "--json"])
assert result.exit_code == 0
data = json.loads(result.output.strip())
assert all(e["family"] == "book" for e in data)
class TestStylesParityAcrossInterfaces:
def test_rest_mcp_same_count_for_article(self, rest_client: TestClient) -> None:
rest_entries = rest_client.get("/styles?family=article").json()["outputs"]
mcp_entries = mcp_module.list_styles(family="article")
assert len(rest_entries) == len(mcp_entries)
def test_rest_mcp_same_names_for_article(self, rest_client: TestClient) -> None:
rest_names = {e["name"] for e in rest_client.get("/styles?family=article").json()["outputs"]}
mcp_names = {e["name"] for e in mcp_module.list_styles(family="article")}
assert rest_names == mcp_names