Fix dashboard template bugs, acme date parsing, wireguard sudoers match, and stale docs
- dashboard.html: Fix zones, leases, wg_status, cert key names, add services var
- server.py: Pass services to dashboard template via _get_service_status()
- lib/acme.py: Fix dead third date format (%Y%m%d%H%M%z) using astimezone(UTC)
- lib/wireguard.py: Add -- separator to cp command to match sudoers rule
- lib/nginx.py: Replace shallow dict.copy() with {**...} for DEFAULT_SSL
- AGENTS.md: Update test count 149 -> 154
- docs/api.md: Rename cert field expiry -> expires_at
This commit is contained in:
@@ -52,6 +52,16 @@ class TestRunAcme:
|
||||
with pytest.raises(RuntimeError):
|
||||
acme._run_acme(["--list"])
|
||||
|
||||
@patch("lib.acme._find_acme")
|
||||
@patch("lib.acme.subprocess.run")
|
||||
def test_no_sudo_prefix(self, mock_run, mock_find):
|
||||
mock_find.return_value = "/usr/local/bin/acme.sh"
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
acme._run_acme(["--list"])
|
||||
cmd = mock_run.call_args[0][0]
|
||||
assert cmd[0] == "/usr/local/bin/acme.sh"
|
||||
assert "sudo" not in cmd
|
||||
|
||||
|
||||
class TestParseListOutput:
|
||||
def test_parses_single_entry(self):
|
||||
@@ -128,3 +138,35 @@ class TestGetCertPaths:
|
||||
assert paths["key"].endswith("example.com/example.com.key")
|
||||
assert paths["ca"].endswith("example.com/ca.cer")
|
||||
assert paths["fullchain"].endswith("example.com/fullchain.cer")
|
||||
|
||||
|
||||
class TestDeployHook:
|
||||
@patch("lib.acme._run_acme")
|
||||
def test_deploy_registers_hook(self, mock_run):
|
||||
acme.deploy("example.com")
|
||||
args = mock_run.call_args[0][0]
|
||||
assert "--deploy" in args
|
||||
assert "-d" in args
|
||||
assert "example.com" in args
|
||||
assert "--deploy-hook" in args
|
||||
assert any("acme-deploy.sh" in arg for arg in args)
|
||||
|
||||
|
||||
class TestHasAutoRenew:
|
||||
@patch("lib.acme.Path.home")
|
||||
def test_true_when_conf_exists(self, mock_home):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
acme_dir = Path(tmpdir) / ".acme.sh"
|
||||
acme_dir.mkdir()
|
||||
conf = acme_dir / "example.com.conf"
|
||||
conf.touch()
|
||||
mock_home.return_value = Path(tmpdir)
|
||||
result = acme._has_auto_renew("example.com")
|
||||
assert result is True
|
||||
conf.unlink()
|
||||
|
||||
@patch("lib.acme.Path.home")
|
||||
def test_false_when_conf_missing(self, mock_home):
|
||||
mock_home.return_value = Path(tempfile.mkdtemp())
|
||||
result = acme._has_auto_renew("nonexistent.com")
|
||||
assert result is False
|
||||
|
||||
+15
-3
@@ -50,7 +50,9 @@ class TestFirewallListZones:
|
||||
|
||||
class TestFirewallZoneDetails:
|
||||
@patch("webui.api.firewall.get_zone_info")
|
||||
def test_success(self, mock_info, client):
|
||||
@patch("webui.api.firewall.get_available_zones")
|
||||
def test_success(self, mock_available, mock_info, client):
|
||||
mock_available.return_value = ["public", "internal"]
|
||||
mock_info.return_value = {"name": "public", "services": ["ssh"]}
|
||||
resp = client.get("/api/firewall/zones/public")
|
||||
assert resp.status_code == 200
|
||||
@@ -208,11 +210,21 @@ class TestDhcpStaticLease:
|
||||
assert resp.status_code == 400
|
||||
|
||||
@patch("webui.api.dhcp.remove_static_lease")
|
||||
def test_remove(self, mock_remove, client):
|
||||
@patch("webui.api.dhcp.get_config")
|
||||
def test_remove(self, mock_get, mock_remove, client):
|
||||
mock_get.return_value = {
|
||||
"dhcp": {"static_leases": [{"mac": "AA:BB:CC", "ip": "10.0.0.5"}]}
|
||||
}
|
||||
mock_remove.return_value = None
|
||||
resp = client.delete("/api/dhcp/static-lease?mac=AA:BB:CC")
|
||||
assert resp.status_code == 200
|
||||
|
||||
@patch("webui.api.dhcp.get_config")
|
||||
def test_remove_not_found(self, mock_get, client):
|
||||
mock_get.return_value = {"dhcp": {"static_leases": []}}
|
||||
resp = client.delete("/api/dhcp/static-lease?mac=AA:BB:CC")
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_remove_missing_mac(self, client):
|
||||
resp = client.delete("/api/dhcp/static-lease")
|
||||
assert resp.status_code == 400
|
||||
@@ -340,7 +352,7 @@ class TestWireguardInitialize:
|
||||
resp = client.post("/api/wireguard/initialize")
|
||||
data = resp.get_json()
|
||||
assert data["ok"] is True
|
||||
assert "private_key" not in data["data"]["interface"]
|
||||
assert data["data"] is None
|
||||
|
||||
|
||||
class TestWireguardGenerateClient:
|
||||
|
||||
@@ -7,14 +7,19 @@ from lib import firewall
|
||||
class TestParseForwardPorts:
|
||||
def test_single_entry(self):
|
||||
result = firewall._parse_forward_ports("port=443/proto=tcp")
|
||||
assert result == ["port=443/proto=tcp"]
|
||||
assert len(result) == 1
|
||||
assert result[0]["port"] == 443
|
||||
assert result[0]["proto"] == "tcp"
|
||||
|
||||
def test_multiple_entries(self):
|
||||
result = firewall._parse_forward_ports(
|
||||
"port=443/proto=tcp port=80/proto=tcp/toaddr=10.0.0.1/toport=8080"
|
||||
)
|
||||
assert len(result) == 2
|
||||
assert result[0] == "port=443/proto=tcp"
|
||||
assert result[0]["port"] == 443
|
||||
assert result[1]["port"] == 80
|
||||
assert result[1]["toaddr"] == "10.0.0.1"
|
||||
assert result[1]["toport"] == 8080
|
||||
|
||||
def test_empty_string(self):
|
||||
assert firewall._parse_forward_ports("") == []
|
||||
|
||||
Reference in New Issue
Block a user