**Phase 168 — Admin Settings panel — final verification: PASS** 1. **Implemented/verified:** `/settings.html` (admin-only) with the 75-field catalogue (`app/core/settings_catalog.py`), DB-over-env overrides in Postgres (`app_settings`, migration 0028 at head, `settings_overlay.py` resolver, `app/api/admin_settings.py` — 4 admin-only routes), live-apply on save with no restart + restart-required badges, Reset/Clear all, write-only secrets, `.env`-never-written, `BOR_SETTINGS_DB=0` kill switch. 2. **Phase E2E: 12/12** — run with `E2E_APP_PORT=8200` (ports 8123/8124 are held by an unrelated "undercooked" project; no process killed, no code change — the documented env override). 3. **Regression set: all 7 suites green in isolation** (theme tab, API tokens, configurable brand, global tuning, admin auth, cache busting, wide desktop column — 38 tests). 4. **Full suite: unit+integration exit 0 (4066 passed), app/ coverage 99%** (gate >90%); **ruff clean, pyright 0 errors / 0 warnings.** 5. **Log contract (rule 10) verified live:** per-turn + per-response byte-budget lines present; settings write lines carry key + action, **never the value**. 6. **Byte-budget audit: all endpoints under the 262,144 B budget**; settings catalogue 23,513 B (gzip 3,326). 7. **Defect fixed (one):** criterion 5 had no integration pin — added `test_bad_row_inserted_directly_never_bricks_boot` to `tests/integration/test_admin_settings_api.py` (malformed row inserted directly → fresh `create_app()` boots, row skipped with logged reason, rest of table applies, `/api/health` 200) — 12/12 green. 8. **Docs pass:** `README.md` and `AGENTS.md` reviewed in full — both consistent with the as-built feature (Settings page table, Settings section, Configuration-reference note, `.env.example` block); **no changes required**. 9. **Cleanup:** audit app killed (by PID); `app_settings` left empty (0 rows); all changes left in the working tree (no commit, per protocol). 10. **Completion criteria:** all 7 checkboxes verified green (criterion 7's commit is the harness's). 11. **Next pending phase:** `170_edit_bubble_sizing`.
175 lines
7.5 KiB
Python
175 lines
7.5 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) is pinned in
|
|
``tests/integration/test_admin_settings_api.py``.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import re
|
|
|
|
import pytest
|
|
|
|
from app.api.admin_settings import _display, _field_type, _item
|
|
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
|