"""An unknown key in an admin request body is an error, not a silent no-op. CLAUDE.md already records why the config models forbid extras: with Pydantic's default a typo "loads cleanly, does nothing, and still looks configured". The request surface had the same hole, and on one endpoint it was destructive rather than merely quiet. """ from __future__ import annotations import sqlite3 from pathlib import Path import pytest from starlette.testclient import TestClient import admin import dispatcher ROOT = Path(__file__).resolve().parent.parent SCHEMA_SQL = (ROOT / "config" / "schema.sql").read_text() @pytest.fixture def client(tmp_path, monkeypatch): db_path = tmp_path / "strict.db" conn = sqlite3.connect(db_path) conn.executescript(SCHEMA_SQL) admin.ensure_admin_tables(conn) conn.execute( """ INSERT INTO models ( model_id, provider, base_model_id, tier, context_window, effective_context_window, max_output_tokens, cost_per_1m_prompt, cost_per_1m_completion, supports_vision, supports_json_mode, latency_class, reasoning_mode, context_variant, access_level, availability, last_updated ) VALUES ('m1', 'neuralwatt', 'm1', 2, 100000, 90000, 4096, 1.0, 2.0, 0, 1, 'standard', 'default', 'full', 'public', 'active', '2026-08-22T00:00:00+00:00') """ ) conn.commit() conn.close() monkeypatch.setattr(dispatcher.cfg.database, "path", str(db_path)) return TestClient(dispatcher.app) # --- the destructive case that motivated this ------------------------------ def test_the_flat_cloud_fallback_shape_is_refused_not_treated_as_a_delete(): """The bug: `_CloudFallbackBody` nests under `cloud_fallback`, so a flat body had every key discarded, leaving the field None -- which is the documented "remove the block" signal. A caller trying to SAVE a cloud classifier deleted it and was told the save succeeded. """ with pytest.raises(Exception) as exc: admin._CloudFallbackBody( base_url="https://api.example.com/v1", model="some-model", api_key_env="SOME_KEY", ) assert "extra" in str(exc.value).lower() def test_the_correct_nested_cloud_fallback_shape_still_validates(): body = admin._CloudFallbackBody( cloud_fallback={"base_url": "https://api.example.com/v1", "model": "some-model"} ) assert body.cloud_fallback["model"] == "some-model" # --- the quiet cases ------------------------------------------------------- def test_a_typo_in_a_profile_field_is_refused(client): """`min_teir` used to return 200 and create a profile with no filters.""" resp = client.post("/admin/api/profiles/", json={"name": "typoprof", "min_teir": 3}) assert resp.status_code == 422, resp.text assert "extra" in resp.text.lower() names = [p["name"] for p in client.get("/admin/api/profiles").json()] assert "typoprof" not in names @pytest.mark.parametrize( "model_cls, kwargs", [ (admin._AvailabilityBody, {"availability": "active", "resaon": "x"}), (admin._ValueBody, {"value": True, "vaule": 1}), (admin._ClassifierConfigBody, {"mode": "local_llm", "modee": "x"}), (admin._ProfileCreateBody, {"name": "p", "provdier": "x"}), (admin._ProfileUpdateBody, {"min_tier": 1, "max_teir": 3}), (admin._ProviderUpdateBody, {"base_url": "https://x/v1", "api_key_env": "K", "has_energy_telemetry": False, "enabled": True, "requires_allowlist": True}), (admin._AllowlistAddBody, {"model_id": "m", "notes": "x"}), ], ) def test_every_admin_body_forbids_unknown_keys(model_cls, kwargs): """One base class covers the whole surface; this asserts nobody escaped. The ``_ProviderUpdateBody`` case is a real near-miss rather than a contrived one: the field is ``require_allowlist``, and ``requires_`` reads so naturally that a caller would never notice it had been dropped. """ with pytest.raises(Exception) as exc: model_cls(**kwargs) assert "extra" in str(exc.value).lower(), model_cls.__name__ def test_known_keys_are_unaffected(client): """Strictness must not cost the happy path. Deliberately exercised through a DB-backed endpoint rather than profile CRUD: profile writes land in ``config/config.local.yaml``, which this fixture does not isolate, so asserting on them here would couple the test to whatever the machine's overlay already contains. That coupling is finding #12 in plans/admin-portal-functional-sweep.md. """ resp = client.post("/admin/api/models/m1/neuralwatt/availability", json={"availability": "deprecated", "reason": "test"}) assert resp.status_code == 200, resp.text assert resp.json()["effective_availability"] == "deprecated" def test_known_keys_are_unaffected_on_the_models_that_write_files(): """The same, for the file-writing bodies, without touching a file.""" assert admin._ProfileCreateBody(name="p", min_tier=2).min_tier == 2 assert admin._ProviderUpdateBody( base_url="https://x/v1", api_key_env="K", has_energy_telemetry=False, enabled=True, ).enabled is True assert admin._AllowlistAddBody(model_id="m", note="n").note == "n"