From 2376bf5d7196f2d0e15475f4670cf567ecc65a92 Mon Sep 17 00:00:00 2001 From: "thomas.kopp" Date: Wed, 1 Apr 2026 20:40:40 +0200 Subject: [PATCH] fix: PUT /config deep-merges nested config instead of shallow update Replaces cfg.update(body) with _deep_merge so partial updates (e.g. setting whisper.base_url) no longer wipe sibling keys. Also persists the merged config back to disk via tomli_w. Adds test_put_config_deep_merges. --- api/router.py | 9 +++++++-- tests/test_api.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/api/router.py b/api/router.py index a42a853..9d353ea 100644 --- a/api/router.py +++ b/api/router.py @@ -196,9 +196,14 @@ async def get_config(user: dict = Depends(current_user)): async def put_config(body: dict, user: dict = Depends(current_user)): if not user.get("is_admin"): raise HTTPException(status_code=403, detail="Nur Administratoren können die Config ändern") + from config import _deep_merge, CONFIG_PATH + import tomli_w cfg = load_config() - cfg.update(body) - return cfg + merged = _deep_merge(cfg, body) + os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) + with open(CONFIG_PATH, "wb") as f: + tomli_w.dump(merged, f) + return merged @router.post("/open") diff --git a/tests/test_api.py b/tests/test_api.py index ff5da1e..761b85f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -144,3 +144,24 @@ def test_audio_combined_forbidden_for_non_admin(): assert r.status_code == 403 finally: app.dependency_overrides.pop(current_user, None) + + +def test_put_config_deep_merges(tmp_path, monkeypatch): + import config as cfg_mod + monkeypatch.setattr(cfg_mod, "CONFIG_PATH", str(tmp_path / "config.toml")) + from main import app + from api.router import current_user + app.dependency_overrides[current_user] = lambda: {"username": "u", "output_dir": "/tmp", "is_admin": True} + try: + from fastapi.testclient import TestClient + client = TestClient(app) + r = client.put("/config", + json={"whisper": {"base_url": "http://beastix:8000"}}, + headers={"Authorization": "Bearer fake"}) + assert r.status_code == 200 + data = r.json() + # base_url updated, model preserved + assert data["whisper"]["base_url"] == "http://beastix:8000" + assert data["whisper"]["model"] == "large-v3" + finally: + app.dependency_overrides.pop(current_user, None)