generated from coulomb/repo-seed
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>
148 lines
5.1 KiB
Python
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
|