state: applied-config snapshots + per-field pending diffs
- lib/common: stamp_applied() now records a _last_applied_config snapshot alongside the hash; strip_apply_meta() centralizes bookkeeping-key stripping; deep_diff() reports field-level changes - state collectors (dnsmasq/nginx/wireguard/networkd) expose pending_diff so the dashboard can show exactly which fields changed since the last apply (wireguard diff excludes private_key paths) - dashboard pending-changes card renders per-change lines with a generic fallback when no snapshot is recorded - firewall: firewalld built-in zones no longer flagged as unmanaged; public-zone masquerade skipped in pending changes since apply drives it via nftables propagation - schema: PendingChange TypedDict; pending_diff on DnsmasqStatus / WgStatus; tests in test_common.py, test_firewall.py, test_state.py
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Tests for lib/state.py — state store and collect functions."""
|
||||
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
from lib.state import State, state
|
||||
@@ -155,6 +156,64 @@ class TestCollectAll:
|
||||
assert "config" in result
|
||||
assert "leases" in result
|
||||
|
||||
@patch("lib.state.run_proc")
|
||||
def test_collect_dnsmasq_pending_diff(self, mock_proc, tmp_path, monkeypatch):
|
||||
from unittest.mock import Mock
|
||||
|
||||
from lib.common import _APPLY_HASH_KEY, _LAST_APPLIED_CONFIG_KEY
|
||||
from lib.state import _collect_dnsmasq
|
||||
|
||||
(tmp_path / "config" / "dnsmasq").mkdir(parents=True)
|
||||
applied = {
|
||||
"dhcp": {
|
||||
"ranges": [
|
||||
{
|
||||
"interface": "eth1",
|
||||
"start": "10.4.20.101",
|
||||
"end": "10.4.20.200",
|
||||
"lease_time": "1h",
|
||||
"gateway": "10.4.20.1",
|
||||
}
|
||||
],
|
||||
"static_leases": [],
|
||||
},
|
||||
"dns": {"upstreams": ["8.8.8.8"], "domain": None, "custom_records": []},
|
||||
}
|
||||
cfg = {
|
||||
"dhcp": {
|
||||
"ranges": [
|
||||
{
|
||||
"interface": "eth1",
|
||||
"start": "10.4.20.100",
|
||||
"end": "10.4.20.200",
|
||||
"lease_time": "12h",
|
||||
"gateway": "10.4.20.1",
|
||||
}
|
||||
],
|
||||
"static_leases": [],
|
||||
},
|
||||
"dns": {
|
||||
"upstreams": ["8.8.8.8", "1.1.1.1"],
|
||||
"domain": None,
|
||||
"custom_records": [],
|
||||
},
|
||||
_LAST_APPLIED_CONFIG_KEY: applied,
|
||||
_APPLY_HASH_KEY: "stale-hash",
|
||||
}
|
||||
(tmp_path / "config" / "dnsmasq" / "config.json").write_text(json.dumps(cfg))
|
||||
monkeypatch.setattr("lib.state.PROJECT_DIR", tmp_path)
|
||||
# service check -> active; lease file read -> no lines
|
||||
mock_proc.return_value = Mock(stdout="active\n", returncode=0)
|
||||
|
||||
result = _collect_dnsmasq()
|
||||
assert result["status"]["pending_changes"] is True
|
||||
paths = {d["path"] for d in result["status"]["pending_diff"]}
|
||||
assert "dhcp.ranges[0].start" in paths
|
||||
assert "dhcp.ranges[0].lease_time" in paths
|
||||
# Apply metadata must not leak into the returned config.
|
||||
assert _LAST_APPLIED_CONFIG_KEY not in result["config"]
|
||||
assert _APPLY_HASH_KEY not in result["config"]
|
||||
|
||||
|
||||
class TestCollectFailure:
|
||||
def test_state_clears_on_failure(self):
|
||||
|
||||
Reference in New Issue
Block a user