feat: add remote_instances and device_name to settings
- Add 'remote_instances': [] to DEFAULT_SETTINGS for peer instance configs - Add 'device_name': '' to DEFAULT_SETTINGS (empty for clean JSON serialization) - Add import socket to settings.py - In load_settings(), fill empty device_name with socket.gethostname() so the hostname is always current even if the machine is renamed - Add 7 new tests covering defaults, hostname fallback, round-trips, and mutation isolation - Update 2 existing tests that compared result == DEFAULT_SETTINGS to account for the hostname fill-in behavior Co-authored-by: Amplifier <amplifier@amplified.dev>
This commit is contained in:
@@ -6,6 +6,7 @@ Settings are stored at ~/.config/muxplex/settings.json.
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
import socket
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
SETTINGS_PATH = Path.home() / ".config" / "muxplex" / "settings.json"
|
SETTINGS_PATH = Path.home() / ".config" / "muxplex" / "settings.json"
|
||||||
@@ -17,6 +18,8 @@ DEFAULT_SETTINGS: dict = {
|
|||||||
"window_size_largest": False,
|
"window_size_largest": False,
|
||||||
"auto_open_created": True,
|
"auto_open_created": True,
|
||||||
"new_session_template": "tmux new-session -d -s {name}",
|
"new_session_template": "tmux new-session -d -s {name}",
|
||||||
|
"remote_instances": [],
|
||||||
|
"device_name": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +38,8 @@ def load_settings() -> dict:
|
|||||||
result[key] = data[key]
|
result[key] = data[key]
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
pass
|
pass
|
||||||
|
if not result["device_name"]:
|
||||||
|
result["device_name"] = socket.gethostname()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,14 @@ def redirect_settings_path(tmp_path, monkeypatch):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_load_returns_defaults_when_no_file():
|
def test_load_returns_defaults_when_no_file(monkeypatch):
|
||||||
"""load_settings() returns DEFAULT_SETTINGS when no file exists."""
|
"""load_settings() returns DEFAULT_SETTINGS (with hostname fill-in) when no file exists."""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
monkeypatch.setattr(socket, "gethostname", lambda: "test-host")
|
||||||
result = load_settings()
|
result = load_settings()
|
||||||
assert result == DEFAULT_SETTINGS
|
expected = {**DEFAULT_SETTINGS, "device_name": "test-host"}
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_load_returns_saved_values(tmp_path, monkeypatch):
|
def test_load_returns_saved_values(tmp_path, monkeypatch):
|
||||||
@@ -82,13 +86,17 @@ def test_save_merges_with_defaults(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
def test_load_handles_corrupt_json(tmp_path, monkeypatch):
|
def test_load_handles_corrupt_json(tmp_path, monkeypatch):
|
||||||
"""load_settings() returns defaults gracefully on corrupt JSON."""
|
"""load_settings() returns defaults gracefully on corrupt JSON."""
|
||||||
|
import socket
|
||||||
|
|
||||||
fake_path = tmp_path / "settings.json"
|
fake_path = tmp_path / "settings.json"
|
||||||
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", fake_path)
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", fake_path)
|
||||||
|
monkeypatch.setattr(socket, "gethostname", lambda: "test-host")
|
||||||
fake_path.write_text("NOT VALID JSON {{{{")
|
fake_path.write_text("NOT VALID JSON {{{{")
|
||||||
|
|
||||||
result = load_settings()
|
result = load_settings()
|
||||||
|
|
||||||
assert result == DEFAULT_SETTINGS
|
expected = {**DEFAULT_SETTINGS, "device_name": "test-host"}
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_patch_settings_merges_single_field(tmp_path, monkeypatch):
|
def test_patch_settings_merges_single_field(tmp_path, monkeypatch):
|
||||||
@@ -142,3 +150,81 @@ def test_load_propagates_non_json_errors(monkeypatch):
|
|||||||
|
|
||||||
with pytest.raises(PermissionError):
|
with pytest.raises(PermissionError):
|
||||||
load_settings()
|
load_settings()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Federation fields tests (task-3-extend-settings-federation-fields)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_include_remote_instances():
|
||||||
|
"""DEFAULT_SETTINGS must have 'remote_instances' key initialised to []."""
|
||||||
|
assert "remote_instances" in DEFAULT_SETTINGS
|
||||||
|
assert DEFAULT_SETTINGS["remote_instances"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_include_device_name():
|
||||||
|
"""DEFAULT_SETTINGS must have 'device_name' key initialised to empty string."""
|
||||||
|
assert "device_name" in DEFAULT_SETTINGS
|
||||||
|
assert DEFAULT_SETTINGS["device_name"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_returns_hostname_when_device_name_empty(monkeypatch):
|
||||||
|
"""load_settings() fills empty device_name with socket.gethostname()."""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
monkeypatch.setattr(socket, "gethostname", lambda: "my-laptop")
|
||||||
|
result = load_settings()
|
||||||
|
assert result["device_name"] == "my-laptop"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_preserves_explicit_device_name(tmp_path, monkeypatch):
|
||||||
|
"""load_settings() keeps an explicitly saved device_name (does not overwrite with hostname)."""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
fake_path = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", fake_path)
|
||||||
|
fake_path.write_text(json.dumps({"device_name": "Work PC"}))
|
||||||
|
monkeypatch.setattr(socket, "gethostname", lambda: "should-not-appear")
|
||||||
|
|
||||||
|
result = load_settings()
|
||||||
|
assert result["device_name"] == "Work PC"
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_instances_round_trip(tmp_path, monkeypatch):
|
||||||
|
"""remote_instances survive a save/load cycle unchanged."""
|
||||||
|
fake_path = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", fake_path)
|
||||||
|
|
||||||
|
instances = [
|
||||||
|
{"url": "http://host1:7681", "name": "Host 1"},
|
||||||
|
{"url": "http://host2:7681", "name": "Host 2"},
|
||||||
|
]
|
||||||
|
save_settings({"remote_instances": instances})
|
||||||
|
|
||||||
|
result = load_settings()
|
||||||
|
assert result["remote_instances"] == instances
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_name_round_trip(tmp_path, monkeypatch):
|
||||||
|
"""An explicit device_name survives a save/load cycle unchanged."""
|
||||||
|
fake_path = tmp_path / "settings.json"
|
||||||
|
monkeypatch.setattr(settings_mod, "SETTINGS_PATH", fake_path)
|
||||||
|
|
||||||
|
save_settings({"device_name": "My Server"})
|
||||||
|
|
||||||
|
result = load_settings()
|
||||||
|
assert result["device_name"] == "My Server"
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_does_not_mutate_default_remote_instances():
|
||||||
|
"""Mutating the list returned by load_settings() must not corrupt DEFAULT_SETTINGS."""
|
||||||
|
result = load_settings()
|
||||||
|
result["remote_instances"].append({"url": "http://leaked", "name": "leaked"})
|
||||||
|
|
||||||
|
# DEFAULT_SETTINGS must be unchanged
|
||||||
|
assert DEFAULT_SETTINGS["remote_instances"] == []
|
||||||
|
|
||||||
|
# A second load must still return the clean default
|
||||||
|
result2 = load_settings()
|
||||||
|
assert result2["remote_instances"] == []
|
||||||
|
|||||||
Reference in New Issue
Block a user