71803418e5
Shelly device management app with mDNS/subnet discovery, inventory, configuration, and mass operations for Gen1/Gen2+ devices. Includes .gitignore excluding runtime data (device DB, user config), AI conversation history, build artifacts, and common Python/OS patterns.
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
"""Config snapshot unified diff (GetConfig history)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
from shelly_manager.core.config_snapshot_diff import (
|
|
pick_older_newer,
|
|
settings_unified_diff_text,
|
|
sort_snapshots_chronologically,
|
|
)
|
|
from shelly_manager.core.models import ConfigSnapshot
|
|
|
|
|
|
def _snap(
|
|
*,
|
|
sid: str,
|
|
created: datetime,
|
|
label: str,
|
|
settings: dict,
|
|
) -> ConfigSnapshot:
|
|
return ConfigSnapshot(
|
|
id=sid,
|
|
device_id="DEV",
|
|
created_at=created,
|
|
label=label,
|
|
settings=settings,
|
|
)
|
|
|
|
|
|
def test_sort_snapshots_chronologically_oldest_first() -> None:
|
|
t0 = datetime(2026, 3, 20, 0, 37, tzinfo=UTC)
|
|
t1 = datetime(2026, 3, 20, 0, 42, tzinfo=UTC)
|
|
a = _snap(sid="a", created=t1, label="newer", settings={})
|
|
b = _snap(sid="b", created=t0, label="older", settings={})
|
|
out = sort_snapshots_chronologically([a, b])
|
|
assert [x.label for x in out] == ["older", "newer"]
|
|
|
|
|
|
def test_pick_older_newer_swaps_when_user_picks_newer_as_a() -> None:
|
|
"""Oldest-first list: index 0 older, index 1 newer — if user picks A=1 B=0, still get older→newer."""
|
|
t0 = datetime(2026, 3, 20, 0, 37, tzinfo=UTC)
|
|
t1 = datetime(2026, 3, 20, 0, 42, tzinfo=UTC)
|
|
snaps = [
|
|
_snap(sid="b", created=t0, label="older", settings={"x": 1}),
|
|
_snap(sid="a", created=t1, label="newer", settings={"x": 2}),
|
|
]
|
|
older, newer = pick_older_newer(snaps, 1, 0)
|
|
assert older.label == "older"
|
|
assert newer.label == "newer"
|
|
|
|
|
|
def test_settings_unified_diff_text_has_line_separated_structure() -> None:
|
|
"""Regression: diff must contain newlines so each +/- line is separate (not one wrapped blob)."""
|
|
old = {"ble": {"enable": True}}
|
|
new = {"ble": {"enable": False}}
|
|
text = settings_unified_diff_text(old, new, fromfile="older/settings.json", tofile="newer/settings.json")
|
|
assert text.count("\n") >= 4
|
|
lines = text.splitlines()
|
|
assert lines[0].startswith("--- ")
|
|
assert lines[1].startswith("+++ ")
|
|
assert any(line.startswith("@@") for line in lines)
|
|
# Every content/hunk line must be its own line (no embedded @@ mid-line)
|
|
for line in lines:
|
|
assert "\n" not in line
|
|
minus_lines = [ln for ln in lines if ln.startswith("-") and not ln.startswith("---")]
|
|
plus_lines = [ln for ln in lines if ln.startswith("+") and not ln.startswith("+++")]
|
|
assert minus_lines
|
|
assert plus_lines
|
|
|
|
|
|
def test_settings_unified_diff_identical_empty() -> None:
|
|
cfg = {"a": 1}
|
|
assert settings_unified_diff_text(cfg, cfg) == ""
|
|
|
|
|
|
def test_pick_older_newer_same_timestamp_tie_break() -> None:
|
|
t = datetime(2026, 3, 20, 12, 0, tzinfo=UTC)
|
|
s1 = _snap(sid="aaa", created=t, label="first", settings={})
|
|
s2 = _snap(sid="zzz", created=t, label="second", settings={})
|
|
snaps = sort_snapshots_chronologically([s2, s1])
|
|
older, newer = pick_older_newer(snaps, 0, 1)
|
|
assert older.id == "aaa"
|
|
assert newer.id == "zzz"
|