feat: sync-aware patch_settings and apply_synced_settings
This commit is contained in:
@@ -8,6 +8,7 @@ import copy
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
SETTINGS_PATH = Path.home() / ".config" / "muxplex" / "settings.json"
|
||||
@@ -151,10 +152,36 @@ def patch_settings(patch: dict) -> dict:
|
||||
# restore by index so editing a URL doesn't erase the key.
|
||||
remote["key"] = existing_remote_keys_by_index[i]
|
||||
|
||||
if any(key in SYNCABLE_KEYS for key in patch if key in DEFAULT_SETTINGS):
|
||||
current["settings_updated_at"] = time.time()
|
||||
|
||||
save_settings(current)
|
||||
return current
|
||||
|
||||
|
||||
def apply_synced_settings(incoming_settings: dict, incoming_timestamp: float) -> dict:
|
||||
"""Apply synced settings from a remote server.
|
||||
|
||||
Only applies keys that are in SYNCABLE_KEYS. Sets settings_updated_at
|
||||
to the incoming timestamp (NOT time.time()) to prevent sync loops.
|
||||
"""
|
||||
current = load_settings()
|
||||
for key in SYNCABLE_KEYS:
|
||||
if key in incoming_settings:
|
||||
current[key] = incoming_settings[key]
|
||||
current["settings_updated_at"] = incoming_timestamp
|
||||
save_settings(current)
|
||||
return current
|
||||
|
||||
|
||||
def get_syncable_settings() -> dict:
|
||||
"""Return only syncable settings + the settings_updated_at timestamp."""
|
||||
settings = load_settings()
|
||||
result = {key: settings[key] for key in SYNCABLE_KEYS if key in settings}
|
||||
result["settings_updated_at"] = settings.get("settings_updated_at", 0.0)
|
||||
return result
|
||||
|
||||
|
||||
def load_federation_key() -> str:
|
||||
"""Load the federation key from disk or env-overridden path.
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import muxplex.settings as settings_mod
|
||||
from muxplex.settings import (
|
||||
DEFAULT_SETTINGS,
|
||||
SYNCABLE_KEYS,
|
||||
apply_synced_settings,
|
||||
get_syncable_settings,
|
||||
load_federation_key,
|
||||
load_settings,
|
||||
patch_settings,
|
||||
@@ -918,3 +920,55 @@ def test_syncable_keys_subset_of_default_settings():
|
||||
def test_defaults_include_settings_updated_at():
|
||||
"""DEFAULT_SETTINGS must include settings_updated_at with default 0.0."""
|
||||
assert DEFAULT_SETTINGS["settings_updated_at"] == 0.0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Sync-aware patch_settings + apply_synced_settings (task-6)
|
||||
# ============================================================
|
||||
|
||||
|
||||
def test_patch_settings_bumps_timestamp_for_syncable_key():
|
||||
"""patch_settings bumps settings_updated_at when a syncable key is patched."""
|
||||
patch_settings({"fontSize": 20})
|
||||
settings = load_settings()
|
||||
assert settings["settings_updated_at"] > 0.0
|
||||
|
||||
|
||||
def test_patch_settings_does_not_bump_timestamp_for_nonsyncable_key():
|
||||
"""patch_settings does NOT bump settings_updated_at for non-syncable keys."""
|
||||
patch_settings({"host": "0.0.0.0"})
|
||||
settings = load_settings()
|
||||
assert settings["settings_updated_at"] == 0.0
|
||||
|
||||
|
||||
def test_apply_synced_settings_uses_incoming_timestamp():
|
||||
"""apply_synced_settings sets settings_updated_at to the incoming timestamp."""
|
||||
apply_synced_settings({"fontSize": 18}, 1712600000.0)
|
||||
settings = load_settings()
|
||||
assert settings["fontSize"] == 18
|
||||
assert settings["settings_updated_at"] == 1712600000.0
|
||||
|
||||
|
||||
def test_apply_synced_settings_ignores_nonsyncable_keys():
|
||||
"""apply_synced_settings ignores keys not in SYNCABLE_KEYS."""
|
||||
apply_synced_settings({"fontSize": 18, "host": "evil.com"}, 1712600000.0)
|
||||
settings = load_settings()
|
||||
assert settings["fontSize"] == 18
|
||||
assert settings["host"] == "127.0.0.1" # unchanged from default
|
||||
|
||||
|
||||
def test_get_syncable_settings_returns_only_syncable_keys():
|
||||
"""get_syncable_settings returns only SYNCABLE_KEYS + settings_updated_at."""
|
||||
result = get_syncable_settings()
|
||||
for key in result:
|
||||
assert key in SYNCABLE_KEYS or key == "settings_updated_at"
|
||||
assert "host" not in result
|
||||
assert "settings_updated_at" in result
|
||||
|
||||
|
||||
def test_apply_synced_settings_does_not_use_time_now():
|
||||
"""apply_synced_settings must use the incoming timestamp, not time.time()."""
|
||||
old_ts = 1000.0
|
||||
apply_synced_settings({"fontSize": 16}, old_ts)
|
||||
settings = load_settings()
|
||||
assert settings["settings_updated_at"] == old_ts # exact match, not "close to now"
|
||||
|
||||
Reference in New Issue
Block a user