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,76 @@
|
||||
import base64
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from mcp_docugen.main import build_app
|
||||
from mcp_docugen.models import TemplateFrontmatter, TemplateVariable
|
||||
|
||||
|
||||
def _openrouter_success(text: str = "# Generated"):
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": "g",
|
||||
"choices": [{"message": {"role": "assistant", "content": text}}],
|
||||
"model": "anthropic/claude-sonnet-4",
|
||||
"usage": {
|
||||
"prompt_tokens": 50,
|
||||
"completion_tokens": 100,
|
||||
"total_cost": 0.02,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def app_env(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("API_KEY", "test-api-key")
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-test")
|
||||
monkeypatch.setenv("PUBLIC_BASE_URL", "https://mcp.test.com")
|
||||
monkeypatch.setenv("DATA_DIR", str(tmp_path))
|
||||
app = await build_app()
|
||||
return app, tmp_path
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_full_generation_flow_records_and_serves_asset(app_env):
|
||||
app, tmp_path = app_env
|
||||
respx.post("https://openrouter.ai/api/v1/chat/completions").mock(
|
||||
return_value=_openrouter_success("# Done")
|
||||
)
|
||||
|
||||
template_store = app.state.template_store
|
||||
renderer = app.state.renderer
|
||||
generation_store = app.state.generation_store
|
||||
|
||||
fm = TemplateFrontmatter(
|
||||
name="report",
|
||||
description="x",
|
||||
required_variables=[TemplateVariable(name="foto", type="image")],
|
||||
)
|
||||
await template_store.create(
|
||||
name="report", frontmatter=fm, body=""
|
||||
)
|
||||
|
||||
png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 50
|
||||
img_var = {
|
||||
"kind": "image",
|
||||
"data_b64": base64.b64encode(png).decode(),
|
||||
"mime": "image/png",
|
||||
}
|
||||
|
||||
result = await renderer.generate(
|
||||
template_name="report",
|
||||
content_md="content",
|
||||
variables={"foto": img_var},
|
||||
instructions=None,
|
||||
)
|
||||
|
||||
assert result.markdown == "# Done"
|
||||
stats = await generation_store.get_stats()
|
||||
assert stats["success"] == 1
|
||||
|
||||
gen_files = list((tmp_path / "generated" / result.generation_id).iterdir())
|
||||
assert len(gen_files) == 1
|
||||
Reference in New Issue
Block a user