| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- """Tests for daemon.config — Config.load() and _get_mac_id()."""
- import pytest
- from daemon.config import Config, _get_mac_id
- class TestConfigLoad:
- """Config.load() reads env vars and validates required fields."""
- def test_load_with_all_env_vars(self, monkeypatch):
- monkeypatch.setenv("SPOOLBUDDY_BACKEND_URL", "http://10.0.0.1:5000")
- monkeypatch.setenv("SPOOLBUDDY_API_KEY", "test-key-123")
- monkeypatch.setenv("SPOOLBUDDY_DEVICE_ID", "my-device")
- monkeypatch.setenv("SPOOLBUDDY_HOSTNAME", "my-host")
- cfg = Config.load()
- assert cfg.backend_url == "http://10.0.0.1:5000"
- assert cfg.api_key == "test-key-123"
- assert cfg.device_id == "my-device"
- assert cfg.hostname == "my-host"
- def test_load_missing_backend_url_raises(self, monkeypatch):
- monkeypatch.delenv("SPOOLBUDDY_BACKEND_URL", raising=False)
- monkeypatch.setenv("SPOOLBUDDY_API_KEY", "key")
- with pytest.raises(RuntimeError, match="SPOOLBUDDY_BACKEND_URL is required"):
- Config.load()
- def test_load_missing_api_key_raises(self, monkeypatch):
- monkeypatch.setenv("SPOOLBUDDY_BACKEND_URL", "http://localhost:5000")
- monkeypatch.delenv("SPOOLBUDDY_API_KEY", raising=False)
- with pytest.raises(RuntimeError, match="SPOOLBUDDY_API_KEY is required"):
- Config.load()
- def test_load_both_missing_raises_backend_url_first(self, monkeypatch):
- monkeypatch.delenv("SPOOLBUDDY_BACKEND_URL", raising=False)
- monkeypatch.delenv("SPOOLBUDDY_API_KEY", raising=False)
- with pytest.raises(RuntimeError, match="SPOOLBUDDY_BACKEND_URL"):
- Config.load()
- def test_load_defaults_device_id_from_mac(self, monkeypatch, tmp_path):
- monkeypatch.setenv("SPOOLBUDDY_BACKEND_URL", "http://localhost:5000")
- monkeypatch.setenv("SPOOLBUDDY_API_KEY", "key")
- monkeypatch.delenv("SPOOLBUDDY_DEVICE_ID", raising=False)
- monkeypatch.delenv("SPOOLBUDDY_HOSTNAME", raising=False)
- # Mock /sys/class/net with a fake interface
- net_dir = tmp_path / "sys" / "class" / "net"
- eth0 = net_dir / "eth0"
- eth0.mkdir(parents=True)
- (eth0 / "address").write_text("aa:bb:cc:dd:ee:ff\n")
- import daemon.config as config_mod
- monkeypatch.setattr(config_mod, "_get_mac_id", lambda: "sb-aabbccddeeff")
- cfg = Config.load()
- assert cfg.device_id == "sb-aabbccddeeff"
- def test_load_defaults_hostname_from_socket(self, monkeypatch):
- monkeypatch.setenv("SPOOLBUDDY_BACKEND_URL", "http://localhost:5000")
- monkeypatch.setenv("SPOOLBUDDY_API_KEY", "key")
- monkeypatch.setenv("SPOOLBUDDY_DEVICE_ID", "dev-1")
- monkeypatch.delenv("SPOOLBUDDY_HOSTNAME", raising=False)
- cfg = Config.load()
- # Should fall back to socket.gethostname()
- import socket
- assert cfg.hostname == socket.gethostname()
- def test_load_default_intervals(self, monkeypatch):
- monkeypatch.setenv("SPOOLBUDDY_BACKEND_URL", "http://localhost:5000")
- monkeypatch.setenv("SPOOLBUDDY_API_KEY", "key")
- monkeypatch.setenv("SPOOLBUDDY_DEVICE_ID", "dev-1")
- cfg = Config.load()
- assert cfg.nfc_poll_interval == 0.3
- assert cfg.scale_read_interval == 0.1
- assert cfg.scale_report_interval == 1.0
- assert cfg.heartbeat_interval == 10.0
- assert cfg.tare_offset == 0
- assert cfg.calibration_factor == 1.0
- class TestGetMacId:
- """_get_mac_id() reads MAC from /sys/class/net."""
- def test_reads_first_non_lo_interface(self, monkeypatch, tmp_path):
- net_dir = tmp_path / "sys" / "class" / "net"
- lo = net_dir / "lo"
- lo.mkdir(parents=True)
- (lo / "address").write_text("00:00:00:00:00:00\n")
- eth0 = net_dir / "eth0"
- eth0.mkdir(parents=True)
- (eth0 / "address").write_text("de:ad:be:ef:00:01\n")
- from pathlib import Path
- import daemon.config as config_mod
- monkeypatch.setattr(
- config_mod, "Path", lambda p: tmp_path / "sys" / "class" / "net" if p == "/sys/class/net" else Path(p)
- )
- _get_mac_id()
- def test_skips_loopback(self, monkeypatch, tmp_path):
- """lo interface is skipped even if it has a MAC — result is uuid fallback."""
- # When only lo exists and /sys/class/net points to our tmp dir,
- # _get_mac_id should skip lo and fall back to uuid.
- # We test the real function by patching Path at the module level.
- from pathlib import Path
- import daemon.config as config_mod
- net_dir = tmp_path / "net"
- lo = net_dir / "lo"
- lo.mkdir(parents=True)
- (lo / "address").write_text("00:00:00:00:00:00\n")
- real_path = Path
- def fake_path(p):
- if p == "/sys/class/net":
- return real_path(net_dir)
- return real_path(p)
- monkeypatch.setattr(config_mod, "Path", fake_path)
- result = _get_mac_id()
- assert result.startswith("sb-")
- assert len(result) == 15 # "sb-" + 12 hex uuid chars
- def test_skips_all_zero_mac(self, monkeypatch, tmp_path):
- """Interfaces with all-zero MAC are skipped."""
- net_dir = tmp_path / "net"
- eth0 = net_dir / "eth0"
- eth0.mkdir(parents=True)
- (eth0 / "address").write_text("00:00:00:00:00:00\n")
- def test_fallback_to_uuid_when_no_interfaces(self, monkeypatch):
- """When /sys/class/net doesn't exist, falls back to uuid."""
- from pathlib import Path
- import daemon.config as config_mod
- # Make Path("/sys/class/net") point to nonexistent dir
- real_path = Path
- def fake_path(p):
- if p == "/sys/class/net":
- return real_path("/nonexistent/path/that/does/not/exist")
- return real_path(p)
- monkeypatch.setattr(config_mod, "Path", fake_path)
- result = _get_mac_id()
- assert result.startswith("sb-")
- assert len(result) == 15 # "sb-" + 12 hex chars
|