Files
brain-of-reese/tests/unit/test_admin_settings_api.py
T
ducoterra a86caec821
Build and Push Containers / build-and-push-app (push) Successful in 1m41s
Build and Push Containers / build-and-push-db (push) Successful in 11s
phase: 173_docs_push_creds_split
All gates green. Final report:

**Phase 173 — final verification pass (all tasks already complete in `complete/`)**

- Verified the full implementation is in the working tree: `docs_user`/`docs_token` settings + catalogue rows (task 01), `docs_repo` PUT 422 guard + `docs_token` write-only pin (task 02), `run_git(env)` + `push_document(user, token)` credential env (task 03), `settings_migrate.py` wired into `create_app()` (task 04), E2E suite (task 05).
- **Completion criteria:**
  1. GET `/api/admin/settings` lists `docs_user` (plain) + `docs_token` (secret, `value=null`), token never in any body — ✅ `test_docs_token_is_write_only_masked_end_to_end` + E2E sentinel-absent-from-DOM
  2. PUT `docs_repo` with `://user:pass@` → 422 naming the fields; clean URL passes — ✅ integration + E2E UI-side
  3. Token set → `GIT_CURL_USERNAME`/`GIT_CURL_PASSWORD`/`GIT_TERMINAL_PROMPT=0` on every git step; empty → `env=None` — ✅ mocked-git unit tests
  4. Legacy env split once at startup, idempotent, one INFO line, `.env` untouched — ✅ `test_settings_migrate.py` (4 shapes) + `test_docs_push_migrate.py`
  5. Full suite green, coverage >90%, lint clean — ✅
  6. E2E isolated: `test_docs_push_creds.py` 4/4; regression set (panel + layout + response_to_docs) 21/21 — ✅
  7. Commit + phase move — harness's job (per instructions, not mine).
  8. README/AGENTS consistent — ✅ (below)
- Tests/lint: `uv run pytest` exit 0 (4112 passed) · `uv run pytest --cov=app --cov-report=term-missing` TOTAL 99% · `uv run ruff check .` clean · `uv run pyright` 0 errors
- Docs: README.md (incl. Docs-push config section, guard/migration paragraph, `.env.example` block) and AGENTS.md verified consistent in full; fixed two stale docstrings in `app/config.py` + `app/core/settings_catalog.py` (`is_set` → the API's actual `has_value` field).
- No live-infrastructure changes (no live step in this phase). No next pending phase — `todo/` held only 173.
2026-10-10 16:47:39 -04:00

224 lines
10 KiB
Python

"""Unit: the admin settings router's pure helpers (phase 168, task 04)
— the env-native stringification (``_display``), the control-kind
derivation (``_field_type``), the ``_item`` builder's masked and
default branches, and the LOCKED A1 static pins: the module never
opens a file (``.env`` is never written), never shells out and never
``eval``s a value.
The DB-driven behavior (the four routes, the candidate-set validation,
the auth matrix, the phase-173 ``docs_repo`` guard on the PUT path)
is pinned in ``tests/integration/test_admin_settings_api.py``.
"""
from __future__ import annotations
import pathlib
import re
import pytest
from fastapi import HTTPException
from app.api.admin_settings import (
_display,
_field_type,
_item,
_refuse_embedded_credentials,
)
from app.config import Settings
from app.core.settings_overlay import LIST_FIELDS
def test_display_bools_are_the_env_spelling() -> None:
"""bool → ``"true"``/``"false"`` — checked BEFORE int (``isinstance(True, int)``
is ``True`` — a bool field must never stringify through ``str()``'s
``"True"``)."""
assert _display("stream_thinking", True) == "true"
assert _display("stream_thinking", False) == "false"
# An int value for an int field is the plain str (no bool confusion).
assert _display("llm_retries", 3) == "3"
def test_display_list_field_is_json() -> None:
"""The one ``list[str]`` field (``suggestions``) stringifies as the
JSON array the env spelling would carry (K2: stored in the same
JSON form, pydantic parses it exactly like the env var)."""
assert set(LIST_FIELDS) == {"suggestions"}
assert _display("suggestions", ["a", "b"]) == '["a", "b"]'
assert _display("suggestions", []) == "[]"
def test_display_scalars_are_plain_strings() -> None:
"""Everything else is ``str()`` — floats, ints, strings, in the
env-native form (what ``app_settings`` stores, D1)."""
assert _display("relevance_threshold", 0.55) == "0.55"
assert _display("llm_timeout", 7.0) == "7.0"
assert _display("app_name", "Brain of Reese") == "Brain of Reese"
assert _display("import_extensions", "md,txt") == "md,txt"
def test_field_type_covers_all_kinds() -> None:
"""The panel control kind per annotation: ``bool`` before ``int``
(a bool annotation is never misread as int), the one list field is
``json``, the rest fall through to their annotation."""
assert _field_type("stream_thinking") == "bool"
assert _field_type("llm_retries") == "int"
assert _field_type("relevance_threshold") == "float"
assert _field_type("app_name") == "str"
assert _field_type("suggestions") == "json"
# Every field resolves to one of the five wire kinds.
kinds = {"int", "float", "bool", "str", "json"}
for key in Settings.model_fields:
assert _field_type(key) in kinds
def test_item_masks_secret_fields() -> None:
"""LOCKED A4: a secret field's item is write-only on the wire —
``masked=True``, ``value=None``, ``db_value=None``,
``default=""`` (the code default is never shown for a secret),
``has_value`` = is a non-empty value in effect at all (the test
env sets a non-empty admin password / session secret)."""
effective = Settings()
assert effective.admin_password # tests/conftest.py pins it non-empty
item = _item("admin_password", {}, effective)
assert item.masked is True
assert item.value is None
assert item.db_value is None
assert item.default == ""
assert item.has_value is True
assert item.secret is True
assert item.key == "admin_password"
assert item.env_var == "BOR_ADMIN_PASSWORD"
# A stored row for a secret field never exposes the text either.
# (The effective value is forced via an init kwarg — the operator's
# local `.env` may legitimately carry a key, so the test does not
# assume a specific effective value.)
item = _item("llm_api_key", {"llm_api_key": "sk-x"}, Settings(llm_api_key="sk-x"))
assert item.masked is True
assert item.value is None
assert item.db_value is None
assert item.has_value is True
assert item.has_override is True
item = _item("llm_api_key", {}, Settings(llm_api_key=""))
assert item.masked is True
assert item.value is None
assert item.db_value is None
assert item.has_value is False # no value in effect at all
def test_item_reports_env_default_and_db_sources(
monkeypatch: pytest.MonkeyPatch
) -> None:
"""The ``source`` badge: ``db`` for an in-effect overridable row,
``env`` when the ``BOR_*`` var is in the process environment,
``default`` otherwise — and a non-overridable row is NEVER an
override (LOCKED A3: the overlay drops it, so a hand-inserted
``database_url`` row is not in effect)."""
import os
effective = Settings()
# "env": tests/conftest.py sets BOR_RELEVANCE_THRESHOLD.
assert os.environ.get("BOR_RELEVANCE_THRESHOLD") is not None
item = _item("relevance_threshold", {}, effective)
assert item.source == "env"
# The value is the EFFECTIVE float stringified ("0.30" → 0.3 → "0.3").
assert item.value == str(float(os.environ["BOR_RELEVANCE_THRESHOLD"]))
assert item.has_override is False
# "default": no BOR_DOCS_WORK_DIR in the test environment.
assert os.environ.get("BOR_DOCS_WORK_DIR") is None
item = _item("docs_work_dir", {}, effective)
assert item.source == "default"
assert item.value == effective.docs_work_dir
# "db": the row is in effect only while the overlay switch is ON
# (the conftest pin is 0 — flip it for the pin, it restores after).
monkeypatch.setenv("BOR_SETTINGS_DB", "1")
item = _item("relevance_threshold", {"relevance_threshold": "0.9"}, effective)
assert item.source == "db"
assert item.has_override is True
# Non-overridable: a stored row is not an override (LOCKED A3) —
# even with the switch on.
item = _item("database_url", {"database_url": "postgresql://x"}, effective)
assert item.has_override is False
assert item.masked is True
# The code default stringifies (non-secret fields).
item = _item("llm_retries", {}, effective)
assert item.default == "3"
assert item.value == "3"
assert item.db_value is None
def test_item_db_value_is_the_stored_string() -> None:
"""A stored non-secret override exposes ``db_value`` (the stored
string, D1) and ``has_override`` — even while the overlay switch
is OFF (the row is stored but not in effect: the badge is
env/default, the panel's ``overlay_enabled`` flag explains the
rest)."""
effective = Settings()
item = _item("llm_retries", {"llm_retries": "7"}, effective)
assert item.has_override is True
assert item.db_value == "7"
assert item.source == "default" # switch off (the conftest pin): not in effect
def test_module_never_writes_files_or_shells_out() -> None:
"""LOCKED A1 + the module's third invariant (the unit pin): the
router never opens a file (``.env`` is never written by any code
path), never shells out and never ``eval``s a value. (The docstring
legitimately mentions ``.env`` — the pins are on the CALLS, the
``test_settings_overlay`` house style.)"""
source = (
pathlib.Path(__file__).resolve().parents[2]
/ "app"
/ "api"
/ "admin_settings.py"
).read_text()
assert re.search(r"\bopen\s*\(", source) is None, (
"app/api/admin_settings.py must never open() a file (LOCKED A1: "
".env is never written)"
)
assert "subprocess" not in source
assert "os.system" not in source
assert re.search(r"\beval\s*\(", source) is None
assert "import shutil" not in source and "shutil." not in source
def test_refuse_embedded_credentials_fires_only_for_docs_repo() -> None:
"""Phase 173 (task 02, C4): the guard 422s for ``docs_repo`` with a
``://<user>:<pass>@`` segment — the detail NAMES the Docs user /
Docs token fields — and passes everything else: other keys (only
``docs_repo`` is guarded), clean ``https://`` URLs, local paths,
``ssh://`` remotes, and a ``user:``-looking subdomain (the
trailing ``@`` requirement keeps it from false-positiving).
An empty user OR an empty pass (``://:pass@`` / ``://u:@``) does
NOT match — the ``+`` quantifiers require both to be non-empty.
"""
# The 422: names the replacement fields.
with pytest.raises(HTTPException) as excinfo:
_refuse_embedded_credentials(
"docs_repo", "https://u:pass@gitea.example.com/o/r.git"
)
assert excinfo.value.status_code == 422
assert "Docs user" in excinfo.value.detail
assert "Docs token" in excinfo.value.detail
# Also a ``git://``-style URL with embedded credentials (the regex
# is scheme-agnostic: it keys on ``://<user>:<pass>@``).
with pytest.raises(HTTPException):
_refuse_embedded_credentials("docs_repo", "git://u:tok@host.example.com/r.git")
# Other keys: never guarded (``docs_user`` may hold any plain text).
_refuse_embedded_credentials("docs_user", "https://u:pass@host.example.com/r.git")
_refuse_embedded_credentials("llm_api_key", "://u:pass@")
# Clean shapes pass: https URL, local path, ssh remote.
_refuse_embedded_credentials("docs_repo", "https://gitea.example.com/o/r.git")
_refuse_embedded_credentials("docs_repo", "/srv/docs-repo")
_refuse_embedded_credentials("docs_repo", "ssh://git@gitea.example.com/o/r.git")
# A subdomain that looks like ``user:`` (no trailing ``@``).
_refuse_embedded_credentials("docs_repo", "https://sub.domain.example.com/r.git")
# Empty user OR empty pass: the ``+`` quantifiers require both
# non-empty — no match (a bare ``://`` is not the leak class).
_refuse_embedded_credentials("docs_repo", "https://:pass@host.example.com/r.git")
_refuse_embedded_credentials("docs_repo", "https://u:@host.example.com/r.git")
# A ``:`` after the ``@`` segment (a URL with no credentials at
# all) and an empty value.
_refuse_embedded_credentials("docs_repo", "https://host.example.com/r.git:3000")
_refuse_embedded_credentials("docs_repo", "")