fix: use deepcopy for DEFAULT_SETTINGS and narrow exception catch in settings module

- Replace DEFAULT_SETTINGS.copy() with copy.deepcopy(DEFAULT_SETTINGS) in
  load_settings() and save_settings() to prevent mutation of module-level
  mutable defaults (hidden_sessions list was shared via shallow copy)
- Replace except (json.JSONDecodeError, Exception) with
  except (FileNotFoundError, json.JSONDecodeError) to only suppress expected
  errors and let unexpected exceptions (PermissionError etc.) propagate
- Add two regression tests:
  test_load_does_not_mutate_default_settings: verifies deepcopy isolation
  test_load_propagates_non_json_errors: verifies narrow exception handling
This commit is contained in:
Brian Krabach
2026-03-29 22:43:24 -07:00
parent ffecfa69cb
commit 85d3bb1312
2 changed files with 30 additions and 5 deletions
+4 -5
View File
@@ -4,6 +4,7 @@ Server-side settings management for muxplex.
Settings are stored at ~/.config/muxplex/settings.json.
"""
import copy
import json
from pathlib import Path
@@ -25,16 +26,14 @@ def load_settings() -> dict:
Returns DEFAULT_SETTINGS if the file does not exist or contains corrupt JSON.
Unknown keys in the file are ignored.
"""
result = DEFAULT_SETTINGS.copy()
result = copy.deepcopy(DEFAULT_SETTINGS)
try:
text = SETTINGS_PATH.read_text()
data = json.loads(text)
for key in DEFAULT_SETTINGS:
if key in data:
result[key] = data[key]
except FileNotFoundError:
pass
except (json.JSONDecodeError, Exception):
except (FileNotFoundError, json.JSONDecodeError):
pass
return result
@@ -45,7 +44,7 @@ def save_settings(data: dict) -> None:
Creates parent directories as needed. Writes JSON with indent=2 and a
trailing newline.
"""
merged = DEFAULT_SETTINGS.copy()
merged = copy.deepcopy(DEFAULT_SETTINGS)
for key in DEFAULT_SETTINGS:
if key in data:
merged[key] = data[key]