Some checks failed
SonarQube Code Quality Scan / SonarQube Scan (push) Failing after 2m37s
126 lines
5.1 KiB
Python
126 lines
5.1 KiB
Python
"""Tests for the config module (config.py)."""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
|
|
# We import the module under test after patching CONFIG_DIR so that
|
|
# the module-level SETTINGS dict is loaded from a temp location.
|
|
@pytest.fixture(autouse=True)
|
|
def _patch_config_paths(monkeypatch, tmp_path):
|
|
"""Redirect CONFIG_DIR to a tmp_path so tests don't touch ~/.config."""
|
|
config_dir = tmp_path / ".config" / "tunetti"
|
|
monkeypatch.setattr("config.CONFIG_DIR", config_dir)
|
|
monkeypatch.setattr("config.CONFIG_FILE", config_dir / "config.json")
|
|
monkeypatch.setattr("config._LEGACY_FILE", tmp_path / "tunetti_config.json")
|
|
|
|
# Also reset SETTINGS and DEFAULT_CONFIG after patching
|
|
import config as cfg
|
|
cfg.SETTINGS = cfg.load_config()
|
|
return cfg
|
|
|
|
|
|
# ── Tests ─────────────────────────────────────────────────────────────────────
|
|
|
|
class TestLoadConfig:
|
|
def test_first_launch_creates_defaults(self, _patch_config_paths):
|
|
"""On first launch, no config exists → defaults are written to disk."""
|
|
cfg = _patch_config_paths
|
|
assert cfg.CONFIG_FILE.exists()
|
|
assert cfg.SETTINGS["volume"] == 50
|
|
assert cfg.SETTINGS["max_history"] == 5000
|
|
assert cfg.SETTINGS["discord_rpc_enabled"] is True
|
|
|
|
def test_loads_existing_config(self, _patch_config_paths):
|
|
"""Loading an existing config merges user values over defaults."""
|
|
cfg = _patch_config_paths
|
|
cfg.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
user_config = {"volume": 75, "max_history": 100}
|
|
cfg.CONFIG_FILE.write_text(json.dumps(user_config))
|
|
|
|
loaded = cfg.load_config()
|
|
assert loaded["volume"] == 75
|
|
assert loaded["max_history"] == 100
|
|
# default keys still present
|
|
assert loaded["discord_rpc_enabled"] is True
|
|
|
|
def test_corrupt_config_falls_back_to_defaults(self, _patch_config_paths):
|
|
"""Corrupt JSON should be ignored and defaults returned."""
|
|
cfg = _patch_config_paths
|
|
cfg.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
cfg.CONFIG_FILE.write_text("not valid json")
|
|
|
|
loaded = cfg.load_config()
|
|
assert loaded["volume"] == 50 # default
|
|
# The corrupt file is not overwritten, but defaults are returned
|
|
|
|
def test_legacy_migration(self, _patch_config_paths):
|
|
"""Legacy config in project dir is migrated to XDG location."""
|
|
cfg = _patch_config_paths
|
|
# Remove the config that was created by the fixture so migration runs
|
|
cfg.CONFIG_FILE.unlink(missing_ok=True)
|
|
cfg.SETTINGS = dict(cfg.DEFAULT_CONFIG)
|
|
|
|
legacy = {**cfg.DEFAULT_CONFIG, "volume": 90}
|
|
# Legacy file location is the parent of the "real" config dir (tmp_path)
|
|
cfg._LEGACY_FILE.write_text(json.dumps(legacy))
|
|
|
|
loaded = cfg.load_config()
|
|
assert loaded["volume"] == 90
|
|
# Legacy file should be renamed
|
|
assert not cfg._LEGACY_FILE.exists()
|
|
assert cfg._LEGACY_FILE.with_suffix(".json.migrated").exists()
|
|
|
|
|
|
class TestSaveConfig:
|
|
def test_save_config_persists(self, _patch_config_paths):
|
|
"""save_config writes merged config to disk."""
|
|
cfg = _patch_config_paths
|
|
cfg.save_config({"volume": 30, "visualizer_enabled": False})
|
|
data = json.loads(cfg.CONFIG_FILE.read_text())
|
|
assert data["volume"] == 30
|
|
assert data["visualizer_enabled"] is False
|
|
|
|
def test_save_volume_clamps(self, _patch_config_paths):
|
|
"""save_volume clamps values between 0 and 100."""
|
|
cfg = _patch_config_paths
|
|
cfg.save_volume(-10)
|
|
assert cfg.SETTINGS["volume"] == 0
|
|
cfg.save_volume(150)
|
|
assert cfg.SETTINGS["volume"] == 100
|
|
cfg.save_volume(42)
|
|
assert cfg.SETTINGS["volume"] == 42
|
|
|
|
def test_save_setting_updates_memory_and_disk(self, _patch_config_paths):
|
|
"""save_setting updates both in-memory SETTINGS and persisted config."""
|
|
cfg = _patch_config_paths
|
|
cfg.save_setting("max_history", 999)
|
|
assert cfg.SETTINGS["max_history"] == 999
|
|
data = json.loads(cfg.CONFIG_FILE.read_text())
|
|
assert data["max_history"] == 999
|
|
|
|
|
|
class TestGetSetting:
|
|
def test_returns_set_value(self, _patch_config_paths):
|
|
"""get_setting returns the current in-memory value."""
|
|
cfg = _patch_config_paths
|
|
cfg.SETTINGS["volume"] = 88
|
|
assert cfg.get_setting("volume") == 88
|
|
|
|
def test_returns_default_for_missing(self, _patch_config_paths):
|
|
"""get_setting falls back to DEFAULT_CONFIG for missing keys."""
|
|
cfg = _patch_config_paths
|
|
# Remove from SETTINGS
|
|
cfg.SETTINGS.pop("volume", None)
|
|
assert cfg.get_setting("volume") == cfg.DEFAULT_CONFIG["volume"]
|
|
|
|
def test_nonexistent_key_returns_none(self, _patch_config_paths):
|
|
"""get_setting returns None for keys not in either dict."""
|
|
cfg = _patch_config_paths
|
|
assert cfg.get_setting("nonexistent_key") is None
|