Files
brain-of-reese/tests/unit/test_models.py
T
ducoterra df91c6316c
Build and Push Containers / build-and-push-app (push) Successful in 1m47s
Build and Push Containers / build-and-push-db (push) Successful in 11s
phase: 92_theme_save_and_coverage
**Phase 92 final verification pass — all green.** This pass re-verified the completed tasks (all 5 task files already in `complete/`) against every completion criterion; no defects found, nothing to fix.

- Verified: 9th identity var `grid_line` end-to-end (migration `0015` at head, model/`theming.py`/schemas/API, 422 + built-in→NULL tests present); `styles.css` zero hardcoded literals outside `:root` + derived `--brand-*` vars; 9th picker in theme form; wordmark themed; `theme.js` save/reset/re-show/mount live-sync; dedicated E2E suite + phase-91 suite updated.
- `uv run pytest --cov=app --cov-report=term-missing` → **1845 passed, exit 0, TOTAL 99%** (>90%)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings
- `uv run pytest tests/e2e/test_theme_save_and_coverage.py -v --no-cov` → **3 passed** (save-live, reset-live, whole-site)
- `uv run pytest tests/e2e/test_admin_theme_tab.py -v --no-cov` → **5 passed**
- Criteria: (1) Save/Reset repaint open page, no nav, SPA-nav survives, pre-paint intact ✅; (2) both `rg` gates green (only `:root` + documented `#fff` Stop label; zero SVG hex attrs), grid/selection/hovers/wash/wordmark E2E-proven ✅; (3) no-op contract live-checked: row-less `/` = no tag + exact A1 CSP, grid-only row = 9-var tag in `COLOR_FIELDS` order + sha256 CSP, with-row ≡ row-less bytes ✅; (4) full suite/coverage/lint/both E2E ✅; (5) commit left to the harness per instructions.
- Deviations (previously made, probe-verified, kept): live repaint uses CSSOM `<html>` overrides because Chromium blocks `<style>` textContent mutations under the locked sha256-only CSP (tag text still mirrors the next load; `<html>` style exact-saved after Save, empty after Reset); wordmark themed via 3 `.brand-mark` CSS rules instead of inline styles (task 03's inline attrs were CSP-blocked — fixed during task 04).
- Next pending phase: none — `todo/` contains only `92_theme_save_and_coverage`.
2026-09-10 00:23:08 -04:00

147 lines
6.3 KiB
Python

"""Unit tests: SQLAlchemy models register the pgvector schema on the metadata.
Importing :mod:`app.models` is what Alembic's ``env.py`` and the runtime rely
on; these tests lock the table/column contract (PLAN §5) without a live DB.
"""
from __future__ import annotations
from pgvector.sqlalchemy import Vector
from sqlalchemy import TextClause, UniqueConstraint
from sqlalchemy.sql.schema import DefaultClause
import app.models # noqa: F401 (import registers all tables on Base.metadata)
from app.db import Base
def test_all_tables_registered() -> None:
tables = Base.metadata.tables
assert "documents" in tables
assert "chunks" in tables
assert "query_log" in tables
assert "ui_settings" in tables # phase 91: the single-row UI settings
def test_ui_settings_single_row_nullable_contract() -> None:
"""Phase 91 (9 identity colors after phase 92, task 01): the
single-row UI settings table — Integer PK ``id`` with the
Python-side ``default=1`` (the row is always id 1), the 3 strings
VARCHAR(300) and the 9 identity colors VARCHAR(7), ALL nullable
(NULL = default — B1: env value for the strings, the built-in
palette for the colors)."""
settings_table = Base.metadata.tables["ui_settings"]
assert set(settings_table.c.keys()) == {
"id", "app_name", "input_placeholder", "footer_text",
"bg", "surface", "ink", "ink_soft", "line", "grid_line",
"brand", "brand_soft", "brand_ink",
}
pk = settings_table.c["id"]
assert pk.primary_key is True, "ui_settings.id must be the PK"
assert pk.default is not None, "id needs the Python-side default=1"
for name in ("app_name", "input_placeholder", "footer_text"):
col = settings_table.c[name]
assert col.nullable is True, f"{name} must be NULL (env default)"
assert getattr(col.type, "length", None) == 300, f"{name} must be String(300)"
for name in ("bg", "surface", "ink", "ink_soft", "line", "grid_line",
"brand", "brand_soft", "brand_ink"):
col = settings_table.c[name]
assert col.nullable is True, f"{name} must be NULL (the built-in)"
assert getattr(col.type, "length", None) == 7, f"{name} must be String(7) — #rrggbb"
def test_chunks_embedding_is_vector_768() -> None:
chunks = Base.metadata.tables["chunks"]
col = chunks.c["embedding"]
assert isinstance(col.type, Vector)
assert col.type.dim == 768
# Embeddings are two-phase: inserted first, embedded later.
assert col.nullable is True
def test_chunks_reference_documents_cascade() -> None:
chunks = Base.metadata.tables["chunks"]
fkc = list(chunks.foreign_key_constraints)[0]
assert fkc.elements[0].column.table.name == "documents"
assert fkc.ondelete == "CASCADE"
def test_documents_unique_source_path() -> None:
documents = Base.metadata.tables["documents"]
uq = [
c
for c in documents.constraints
if isinstance(c, UniqueConstraint)
and {col.name for col in c.columns} == {"source", "path"}
]
assert uq, "documents must be unique on (source, path) — the upsert key"
def test_doc_drafts_token_is_unique_not_null() -> None:
"""Phase 59: the draft's URL credential — an unguessable uuid4,
UNIQUE + NOT NULL (no "un-drafted" state, unlike the NULLable
``saved_chats.share_token``)."""
drafts = Base.metadata.tables["doc_drafts"]
token = drafts.c["token"]
assert token.nullable is False, "doc_drafts.token must be NOT NULL"
uq = [
c
for c in drafts.constraints
if isinstance(c, UniqueConstraint)
and {col.name for col in c.columns} == {"token"}
]
assert uq, "doc_drafts must be unique on (token) — the URL credential"
def test_git_sources_ignore_paths_column_contract() -> None:
"""Phase 89: every source row carries its ignore list — JSONB, NOT
NULL, server default ``'[]'`` (a pre-phase-89 row reads ``[]``, so
every existing source imports exactly as before)."""
sources = Base.metadata.tables["git_sources"]
assert "ignore_paths" in sources.c, (
"git_sources must have the ignore_paths column (phase 89)"
)
col = sources.c["ignore_paths"]
assert col.nullable is False, "git_sources.ignore_paths must be NOT NULL"
sd = col.server_default
assert isinstance(sd, DefaultClause), "ignore_paths needs a server default"
assert isinstance(sd.arg, TextClause), (
"the server default must be the literal SQL text '[]'"
)
assert sd.arg.text == "'[]'", "ignore_paths server default must be '[]'"
def test_git_source_python_default_empty_list() -> None:
"""A freshly constructed row (no DB) resolves to an empty ignore
list via the Python-side default (``default=list``) — the ORM
INSERT-time default, so an ORM insert that omits the column inserts
``[]`` rather than NULL (the server default ``'[]'`` independently
covers non-ORM inserts)."""
from app.models import GitSource
row = GitSource(url="https://example.com/r.git", kind="git")
col = row.__table__.c["ignore_paths"]
assert col.default is not None, (
"ignore_paths needs a Python-side (INSERT-time) default"
)
# Invoked with the (unused) execution context at INSERT time — a
# freshly constructed row that omits the kwarg stores [] rather
# than NULL (the real-DB behaviour is pinned against the dev DB
# by the git-sources API integration tests).
assert col.default.arg(None) == [], "the default must resolve to []"
def test_doc_drafts_column_contract() -> None:
"""Phase 59: the editable triple (title/path/body) + status +
timestamps are NOT NULL; ``branch`` / ``commit_sha`` are NULL
until the push endpoint records them."""
drafts = Base.metadata.tables["doc_drafts"]
assert set(drafts.c.keys()) == {
"id", "token", "title", "path", "body", "status",
"branch", "commit_sha", "created_at", "updated_at",
}
for name in ("title", "path", "body", "status", "created_at", "updated_at"):
assert drafts.c[name].nullable is False, f"{name} must be NOT NULL"
for name in ("branch", "commit_sha"):
assert drafts.c[name].nullable is True, f"{name} must be NULL until pushed"
assert drafts.c["status"].default is not None, "status needs an ORM default (draft)"
assert drafts.c["token"].default is not None, "token needs an ORM default (uuid4)"