Initial commit: Shelly Manager with Textual CLI, Streamlit UI, and comprehensive .gitignore
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.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user