import base64 import pytest from mcp_docugen.models import TemplateFrontmatter from mcp_docugen.template_store import ( InvalidFrontmatter, TemplateAlreadyExists, TemplateNotFound, TemplateStore, ) @pytest.fixture def store(tmp_path): return TemplateStore(base_dir=tmp_path) def _fm(name="fattura"): return TemplateFrontmatter( name=name, description="Fattura commerciale", required_variables=[{"name": "cliente", "type": "string"}], ) async def test_create_and_get(store): fm = _fm() await store.create(name="fattura", frontmatter=fm, body="# Body {{cliente}}") got = await store.get("fattura") assert got.frontmatter.name == "fattura" assert "# Body {{cliente}}" in got.body async def test_create_duplicate_raises(store): fm = _fm() await store.create(name="fattura", frontmatter=fm, body="body") with pytest.raises(TemplateAlreadyExists): await store.create(name="fattura", frontmatter=fm, body="body") async def test_get_missing_raises(store): with pytest.raises(TemplateNotFound): await store.get("nope") async def test_list_returns_summaries(store): await store.create(name="a", frontmatter=_fm("a"), body="b") await store.create(name="b", frontmatter=_fm("b"), body="b") result = await store.list() names = sorted(t.name for t in result) assert names == ["a", "b"] async def test_update_overwrites(store): await store.create(name="f", frontmatter=_fm("f"), body="old") new_fm = TemplateFrontmatter(name="f", description="new description") await store.update(name="f", frontmatter=new_fm, body="new body") got = await store.get("f") assert got.body == "new body" assert got.frontmatter.description == "new description" async def test_update_missing_raises(store): with pytest.raises(TemplateNotFound): await store.update(name="nope", frontmatter=_fm("nope"), body="x") async def test_delete_removes(store): await store.create(name="f", frontmatter=_fm("f"), body="b") await store.delete("f") with pytest.raises(TemplateNotFound): await store.get("f") async def test_delete_missing_raises(store): with pytest.raises(TemplateNotFound): await store.delete("nope") async def test_assets_are_saved_and_listed(store): png_bytes = b"\x89PNG\r\n\x1a\n" assets = [ { "filename": "logo.png", "data_b64": base64.b64encode(png_bytes).decode(), "mime": "image/png", } ] await store.create(name="f", frontmatter=_fm("f"), body="b", assets=assets) got = await store.get("f") asset_names = [a.filename for a in got.assets] assert "logo.png" in asset_names content = await store.read_asset("f", "logo.png") assert content == png_bytes async def test_asset_filename_rejects_path_traversal(store): assets = [{"filename": "../evil.png", "data_b64": "aGk=", "mime": "image/png"}] with pytest.raises(ValueError): await store.create(name="f", frontmatter=_fm("f"), body="b", assets=assets) async def test_frontmatter_parsing_rejects_malformed_yaml(store, tmp_path): template_dir = tmp_path / "broken" template_dir.mkdir() (template_dir / "template.md").write_text("---\nname: :::broken\n---\nbody") with pytest.raises(InvalidFrontmatter): await store.get("broken")