diff --git a/src/repositories/welcome_template.py b/src/repositories/welcome_template.py new file mode 100644 index 0000000..a3f40f0 --- /dev/null +++ b/src/repositories/welcome_template.py @@ -0,0 +1,53 @@ +"""Repository for the per-instance welcome-prompt override (singleton row).""" + +from datetime import datetime, timezone +from typing import Any, Optional + +import duckdb + + +class WelcomeTemplateRepository: + def __init__(self, conn: duckdb.DuckDBPyConnection): + self.conn = conn + + def get(self) -> dict[str, Any]: + """Return the singleton row. Always exists post-migration; content + is None when no override is set (= use shipped default).""" + row = self.conn.execute( + "SELECT id, content, updated_at, updated_by FROM welcome_template WHERE id = 1" + ).fetchone() + if row is None: + # Defensive: re-seed if a previous admin manually deleted it. + self.conn.execute( + "INSERT INTO welcome_template (id, content) VALUES (1, NULL) " + "ON CONFLICT (id) DO NOTHING" + ) + return {"id": 1, "content": None, "updated_at": None, "updated_by": None} + return { + "id": row[0], + "content": row[1], + "updated_at": row[2], + "updated_by": row[3], + } + + def set(self, content: str, *, updated_by: str) -> None: + now = datetime.now(timezone.utc) + self.conn.execute( + """INSERT INTO welcome_template (id, content, updated_at, updated_by) + VALUES (1, ?, ?, ?) + ON CONFLICT (id) DO UPDATE SET + content = excluded.content, + updated_at = excluded.updated_at, + updated_by = excluded.updated_by""", + [content, now, updated_by], + ) + + def reset(self, *, updated_by: str) -> None: + """Clear override; renderer falls back to shipped default.""" + now = datetime.now(timezone.utc) + self.conn.execute( + """UPDATE welcome_template + SET content = NULL, updated_at = ?, updated_by = ? + WHERE id = 1""", + [now, updated_by], + ) diff --git a/tests/test_welcome_template_repo.py b/tests/test_welcome_template_repo.py new file mode 100644 index 0000000..89d41c7 --- /dev/null +++ b/tests/test_welcome_template_repo.py @@ -0,0 +1,40 @@ +"""Unit tests for WelcomeTemplateRepository.""" + +import duckdb +import pytest + +from src.db import _ensure_schema +from src.repositories.welcome_template import WelcomeTemplateRepository + + +@pytest.fixture +def conn(tmp_path): + db_path = tmp_path / "system.duckdb" + c = duckdb.connect(str(db_path)) + _ensure_schema(c) + yield c + c.close() + + +def test_get_returns_none_on_fresh_install(conn): + repo = WelcomeTemplateRepository(conn) + row = repo.get() + assert row is not None + assert row["content"] is None # default sentinel + + +def test_set_stores_content(conn): + repo = WelcomeTemplateRepository(conn) + repo.set("Hello {{ instance.name }}", updated_by="admin@example.com") + row = repo.get() + assert row["content"] == "Hello {{ instance.name }}" + assert row["updated_by"] == "admin@example.com" + assert row["updated_at"] is not None + + +def test_reset_clears_content(conn): + repo = WelcomeTemplateRepository(conn) + repo.set("custom", updated_by="admin@example.com") + repo.reset(updated_by="admin@example.com") + row = repo.get() + assert row["content"] is None