refactor(server): extract the settings routes into routers/settings.py (R3) (#863)

GET/POST /api/settings, /api/settings/reset, and the two-phase atomic
export/import bundle (/api/settings/export|import) move to lib/routers/settings.py
with their exclusive helpers (the relpath allowlist validator, the atomic writer,
the library-DB snapshot + sqlite integrity gate, the config-type validator, the
bundle schema). Bodies verbatim except @app->@router and the seam reads:
meta_db->appstate.meta_db, CONFIG_DIR->appstate.config_dir,
_running_version->appstate.running_version(), and _default_settings->
appstate.default_settings (the canonical defaults builder stays in server.py —
the scan + artist-links code share it — and is injected as a new seam callable).

server.py: 5,539 -> 4,478 (-1,061).

Verified: pyflakes clean (bar the pre-existing File/safe_join/tuning_name/ET);
route table IDENTICAL (143); full pytest 2397 passed (154 settings cases incl the
export→import round-trip + library-DB snapshot/restore + relpath-allowlist SSRF/
traversal guards, retargeted onto the settings module). eslint 0.

BEHAVIORAL — needs an on-device settings export→import round-trip sign-off before
merge (do not merge on green CI alone).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Byron Gamatos
2026-07-11 12:35:05 +02:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 73127d5416
commit 7258e1066a
9 changed files with 1178 additions and 1092 deletions
+5 -4
View File
@@ -11,6 +11,7 @@ is exercised separately in `test_plugins.py`.
import base64
import importlib
from routers import settings as settings_router
import json
import os
import sys
@@ -478,7 +479,7 @@ def test_normalize_export_paths_consistency(server_mod, tmp_path):
# Wraps `_validate_relpath` to assert it doesn't raise the
# hard-failure ValueErrors. _UndeclaredFile would mean the
# allowlist is wrong, not that the relpath shape is bad.
server_mod._validate_relpath(rel, cleaned, tmp_path)
settings_router._validate_relpath(rel, cleaned, tmp_path)
# ── Atomic write: unique tmp + cleanup on failure ───────────────────────────
@@ -502,7 +503,7 @@ def test_atomic_write_cleans_up_tmp_on_failure(server_mod, tmp_path, monkeypatch
for _ in range(2):
with pytest.raises(OSError):
server_mod._atomic_write_file(target, b"payload")
settings_router._atomic_write_file(target, b"payload")
# Both attempts cleaned up. No .tmp.import residue means the
# mkstemp + finally-unlink pattern held even across failures.
@@ -512,7 +513,7 @@ def test_atomic_write_cleans_up_tmp_on_failure(server_mod, tmp_path, monkeypatch
# Restoring real replace, the function should still work end-to-end.
monkeypatch.setattr(server_mod.os, "replace", real_replace)
server_mod._atomic_write_file(target, b"payload")
settings_router._atomic_write_file(target, b"payload")
assert target.read_bytes() == b"payload"
assert list(tmp_path.glob("*.tmp.import")) == []
@@ -764,7 +765,7 @@ def test_atomic_write_closes_fd_when_fdopen_fails(server_mod, tmp_path, monkeypa
monkeypatch.setattr(server_mod.os, "fdopen", boom_fdopen)
with pytest.raises(OSError, match="simulated EMFILE"):
server_mod._atomic_write_file(target, b"payload")
settings_router._atomic_write_file(target, b"payload")
# fd was closed (so it didn't leak), and the temp file mkstemp
# created was removed (so it doesn't litter / lock on Windows).