Everything is verified and green. Final report: **Phase 34 final verification pass — complete** - Verified all 6 completed tasks' work against the live fleet and hermetic suite: `llm:radiance` job registered, `llm_engines()` registry drives 404 gate / history defaults / `/api/config` (additive, byte-identical gufo/vllm keys), radiance field map + `/stats` sidecar + 7-position spec curve, `/llm` strip/curve/rows. - **Defect found & fixed (pre-existing, task 05):** the vLLM-only sentence "vLLM exposes no per-slot JSON…" rendered on the **radiance** panel (`slotSection`'s non-gufo branch). Fix: vLLM line gated to `engine === 'vllm'`; radiance's panel slot section renders nothing (its session home is the strip) — `page-llm.js` + the served-byte pin in `tests/test_api.py` (now asserts the radiance branch is empty and the sentence can't leak). Browser-verified; re-shot `data/logs/34-llm-radiance-rest.png` (the 4.5-min wait found no in-flight request, so the busy shot remains the earlier pass's — its strip rows still stand; only the panel text predates the fix). - Tests: `uv run pytest` → **2031 passed, 19 skipped** (100%); `uv run pytest --cov` → **98.73%** (floor 94%; config 100%, llm 99%, collectors 98%, api 99%); `uv run ruff check src tests agent && uv run pyright` → clean; `node --check` OK; `uv pip list --outdated` → empty. - `uv run pytest -m live --live` → **13 passed, 2 skipped** (vLLM unset — correct; live-write opt-in — correct); the read-only radiance test passed. - Completion criteria: (1) engines `gufo`+`radiance`, `fleet.engines_live` 2, `config.llm.radiance.enabled` true — **pass (live curl)**; (2) unset → 404 no-dial + no job — **pass (spy-pinned, green)**; (3) fixtures present, both families (79 `radiance:` / 198 `vllm:` lines), drift pin — **pass**; (4) panel/curve/rows — **pass (re-shot rest screenshot)**; (5) live radiance test — **pass**; (6) suite/coverage/lint/`bash .agents/validate.sh` (exit 0) — **pass**; (7) docs — below; (8) gufo/vLLM behaviour unchanged — **pass (existing pins untouched)**. - Documentation: **README.md** full review — env table, LLM-engines section, architecture diagram, capture-script and `/llm` section all name radiance correctly; no inconsistencies found, no changes needed. **AGENTS.md** — the three task-06 fixes (fleet line, route row, module bullets) are in place and all other statements match reality; no changes needed. - Dev server healthy (`./scripts/dev-server.sh status`: all sources ok, `llm:radiance` answering); all changes left in the working tree for the harness. - Next pending phase: none — 34 was the last phase in `todo/`.
492 lines
22 KiB
Python
492 lines
22 KiB
Python
"""Configuration: env-only, no defaults that point at somebody else's machine."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
from dataclasses import FrozenInstanceError, fields
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from dashboards import config
|
|
from dashboards.auth import PBKDF2_ITERATIONS
|
|
from dashboards.collectors import llm_jobs
|
|
from dashboards.config import Settings, parse_targets
|
|
from dashboards.rates import RateEstimator
|
|
|
|
|
|
def test_parses_whitespace_and_comma_separated_targets() -> None:
|
|
targets, problems = parse_targets("a=http://h1:9120 b=http://h2:9120,c=http://h3:9120")
|
|
assert [t.name for t in targets] == ["a", "b", "c"]
|
|
assert [t.url for t in targets] == ["http://h1:9120", "http://h2:9120", "http://h3:9120"]
|
|
assert problems == []
|
|
|
|
|
|
def test_trailing_slash_is_stripped_so_urls_do_not_double_up() -> None:
|
|
targets, _ = parse_targets("a=http://h1:9120/")
|
|
assert targets[0].url == "http://h1:9120"
|
|
|
|
|
|
def test_one_bad_entry_does_not_sink_the_others() -> None:
|
|
targets, problems = parse_targets("good=http://h:1 broken-no-equals another=ftp://h:1")
|
|
assert [t.name for t in targets] == ["good"]
|
|
assert len(problems) == 2
|
|
assert any("not name=url" in p for p in problems)
|
|
assert any("http://" in p for p in problems)
|
|
|
|
|
|
def test_empty_input_yields_no_targets_and_no_noise() -> None:
|
|
assert parse_targets("") == ([], [])
|
|
assert parse_targets(" \n ") == ([], [])
|
|
|
|
|
|
def test_token_in_url_becomes_a_bearer_header_and_stays_out_of_the_display_url() -> None:
|
|
targets, problems = parse_targets("driveripper=http://SEKRIT@driveripper:9120")
|
|
assert problems == []
|
|
target = targets[0]
|
|
assert target.headers == {"Authorization": "Bearer SEKRIT"}
|
|
assert target.url == "http://driveripper:9120", "the token must not stay in the URL"
|
|
assert target.display_url == "http://driveripper:9120"
|
|
assert "SEKRIT" not in target.display_url
|
|
|
|
|
|
@pytest.mark.parametrize("raw,shown", [
|
|
("http://SEKRIT@host:9120", "http://host:9120"),
|
|
("https://user:pass@host:8000/metrics", "https://host:8000/metrics"),
|
|
("HTTP://SEKRIT@host:1", "HTTP://host:1"),
|
|
("http://host:9120", "http://host:9120"), # clean URLs pass through untouched
|
|
("", ""),
|
|
])
|
|
def test_strip_userinfo_is_the_one_redaction_every_url_leaves_by(raw: str, shown: str) -> None:
|
|
"""`strip_userinfo` is what `/api/config` and every `UpstreamError` message use, so it has
|
|
to survive the shapes a URL actually comes in: both schemes, `user:pass@`, a path, and the
|
|
common no-credentials case where it must change nothing (invariant 6).
|
|
"""
|
|
assert config.strip_userinfo(raw) == shown
|
|
|
|
|
|
def test_target_names_are_restricted_because_they_are_db_keys() -> None:
|
|
"""Names key the history DB and appear in URLs, so they are validated, not guessed.
|
|
|
|
Whitespace separates entries, which means "Bad Name=http://h" is two entries — both
|
|
reported, because the operator needs to see exactly what was rejected.
|
|
"""
|
|
targets, problems = parse_targets("Bad=http://h:1 /=http://h:1 ok-1_2=http://h:1")
|
|
assert [t.name for t in targets] == ["ok-1_2"]
|
|
assert len(problems) == 2
|
|
assert any("Bad" in p for p in problems)
|
|
|
|
_targets, problems = parse_targets("Bad Name=http://h:1")
|
|
assert len(problems) == 2, "a space splits the entry, so both halves are rejected"
|
|
|
|
|
|
def test_settings_default_to_no_sources_configured() -> None:
|
|
settings = Settings()
|
|
assert settings.host_targets == ()
|
|
assert settings.gufo_url == "" and settings.vllm_url == ""
|
|
assert settings.nut_host == ""
|
|
assert settings.nut_enabled is False
|
|
assert settings.any_source_enabled is False
|
|
|
|
|
|
def test_the_gateway_source_names_the_ingress_host_and_its_own_cadence() -> None:
|
|
"""Phase 29: one `HOST_TARGETS` name is the gateway, and it is polled on its own clock.
|
|
|
|
`gateway_host` defaults to the fleet's real name so installing agent 1.2.0 on a host called
|
|
`gateway` is the whole configuration — but the **default is not a target**: with no
|
|
`HOST_TARGETS` entry of that name the source is unconfigured, `/api/gateway/*` 404s without
|
|
dialling, and the page says so (invariant 3). The interval sits between the system poll and
|
|
SMART because a connection count is the shape of half a minute, not of ten seconds.
|
|
"""
|
|
settings = Settings()
|
|
assert settings.gateway_host == "gateway"
|
|
assert settings.gateway_interval == 30.0
|
|
assert settings.gateway_timeout == 10.0
|
|
assert settings.gateway_enabled is False
|
|
assert settings.any_source_enabled is False
|
|
|
|
|
|
def test_gateway_enabled_is_true_only_for_a_name_that_is_a_target() -> None:
|
|
"""A name with no agent behind it is unconfigured, not broken — the `nut_enabled` shape."""
|
|
def with_targets(raw: str, name: str = "gateway") -> bool:
|
|
targets, _problems = parse_targets(raw)
|
|
return Settings(host_targets=tuple(targets), gateway_host=name).gateway_enabled
|
|
|
|
assert with_targets("gateway=https://gw.example.net:9120") is True
|
|
assert with_targets(
|
|
"deskwork=https://d.example.net:9120 gateway=https://gw.example.net:9120") is True
|
|
assert with_targets("deskwork=https://d.example.net:9120") is False
|
|
assert with_targets("router=https://gw.example.net:9120", name="router") is True
|
|
assert with_targets("Gateway=https://gw.example.net:9120") is False, ("names are lowercase")
|
|
assert with_targets("") is False
|
|
|
|
|
|
def test_the_gateway_settings_come_from_the_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""`GATEWAY_HOST=router` is how a deployment whose ingress is called something else opts in."""
|
|
monkeypatch.setenv("GATEWAY_HOST", "router")
|
|
monkeypatch.setenv("GATEWAY_INTERVAL_SECONDS", "15")
|
|
monkeypatch.setenv("GATEWAY_TIMEOUT_SECONDS", "20")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.gateway_host == "router"
|
|
assert reloaded.SETTINGS.gateway_interval == 15.0
|
|
assert reloaded.SETTINGS.gateway_timeout == 20.0
|
|
finally:
|
|
for name in ("GATEWAY_HOST", "GATEWAY_INTERVAL_SECONDS", "GATEWAY_TIMEOUT_SECONDS"):
|
|
monkeypatch.delenv(name)
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_the_gateway_write_is_off_by_default_and_budgets_are_their_own() -> None:
|
|
"""Phase 33 task 05: the flag is a *decision*, and its default is the safe one.
|
|
|
|
`GATEWAY_ALLOW_WRITES` is `NUT_ALLOW_ACTIONS`'s sibling: the same truthy rule, the same
|
|
default, and its own budget. The read's 10 s is the read's alone; the write's 35 s default
|
|
is the write's own, and the two never share a number.
|
|
"""
|
|
settings = Settings()
|
|
assert settings.gateway_allow_writes is False
|
|
assert settings.gateway_write_timeout == 35.0
|
|
assert settings.gateway_timeout == 10.0
|
|
|
|
|
|
def test_the_gateway_write_settings_come_from_the_environment(
|
|
monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""`GATEWAY_ALLOW_WRITES=1` is the only way the flag turns on, the same rule as the NUT one."""
|
|
monkeypatch.setenv("GATEWAY_ALLOW_WRITES", "1")
|
|
monkeypatch.setenv("GATEWAY_WRITE_TIMEOUT_SECONDS", "12.5")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.gateway_allow_writes is True
|
|
assert reloaded.SETTINGS.gateway_write_timeout == 12.5
|
|
finally:
|
|
for name in ("GATEWAY_ALLOW_WRITES", "GATEWAY_WRITE_TIMEOUT_SECONDS"):
|
|
monkeypatch.delenv(name)
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_the_gateway_flag_uses_the_same_truthy_rule_as_every_other_flag(
|
|
monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""The rule is `_flag`'s: the two writes are two decisions, the same shape (task 05)."""
|
|
for truthy in ("1", "true", "TRUE", "yes", "on"):
|
|
monkeypatch.setenv("GATEWAY_ALLOW_WRITES", truthy)
|
|
assert config._flag("GATEWAY_ALLOW_WRITES") is True, truthy
|
|
for falsey in ("0", "false", "", "no"):
|
|
monkeypatch.setenv("GATEWAY_ALLOW_WRITES", falsey)
|
|
assert config._flag("GATEWAY_ALLOW_WRITES") is False, falsey
|
|
|
|
|
|
def test_the_disk_source_defaults_to_a_five_minute_cadence() -> None:
|
|
"""SMART is not a per-second signal (phase 09), and the fetch waits for the agent's
|
|
own 20 s smartctl budget. Both are env-only, like every other interval."""
|
|
settings = Settings()
|
|
assert settings.disks_interval == 300.0
|
|
assert settings.disks_timeout == 30.0
|
|
|
|
|
|
def test_the_disk_cadence_is_an_env_var_like_every_other_one(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""`DISKS_INTERVAL_SECONDS=60` is how an operator watches a failing drive closely."""
|
|
monkeypatch.setenv("DISKS_INTERVAL_SECONDS", "60")
|
|
monkeypatch.setenv("DISKS_TIMEOUT_SECONDS", "45")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.disks_interval == 60.0
|
|
assert reloaded.SETTINGS.disks_timeout == 45.0
|
|
finally:
|
|
monkeypatch.delenv("DISKS_INTERVAL_SECONDS")
|
|
monkeypatch.delenv("DISKS_TIMEOUT_SECONDS")
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_there_is_one_window_knob_per_measuring_cadence() -> None:
|
|
"""Phase 23: `RATE_WINDOW_SECONDS` is the **host's** Δ window; the LLM's *is* its interval.
|
|
|
|
These are the defaults `app.build_hub` spans its two estimators with — the wiring is pinned in
|
|
`tests/test_api.py`, the payload labels in `tests/test_llm_source.py` and
|
|
`tests/test_host_source.py`, both through `tests/rate_window_contract.py`. And there is
|
|
exactly **one** `*window*` setting: the LLM window tracks `LLM_INTERVAL_SECONDS` by
|
|
construction, so it cannot drift from the cadence it belongs to, while the host's cannot
|
|
shrink to match it either (a 2 s span over a 10 s poll has nothing inside it to diff, which
|
|
`test_the_host_rates_keep_their_own_cadence_s_window` pins as `n/a`).
|
|
"""
|
|
settings = Settings()
|
|
assert settings.rate_window == 10.0
|
|
assert settings.llm_interval == 2.0
|
|
assert settings.host_interval == 10.0
|
|
window_knobs = {field.name for field in fields(Settings) if "window" in field.name}
|
|
assert window_knobs == {"rate_window"}, (
|
|
"the LLM window must stay `LLM_INTERVAL_SECONDS`: a second knob drifts from its cadence")
|
|
|
|
|
|
def test_actions_need_the_flag_and_are_dead_under_read_only() -> None:
|
|
assert Settings(nut_allow_actions=True, read_only=False).actions_allowed is True
|
|
assert Settings(nut_allow_actions=True, read_only=True).actions_allowed is False
|
|
assert Settings(nut_allow_actions=False, read_only=False).actions_allowed is False
|
|
|
|
|
|
def test_nut_host_accepts_a_nut_scheme_prefix() -> None:
|
|
assert Settings(nut_host="nut://ups-box:3493").nut_enabled is True
|
|
|
|
|
|
def test_load_reads_host_targets_from_the_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("HOST_TARGETS", "deskwork=http://tok@deskwork.example.net:9120")
|
|
settings = config.load()
|
|
assert [t.name for t in settings.host_targets] == ["deskwork"]
|
|
|
|
|
|
def test_env_driven_scalars_are_read_at_import(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""The dataclass defaults read os.environ, so a reload must pick up a changed env."""
|
|
monkeypatch.setenv("DASH_PORT", "8123")
|
|
monkeypatch.setenv("RETAIN_RAW_HOURS", "6")
|
|
monkeypatch.setenv("NUT_ALLOW_ACTIONS", "1")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.port == 8123
|
|
assert reloaded.SETTINGS.raw_keep_hours == 6
|
|
assert reloaded.SETTINGS.actions_allowed is True
|
|
finally:
|
|
monkeypatch.delenv("DASH_PORT")
|
|
monkeypatch.delenv("RETAIN_RAW_HOURS")
|
|
monkeypatch.delenv("NUT_ALLOW_ACTIONS")
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_flags_accept_the_usual_spellings(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
for truthy in ("1", "true", "TRUE", "yes", "on"):
|
|
monkeypatch.setenv("DASH_READ_ONLY", truthy)
|
|
assert config._flag("DASH_READ_ONLY") is True, truthy
|
|
for falsey in ("0", "false", "", "no"):
|
|
monkeypatch.setenv("DASH_READ_ONLY", falsey)
|
|
assert config._flag("DASH_READ_ONLY") is False, falsey
|
|
|
|
|
|
def test_numeric_env_must_be_a_number_or_the_default_wins(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("HOST_INTERVAL_SECONDS", "not-a-number")
|
|
assert config._num("HOST_INTERVAL_SECONDS", 10.0) == 10.0
|
|
monkeypatch.setenv("HOST_INTERVAL_SECONDS", "2.5")
|
|
assert config._num("HOST_INTERVAL_SECONDS", 10.0) == 2.5
|
|
|
|
|
|
def test_settings_is_immutable_so_nobody_mutates_shared_state() -> None:
|
|
settings: Any = Settings()
|
|
with pytest.raises(FrozenInstanceError):
|
|
settings.port = 1 # type: ignore[misc]
|
|
|
|
|
|
# ---- the .env convenience -------------------------------------------------- #
|
|
|
|
def test_no_env_file_is_the_normal_case(monkeypatch: pytest.MonkeyPatch, tmp_path: Any) -> None:
|
|
monkeypatch.setenv("DASH_ENV_FILE", str(tmp_path / "absent.env"))
|
|
assert config.load_env_file() is None
|
|
|
|
|
|
def test_env_file_seeds_values_a_deployed_env_does_not_set(monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Any) -> None:
|
|
env = tmp_path / ".env"
|
|
env.write_text("DASH_PORT=9999\nNUT_HOST=ups.internal\n")
|
|
monkeypatch.setenv("DASH_ENV_FILE", str(env))
|
|
monkeypatch.delenv("DASH_PORT", raising=False)
|
|
monkeypatch.delenv("NUT_HOST", raising=False)
|
|
assert config.load_env_file() == env
|
|
# Settings reads its defaults when the module is imported, so the reload is what a real
|
|
# process gets for free and what the test has to ask for explicitly
|
|
reloaded = importlib.reload(config)
|
|
assert reloaded.SETTINGS.port == 9999
|
|
assert reloaded.SETTINGS.nut_host == "ups.internal"
|
|
|
|
|
|
def test_a_real_environment_variable_beats_the_file(monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Any) -> None:
|
|
"""The container/quadlet env is the source of truth; the file is for laptops."""
|
|
env = tmp_path / ".env"
|
|
env.write_text("DASH_PORT=9999\n")
|
|
monkeypatch.setenv("DASH_ENV_FILE", str(env))
|
|
monkeypatch.setenv("DASH_PORT", "7777")
|
|
reloaded = importlib.reload(config)
|
|
assert os.environ["DASH_PORT"] == "7777"
|
|
assert reloaded.SETTINGS.port == 7777
|
|
|
|
|
|
def test_env_file_expands_other_variables(monkeypatch: pytest.MonkeyPatch, tmp_path: Any) -> None:
|
|
"""`.env.example` shows `DASH_DB=${HOME}/...` — the file has to mean what it shows."""
|
|
env = tmp_path / ".env"
|
|
env.write_text("MY_BASE=/srv/dash\nDASH_DB=${MY_BASE}/dashboards.sqlite3\n")
|
|
monkeypatch.setenv("DASH_ENV_FILE", str(env))
|
|
monkeypatch.delenv("MY_BASE", raising=False)
|
|
monkeypatch.delenv("DASH_DB", raising=False)
|
|
reloaded = importlib.reload(config)
|
|
assert reloaded.SETTINGS.db_path == "/srv/dash/dashboards.sqlite3"
|
|
|
|
|
|
def test_the_example_file_is_never_loaded(monkeypatch: pytest.MonkeyPatch, tmp_path: Any) -> None:
|
|
example = tmp_path / ".env.example"
|
|
example.write_text("NUT_HOST=should-not-load\n")
|
|
monkeypatch.setenv("DASH_ENV_FILE", str(example))
|
|
monkeypatch.delenv("NUT_HOST", raising=False)
|
|
reloaded = importlib.reload(config)
|
|
assert reloaded.load_env_file() is None
|
|
assert reloaded.SETTINGS.nut_host == ""
|
|
|
|
|
|
# ---- authentication settings (phase 11) ------------------------------------ #
|
|
|
|
|
|
def test_the_auth_defaults_are_a_locked_door_and_a_long_session() -> None:
|
|
"""No default password, a 30-day session, no `Secure` flag, OWASP's cost.
|
|
|
|
Each default is a decision, so each is asserted rather than inherited:
|
|
|
|
* `admin_password == ""` is what makes "no password configured" detectable at all — a
|
|
default of `testpass` would ship a known credential on every box that forgot the env.
|
|
* 720 h (30 days) because a dashboard that logs the operator out mid-outage gets bypassed.
|
|
* `cookie_secure` off: dev is `http://127.0.0.1`, and a default on would make the cookie
|
|
unsendable there (the task adds the `X-Forwarded-Proto` case for the real proxy).
|
|
* the cost is `auth.PBKDF2_ITERATIONS`, imported rather than repeated, so the primitive and
|
|
the setting cannot drift.
|
|
"""
|
|
settings = Settings()
|
|
assert settings.admin_password == ""
|
|
assert settings.session_hours == 720.0
|
|
assert settings.cookie_secure is False
|
|
assert settings.pbkdf2_iterations == PBKDF2_ITERATIONS == 600_000
|
|
|
|
|
|
def test_the_auth_settings_come_from_the_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("DASH_ADMIN_PASSWORD", " spaced-but-real ")
|
|
monkeypatch.setenv("DASH_SESSION_HOURS", "12")
|
|
monkeypatch.setenv("DASH_COOKIE_SECURE", "1")
|
|
monkeypatch.setenv("DASH_PBKDF2_ITERATIONS", "4096")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.admin_password == "spaced-but-real", "_env() strips"
|
|
assert reloaded.SETTINGS.session_hours == 12.0
|
|
assert reloaded.SETTINGS.cookie_secure is True
|
|
assert reloaded.SETTINGS.pbkdf2_iterations == 4096
|
|
finally:
|
|
for name in ("DASH_ADMIN_PASSWORD", "DASH_SESSION_HOURS", "DASH_COOKIE_SECURE",
|
|
"DASH_PBKDF2_ITERATIONS"):
|
|
monkeypatch.delenv(name)
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_an_empty_DASH_ADMIN_PASSWORD_means_unset_not_blank(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""`DASH_ADMIN_PASSWORD=` is the operator saying "no seed", not "the password is empty".
|
|
|
|
The distinction is load-bearing: `load_auth_state` treats `""` as no seed and fails closed,
|
|
where a value of `""` that survived as a password would be the one credential shape an
|
|
attacker tries first.
|
|
"""
|
|
monkeypatch.setenv("DASH_ADMIN_PASSWORD", "")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.admin_password == ""
|
|
finally:
|
|
monkeypatch.delenv("DASH_ADMIN_PASSWORD")
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_the_pbkdf2_knob_is_for_tests_and_not_a_performance_tune(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""A garbage value falls back to the OWASP figure rather than to 0 or 1.
|
|
|
|
`_int`/`_num` answer a non-number with the default, so a typo in
|
|
`DASH_PBKDF2_ITERATIONS` cannot produce a verifier hashed once.
|
|
"""
|
|
monkeypatch.setenv("DASH_PBKDF2_ITERATIONS", "not-a-cost")
|
|
assert config._int("DASH_PBKDF2_ITERATIONS", 600_000) == 600_000
|
|
monkeypatch.setenv("DASH_PBKDF2_ITERATIONS", "1000")
|
|
assert config._int("DASH_PBKDF2_ITERATIONS", 600_000) == 1000
|
|
|
|
|
|
# ---- the llm engine registry (phase 34 task 02) --------------------------- #
|
|
|
|
def test_radiance_defaults_to_unset_the_way_the_other_engines_do() -> None:
|
|
"""Unset means "this deployment does not run it" (invariant 3), like `gufo_url`.
|
|
|
|
It must be invisible to `any_source_enabled` too: a deployment that runs no source at
|
|
all is `empty`, not broken, and an unset engine is the half of the page that
|
|
disappears, never a red panel.
|
|
"""
|
|
settings = Settings()
|
|
assert settings.radiance_url == ""
|
|
assert settings.radiance_api_key == ""
|
|
assert settings.any_source_enabled is False
|
|
|
|
|
|
def test_radiance_settings_come_from_the_environment(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("RADIANCE_BASE_URL", " http://driveripper.example.net:8000/ ")
|
|
monkeypatch.setenv("RADIANCE_API_KEY", " engine-key ")
|
|
reloaded = importlib.reload(config)
|
|
try:
|
|
assert reloaded.SETTINGS.radiance_url == "http://driveripper.example.net:8000", "`_base_url` strips whitespace and the trailing slash"
|
|
assert reloaded.SETTINGS.radiance_api_key == "engine-key", "_env() strips"
|
|
assert reloaded.SETTINGS.any_source_enabled is True
|
|
finally:
|
|
for name in ("RADIANCE_BASE_URL", "RADIANCE_API_KEY"):
|
|
monkeypatch.delenv(name)
|
|
importlib.reload(config)
|
|
|
|
|
|
def test_llm_engines_is_the_one_registry_in_its_stable_three_engine_order() -> None:
|
|
"""The order `gufo`, `vllm`, `radiance` is the contract: the UI's card order and the
|
|
history defaults follow it, so a reordered registry is a page change, not a list edit.
|
|
`key` is the history DB's host key and the job id; `display` is what a health record
|
|
and a page print; the api key travels verbatim into the dial's `Authorization` header.
|
|
"""
|
|
settings = Settings(gufo_url="http://gufo.example.net:8731", gufo_api_key="gufo-key",
|
|
vllm_url="http://vllm.example.net:8000", vllm_api_key="vllm-key",
|
|
radiance_url="http://driveripper.example.net:8000",
|
|
radiance_api_key="radiance-key")
|
|
assert settings.llm_engines() == [
|
|
("gufo", "gufo", "http://gufo.example.net:8731", "gufo-key"),
|
|
("vllm", "vLLM", "http://vllm.example.net:8000", "vllm-key"),
|
|
("radiance", "radiance", "http://driveripper.example.net:8000", "radiance-key"),
|
|
]
|
|
|
|
|
|
def test_llm_engines_lists_an_unset_engine_with_an_empty_url() -> None:
|
|
"""The registry is the full fleet the way `parse_targets` keeps the host list: an
|
|
unset engine is *listed*, with an empty `url` — configured-ness is the call site's
|
|
`bool(url)` test (the `nut_enabled` shape), never a missing entry, so a site that
|
|
iterates the list cannot silently read a two-engine world.
|
|
"""
|
|
entries = Settings().llm_engines()
|
|
assert [key for key, _display, _url, _api_key in entries] == ["gufo", "vllm", "radiance"]
|
|
assert [display for _key, display, _url, _api_key in entries] == ["gufo", "vLLM", "radiance"]
|
|
assert [url for _key, _display, url, _api_key in entries] == ["", "", ""]
|
|
|
|
|
|
def _llm_job_keys(settings: Settings) -> list[str]:
|
|
"""The job ids `llm_jobs` would build, from the registry, for one settings object.
|
|
|
|
The client is never touched by the construction (the jobs close over it for their
|
|
own poll loops), so a bare, never-entered `AsyncClient` is the right fake — and an
|
|
unentered one warns nothing and needs no closing.
|
|
"""
|
|
client = httpx.AsyncClient()
|
|
return [job.key for job in llm_jobs(settings, client, RateEstimator())]
|
|
|
|
|
|
def test_radiance_unset_gets_no_job_and_changes_nothing_about_the_source() -> None:
|
|
"""No job, and `any_source_enabled` is unchanged by it: an unconfigured engine is never
|
|
dialled and never shows a red record (invariant 3)."""
|
|
settings = Settings()
|
|
assert settings.any_source_enabled is False
|
|
assert _llm_job_keys(settings) == []
|
|
|
|
|
|
def test_radiance_set_gets_its_job_and_enables_the_source() -> None:
|
|
"""One `llm:radiance` job, in the registry's position, and the same job set a three-engine
|
|
deployment already had before the third engine existed (gufo/vllm byte-identical)."""
|
|
settings = Settings(radiance_url="http://driveripper.example.net:8000")
|
|
assert settings.any_source_enabled is True
|
|
assert _llm_job_keys(settings) == ["llm:radiance"]
|
|
settings = Settings(gufo_url="http://gufo.example.net:8731",
|
|
vllm_url="http://vllm.example.net:8000",
|
|
radiance_url="http://driveripper.example.net:8000")
|
|
assert _llm_job_keys(settings) == ["llm:gufo", "llm:vllm", "llm:radiance"]
|