feat: phases 77–80 — navbar view refresh, static background, API tokens, history suggestion chips
Build and Push Containers / build-and-push-app (push) Successful in 1m45s
Build and Push Containers / build-and-push-db (push) Successful in 13s

Single consolidated commit for four completed, validated phases (77, 78,
79, 80). The pipeline run left all work uncommitted because the harness
commits only with PHASE_COMMIT=1 while child executors are forbidden from
committing; the phases themselves all passed validation and moved to
.agents/phases/complete/.

Phase 77 — navbar view refresh
- router.js dispatches bor:view-refresh on re-show / active re-click /
  popstate (gated on wasMounted; first show and boot exempt)
- History / RAG / Sources / Tuning re-fetch on refresh (admin branch);
  Chat deliberately excluded (stream survival)
- History "Refresh" button (admin-only, in-flight disable + status line)
- New story suite tests/e2e/test_navbar_refresh.py (7 tests)

Phase 78 — static background
- Removed the animated glow layers; static 44px grid over the flat --bg
  canvas; default and reduced-motion renders byte-identical
- Updated background/theme E2E suites; removed bg-glow test pins

Phase 79 — API tokens
- api_tokens model + migration 0012; hash-only token service
- Admin tokens API + Tokens admin view; POST /api/token-auth;
  live-revoking require_user on chat / suggestions / document content
- Frontend token gate with localStorage cache; anonymous E2E suites
  migrated to token login
- New story suite tests/e2e/test_api_tokens.py (9 tests)

Phase 80 — history suggestion chips
- last_questions() endpoint with SEED fallback; startNewChat() refetch
- Seed-semantics docs (config.py, .env.example, README)
- Integration state matrix + E2E suite rewritten to the 4 chip states

Also included: phase-76 report artifacts and the repo restore-test-db
skill (previously untracked), scripts/* ruff fixes from phase 77.

Final gate state (phase 80 final pass, covers everything above):
- uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99%
- uv run ruff check . && uv run pyright → clean, 0 errors
- Per-phase story E2E suites green in isolation
This commit is contained in:
2026-09-07 12:39:01 -04:00
parent 495d042a98
commit 7fce6572d0
215 changed files with 10142 additions and 1643 deletions
+136 -3
View File
@@ -1,11 +1,17 @@
"""Unit tests: single-admin auth (phase 16; A10 revised).
"""Unit tests: auth (phase 16 single-admin; phase 79 token users).
Covers the config gate (fail-loud, including via ``create_app``), the
constant-time password check, the ``require_admin`` dependency, the
whoami payload shape, and the sign_in/sign_out session semantics.
phase-79 ``require_user`` matrix (admin pass, live token pass, revoked /
missing-row 401 + session keys popped, anonymous 401, admin+user
coexistence), the three-role whoami payload shape, and the
sign_in/sign_out session semantics.
"""
from __future__ import annotations
import uuid
from datetime import UTC, datetime
import pytest
from fastapi import HTTPException
from starlette.middleware.sessions import Session
@@ -18,9 +24,11 @@ from app.core.auth import (
check_password,
ensure_admin_configured,
require_admin,
require_user,
sign_in,
sign_out,
)
from app.models import ApiToken
from app.schemas import WhoamiResponse
@@ -122,7 +130,117 @@ def test_require_admin_403s_anonymous(session: dict) -> None:
assert exc.value.detail == "admin only"
# ---------- whoami payload shape ----------
# ---------- require_user (phase 79: admin OR live token, else 401) ----------
class _FakeTokenResult:
"""``execute()`` result for the fake db: ``.scalars().first()``
yields the one fixed row (or ``None``)."""
def __init__(self, row: ApiToken | None) -> None:
self._row = row
def scalars(self) -> _FakeTokenResult:
return self
def first(self) -> ApiToken | None:
return self._row
class _FakeTokenDb:
"""Session stand-in for ``require_user``'s PK lookup: counts the
queries it is given and always returns the one fixed row — enough to
pin the matrix without a database (the admin path must NOT query at
all)."""
def __init__(self, row: ApiToken | None) -> None:
self._row = row
self.queries = 0
def execute(self, _stmt: object) -> _FakeTokenResult:
self.queries += 1
return _FakeTokenResult(self._row)
def _token_row(**kwargs: object) -> ApiToken:
base: dict[str, object] = {
"id": uuid.uuid4(),
"label": "alice",
"token_hash": "0" * 64,
"created_at": datetime.now(UTC),
}
base.update(kwargs)
return ApiToken(**base) # pyright: ignore[reportCallIssue]
def test_require_user_admin_session_passes_without_any_db_lookup() -> None:
"""An admin ALWAYS passes — token state irrelevant, no row fetched
(the admin+user coexistence case: admin wins outright)."""
db = _FakeTokenDb(None) # would be a dead row if it were ever consulted
require_user(
_request_with_session(
admin=True, user=True, user_token_id=str(uuid.uuid4())
),
db, # pyright: ignore[reportArgumentType]
) # no exception
assert db.queries == 0
def test_require_user_active_token_session_passes() -> None:
row = _token_row()
db = _FakeTokenDb(row)
require_user(
_request_with_session(user=True, user_token_id=str(row.id)),
db, # pyright: ignore[reportArgumentType]
) # no exception
assert db.queries == 1 # the live PK lookup ran
@pytest.mark.parametrize(
("row", "token_id"),
[
(None, str(uuid.uuid4())), # the row is gone (deleted out-of-band)
(_token_row(revoked_at=datetime.now(UTC)), str(uuid.uuid4())), # revoked
(None, "not-a-uuid"), # corrupt session — no valid row id at all
],
ids=["missing-row", "revoked", "corrupt-token-id"],
)
def test_require_user_dead_token_session_401s_and_pops_both_keys(
row: ApiToken | None, token_id: str
) -> None:
"""Row missing / revoked / unresolvable → 401 ``authentication
required`` AND the dead session is dropped NOW (both user keys
popped, so the next whoami is anonymous)."""
request = _request_with_session(user=True, user_token_id=token_id)
with pytest.raises(HTTPException) as exc:
require_user(request, _FakeTokenDb(row)) # pyright: ignore[reportArgumentType]
assert exc.value.status_code == 401
assert exc.value.detail == "authentication required"
assert "user" not in request.session
assert "user_token_id" not in request.session
def test_require_user_anonymous_401s() -> None:
with pytest.raises(HTTPException) as exc:
require_user(
_request_with_session(), _FakeTokenDb(None) # pyright: ignore[reportArgumentType]
)
assert exc.value.status_code == 401
assert exc.value.detail == "authentication required"
def test_require_user_user_key_without_token_id_401s_and_pops() -> None:
"""A ``user`` key with no ``user_token_id`` at all is a dead session
too — same 401, both keys dropped."""
request = _request_with_session(user=True)
with pytest.raises(HTTPException) as exc:
require_user(request, _FakeTokenDb(None)) # pyright: ignore[reportArgumentType]
assert exc.value.status_code == 401
assert exc.value.detail == "authentication required"
assert "user" not in request.session
# ---------- whoami payload shape (three roles, phase 79) ----------
def test_whoami_anonymous_payload() -> None:
@@ -136,6 +254,21 @@ def test_whoami_admin_payload() -> None:
assert body == WhoamiResponse(authenticated=True, role="admin")
def test_whoami_token_user_payload() -> None:
body = whoami(_request_with_session(user=True, user_token_id=str(uuid.uuid4())))
assert body == WhoamiResponse(authenticated=True, role="user")
def test_whoami_admin_wins_when_both_roles_are_set() -> None:
"""Coexistence is deliberate: a browser holding BOTH an admin and a
token session reports admin (the UI keys off role, the admin surface
stays open)."""
body = whoami(
_request_with_session(admin=True, user=True, user_token_id=str(uuid.uuid4()))
)
assert body == WhoamiResponse(authenticated=True, role="admin")
# ---------- sign_in / sign_out session semantics ----------