feat: phase 2 content model + cache — SQLite schema, markdown, TTL
Stand up the full SQLite content layer: all 7 tables from the authoritative schema with WAL + foreign-keys enforced per-connection, entity dataclasses plus row mappers, hand-rolled versioned migrations tracked in schema_migrations, and an idempotent Python seed (system user + welcome post + About page). Add a Markdown->HTML service using markdown-it-py with a strict bleach allowlist (tables intentionally omitted on both sides). Add a typed in-process TTLCache[K,V] and wire it into real DB-backed PostService and PageService, both exposing invalidate_all() for Phase 4 admin writes. Rewire / and /about to read from the DB; homepage renders the seeded welcome post, About renders page.title + sanitized body_html_cached. Update the Phase 1 route tests accordingly. Mark Phase 2 complete in docs/ROADMAP.md.
This commit is contained in:
56
tests/conftest.py
Normal file
56
tests/conftest.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Shared pytest fixtures for the ``chicken_babies_site`` suite.
|
||||
|
||||
Key fixtures:
|
||||
|
||||
- ``db_engine``: a session-scoped SQLAlchemy engine pointed at a
|
||||
temp-file SQLite database. Migrations + seed run once per test
|
||||
session. Per the CLAUDE.md mandate, tests do NOT mock the DB —
|
||||
they use a real SQLite file so behavior matches production.
|
||||
- ``clean_db_engine``: a function-scoped engine with migrations
|
||||
applied but seed NOT run, for tests that need to exercise the
|
||||
first-boot path.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterator
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import Engine
|
||||
|
||||
from app.db import build_engine, run_migrations
|
||||
from app.models.seed import run_seed
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def db_engine(tmp_path_factory: pytest.TempPathFactory) -> Iterator[Engine]:
|
||||
"""Return a migrated + seeded SQLite engine shared across the session.
|
||||
|
||||
Uses a real on-disk file (NOT ``:memory:``) because the CLAUDE.md
|
||||
project rules forbid mocking the DB in auth / magic-link tests,
|
||||
and doing the same here keeps the behavior identical to
|
||||
production.
|
||||
"""
|
||||
db_path: Path = tmp_path_factory.mktemp("db") / "test.db"
|
||||
engine = build_engine(f"sqlite:///{db_path}")
|
||||
run_migrations(engine)
|
||||
run_seed(engine)
|
||||
yield engine
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_db_engine(tmp_path: Path) -> Iterator[Engine]:
|
||||
"""Return a fresh engine with tables created but NO seed data.
|
||||
|
||||
Function-scoped so each test that uses it starts with a virgin
|
||||
database — useful for asserting first-run seed behavior and
|
||||
migration idempotency without contaminating the session-scoped
|
||||
``db_engine``.
|
||||
"""
|
||||
db_path = tmp_path / "clean.db"
|
||||
engine = build_engine(f"sqlite:///{db_path}")
|
||||
run_migrations(engine)
|
||||
yield engine
|
||||
engine.dispose()
|
||||
Reference in New Issue
Block a user