Files
DECNET/tests/config/test_config.py
anti f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00

145 lines
4.4 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Tests for decnet.config — Pydantic models, save/load/clear state.
Covers the uncovered lines: validators, save_state, load_state, clear_state.
"""
import pytest
import decnet.config as config_module
from decnet.config import (
DeckyConfig,
DecnetConfig,
save_state,
load_state,
clear_state,
)
# ---------------------------------------------------------------------------
# DeckyConfig validator
# ---------------------------------------------------------------------------
class TestDeckyConfig:
def _base(self, **kwargs):
defaults = dict(
name="decky-01", ip="192.168.1.10", services=["ssh"],
distro="debian", base_image="debian", hostname="host-01",
)
defaults.update(kwargs)
return defaults
def test_valid_decky(self):
d = DeckyConfig(**self._base())
assert d.name == "decky-01"
def test_empty_services_raises(self):
with pytest.raises(Exception, match="at least 1 item"):
DeckyConfig(**self._base(services=[]))
def test_multiple_services_ok(self):
d = DeckyConfig(**self._base(services=["ssh", "smb", "rdp"]))
assert len(d.services) == 3
# ---------------------------------------------------------------------------
# DecnetConfig validator
# ---------------------------------------------------------------------------
class TestDecnetConfig:
def _base_decky(self):
return DeckyConfig(
name="d", ip="10.0.0.2", services=["ssh"],
distro="debian", base_image="debian", hostname="h",
)
def test_valid_config(self):
cfg = DecnetConfig(
mode="unihost", interface="eth0",
subnet="10.0.0.0/24", gateway="10.0.0.1",
deckies=[self._base_decky()],
)
assert cfg.mode == "unihost"
def test_log_file_field(self):
cfg = DecnetConfig(
mode="unihost", interface="eth0",
subnet="10.0.0.0/24", gateway="10.0.0.1",
deckies=[self._base_decky()],
log_file="/var/log/decnet/decnet.log",
)
assert cfg.log_file == "/var/log/decnet/decnet.log"
def test_log_file_defaults_to_none(self):
cfg = DecnetConfig(
mode="unihost", interface="eth0",
subnet="10.0.0.0/24", gateway="10.0.0.1",
deckies=[self._base_decky()],
)
assert cfg.log_file is None
# ---------------------------------------------------------------------------
# save_state / load_state / clear_state
# ---------------------------------------------------------------------------
@pytest.fixture(autouse=True)
def patch_state_file(tmp_path, monkeypatch):
monkeypatch.setattr(config_module, "STATE_FILE", tmp_path / "decnet-state.json")
def _sample_config():
return DecnetConfig(
mode="unihost", interface="eth0",
subnet="192.168.1.0/24", gateway="192.168.1.1",
deckies=[
DeckyConfig(
name="decky-01", ip="192.168.1.10", services=["ssh"],
distro="debian", base_image="debian", hostname="host-01",
)
],
)
def test_save_and_load_state(tmp_path):
cfg = _sample_config()
compose = tmp_path / "docker-compose.yml"
save_state(cfg, compose)
result = load_state()
assert result is not None
loaded_cfg, loaded_compose = result
assert loaded_cfg.mode == "unihost"
assert loaded_cfg.deckies[0].name == "decky-01"
assert loaded_compose == compose
def test_load_state_returns_none_when_missing(tmp_path, monkeypatch):
monkeypatch.setattr(config_module, "STATE_FILE", tmp_path / "nonexistent.json")
assert load_state() is None
def test_clear_state(tmp_path):
cfg = _sample_config()
save_state(cfg, tmp_path / "compose.yml")
assert config_module.STATE_FILE.exists()
clear_state()
assert not config_module.STATE_FILE.exists()
def test_clear_state_noop_when_missing(tmp_path, monkeypatch):
monkeypatch.setattr(config_module, "STATE_FILE", tmp_path / "nonexistent.json")
clear_state() # should not raise
def test_state_roundtrip_preserves_all_fields(tmp_path):
cfg = _sample_config()
cfg.deckies[0].archetype = "workstation"
cfg.deckies[0].mutate_interval = 45
compose = tmp_path / "compose.yml"
save_state(cfg, compose)
loaded_cfg, _ = load_state()
assert loaded_cfg.deckies[0].archetype == "workstation"
assert loaded_cfg.deckies[0].mutate_interval == 45