"""The catalog must never write over a customer's templates. These tests exist because the opposite behaviour shipped first: the catalog used to be re-imported on every start, which would silently discard an admin's edits the next time we improved a shipped blueprint. """ from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models import Template from app.template_catalog import ( STARTER_TEMPLATE_IDS, catalog_for_locale, load_catalog, seed_starter_templates, ) from app.template_import import parse_template, upsert_template async def test_starter_set_seeds_only_an_empty_table(db: AsyncSession) -> None: seeded = await seed_starter_templates(db) assert seeded == len(STARTER_TEMPLATE_IDS) ids = set((await db.execute(select(Template.config["id"].astext))).scalars().all()) assert ids == set(STARTER_TEMPLATE_IDS) # A second start adds nothing — the admin's curation is the truth. assert await seed_starter_templates(db) == 0 count = (await db.execute(select(func.count(Template.id)))).scalar_one() assert count == len(STARTER_TEMPLATE_IDS) async def test_seeding_never_overwrites_an_edited_template(db: AsyncSession) -> None: """The regression this module is named for: an admin edits a starter template, the server restarts, and the edit survives.""" await seed_starter_templates(db) row = ( await db.execute( select(Template).where(Template.config["id"].astext == "prozess") ) ).scalar_one() row.name = "Unser Ablauf" row.config = {**row.config, "persona": "Komplett umgeschrieben."} await db.commit() await seed_starter_templates(db) await db.refresh(row) assert row.name == "Unser Ablauf" assert row.config["persona"] == "Komplett umgeschrieben." async def test_deleting_a_starter_template_keeps_it_deleted(db: AsyncSession) -> None: """Deleting all but one must not resurrect the rest on restart — the table is non-empty, so seeding stays out.""" await seed_starter_templates(db) rows = (await db.execute(select(Template))).scalars().all() for row in rows[1:]: await db.delete(row) await db.commit() await seed_starter_templates(db) count = (await db.execute(select(func.count(Template.id)))).scalar_one() assert count == 1 async def test_an_empty_table_after_deleting_everything_reseeds( db: AsyncSession, ) -> None: """The flip side, and the honest consequence of the empty-table rule: an admin who removes every template gets the starter set back on restart rather than an instance nobody can capture with.""" await seed_starter_templates(db) for row in (await db.execute(select(Template))).scalars().all(): await db.delete(row) await db.commit() assert await seed_starter_templates(db) == len(STARTER_TEMPLATE_IDS) async def test_importing_a_customer_template_is_untouched_by_seeding( db: AsyncSession, ) -> None: own = parse_template( "\n".join( [ "id: unser-eigenes", 'name: "Unser eigenes"', 'version: "1.0"', "kind: authoring", "persona: |", " Du bist ein Fachredakteur.", 'title_template: "X: {{user.name}} ({{date}})"', "skeleton: |", " ## Thema", "sections:", ' - heading: "Thema"', ' hint: "Worum es geht."', ] ) ) await upsert_template(db, own) await db.commit() # The table is not empty, so nothing is seeded over it. assert await seed_starter_templates(db) == 0 count = (await db.execute(select(func.count(Template.id)))).scalar_one() assert count == 1 def test_every_shipped_blueprint_parses_and_declares_its_language() -> None: """A broken blueprint is skipped at load time, so a silent typo would quietly shrink the catalog instead of failing loudly.""" entries = load_catalog() assert len(entries) >= len(STARTER_TEMPLATE_IDS) assert all(entry.locale in ("de", "en") for entry in entries) assert all(entry.description for entry in entries) # A blueprint may have no sections at all (`notiz` opens an empty # document on purpose), but one that HAS a skeleton must hint at it. assert all(entry.sections >= 1 for entry in entries if entry.id != "notiz") def test_catalog_collapses_language_variants_to_one_entry_per_id() -> None: per_locale = load_catalog() collapsed = catalog_for_locale("de") assert len(collapsed) == len({entry.id for entry in per_locale}) assert all(entry.locale == "de" for entry in collapsed)