feat(mcp-docugen): Task 7-10 renderer, http_routes, mcp_tools, main bootstrap
- Renderer: orchestratore generate() — validazione strict variabili,
materializzazione image vars come asset effimeri su disco + URL rewrite,
asset paths template da ./assets/X -> {PUBLIC_BASE_URL}/assets/<t>/X,
integrazione LLM error -> record success=0
- FastAPI sub-app: GET /health (no auth), /assets/{t}/{f} (auth+traversal check),
/generated/{gen_id}/{f} (410 su scaduto o mancante)
- FastMCP server con 6 tool: template_create/update/delete/list/get,
document_generate. Tools esposti anche via mcp.tools dict per test.
- main.build_app() compone http_app + FastMCP mount su /mcp + auth middleware
+ lifespan cleanup task TTL (24h). run() entry point per script console.
68 test passed. Build Docker arca-mcp-docugen:dev verificata,
/health endpoint risponde correttamente nel container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp_docugen.generation_store import GenerationStore
|
||||
from mcp_docugen.llm_client import LLMResponse
|
||||
from mcp_docugen.mcp_tools import build_mcp_server
|
||||
from mcp_docugen.renderer import Renderer
|
||||
from mcp_docugen.template_store import TemplateStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mcp_env(tmp_path):
|
||||
template_store = TemplateStore(base_dir=tmp_path / "templates")
|
||||
generation_store = GenerationStore(
|
||||
db_path=tmp_path / "gen.db",
|
||||
generated_dir=tmp_path / "generated",
|
||||
)
|
||||
await generation_store.init()
|
||||
llm = AsyncMock()
|
||||
llm.chat.return_value = LLMResponse(
|
||||
text="# Out",
|
||||
model="m",
|
||||
tokens_in=5,
|
||||
tokens_out=10,
|
||||
cost_usd=0.01,
|
||||
latency_ms=50,
|
||||
)
|
||||
renderer = Renderer(
|
||||
template_store=template_store,
|
||||
generation_store=generation_store,
|
||||
llm=llm,
|
||||
public_base_url="https://mcp.example.com",
|
||||
default_model="m",
|
||||
asset_ttl_days=30,
|
||||
max_image_size_mb=10,
|
||||
)
|
||||
mcp = build_mcp_server(template_store, renderer)
|
||||
return mcp, template_store
|
||||
|
||||
|
||||
async def test_template_create_and_get(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="demo",
|
||||
frontmatter={"name": "demo", "description": "d"},
|
||||
body="body",
|
||||
assets=None,
|
||||
)
|
||||
got = await mcp.tools["template_get"](name="demo")
|
||||
assert got["name"] == "demo"
|
||||
assert got["body"] == "body"
|
||||
|
||||
|
||||
async def test_template_create_duplicate_errors(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="demo",
|
||||
frontmatter={"name": "demo", "description": "x"},
|
||||
body="b",
|
||||
assets=None,
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
await mcp.tools["template_create"](
|
||||
name="demo",
|
||||
frontmatter={"name": "demo", "description": "x"},
|
||||
body="b",
|
||||
assets=None,
|
||||
)
|
||||
|
||||
|
||||
async def test_template_list_returns_summaries(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="a",
|
||||
frontmatter={"name": "a", "description": "A"},
|
||||
body="b",
|
||||
assets=None,
|
||||
)
|
||||
await mcp.tools["template_create"](
|
||||
name="b",
|
||||
frontmatter={"name": "b", "description": "B"},
|
||||
body="b",
|
||||
assets=None,
|
||||
)
|
||||
result = await mcp.tools["template_list"]()
|
||||
names = sorted(t["name"] for t in result)
|
||||
assert names == ["a", "b"]
|
||||
|
||||
|
||||
async def test_template_update(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="u",
|
||||
frontmatter={"name": "u", "description": "d"},
|
||||
body="old",
|
||||
assets=None,
|
||||
)
|
||||
await mcp.tools["template_update"](
|
||||
name="u",
|
||||
frontmatter={"name": "u", "description": "new"},
|
||||
body="new",
|
||||
assets=None,
|
||||
)
|
||||
got = await mcp.tools["template_get"](name="u")
|
||||
assert got["body"] == "new"
|
||||
assert got["frontmatter"]["description"] == "new"
|
||||
|
||||
|
||||
async def test_template_delete(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="d",
|
||||
frontmatter={"name": "d", "description": "x"},
|
||||
body="b",
|
||||
assets=None,
|
||||
)
|
||||
await mcp.tools["template_delete"](name="d")
|
||||
with pytest.raises(Exception):
|
||||
await mcp.tools["template_get"](name="d")
|
||||
|
||||
|
||||
async def test_document_generate_happy_path(mcp_env):
|
||||
mcp, _ = mcp_env
|
||||
await mcp.tools["template_create"](
|
||||
name="g",
|
||||
frontmatter={
|
||||
"name": "g",
|
||||
"description": "x",
|
||||
"required_variables": [{"name": "cliente", "type": "string"}],
|
||||
},
|
||||
body="Cliente: {{cliente}}",
|
||||
assets=None,
|
||||
)
|
||||
result = await mcp.tools["document_generate"](
|
||||
template_name="g",
|
||||
content_md="# Ordine",
|
||||
variables={"cliente": "ACME"},
|
||||
instructions=None,
|
||||
)
|
||||
assert result["markdown"] == "# Out"
|
||||
assert result["model"] == "m"
|
||||
assert result["tokens"]["input"] == 5
|
||||
assert "generation_id" in result
|
||||
Reference in New Issue
Block a user