Files
vacuum-wall/tests/test_api.py
T
mteehan d9797b6dac Migrate declarative configs to config/ dir and remove hardcoded paths
Replace all hardcoded /home/wall/vacuum-wall paths in lib/ with Path(__file__).resolve()
auto-discovery. Move config files from data/ to config/<subsystem>/config.json.
ACME now uses ACME_HOME env var and data/acme/ for cert storage. Systemd units
and sudoers use {{ USER_NAME }}, {{ PROJECT_DIR }}, {{ ACME_HOME }} Jinja2
template variables for install-time substitution. Remove sys.path.insert boot
strap from test files.
2026-05-14 03:31:13 +00:00

379 lines
13 KiB
Python

from unittest.mock import patch
import pytest
from webui.api.certs import bp as certs_bp
from webui.api.dhcp import bp as dhcp_bp
from webui.api.firewall import bp
from webui.api.proxy import bp as proxy_bp
from webui.api.wireguard import bp as wg_bp
@pytest.fixture
def client():
from flask import Flask
app = Flask(__name__)
app.register_blueprint(bp, url_prefix="/api/firewall")
app.register_blueprint(dhcp_bp, url_prefix="/api/dhcp")
app.register_blueprint(proxy_bp, url_prefix="/api/proxy")
app.register_blueprint(certs_bp, url_prefix="/api/certs")
app.register_blueprint(wg_bp, url_prefix="/api/wireguard")
return app.test_client()
class TestFirewallListZones:
@patch("webui.api.firewall.get_active_zones")
@patch("webui.api.firewall.get_available_zones")
def test_success(self, mock_available, mock_active, client):
mock_active.return_value = {"public": ["eth0"]}
mock_available.return_value = ["public", "internal"]
resp = client.get("/api/firewall/zones")
assert resp.status_code == 200
data = resp.get_json()
assert data["ok"] is True
assert "public" in data["data"]["active"]
@patch("webui.api.firewall.get_active_zones")
def test_runtime_error(self, mock_active, client):
mock_active.side_effect = RuntimeError("no sudo")
resp = client.get("/api/firewall/zones")
assert resp.status_code == 500
data = resp.get_json()
assert data["ok"] is False
class TestFirewallZoneDetails:
@patch("webui.api.firewall.get_zone_info")
@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
data = resp.get_json()
assert data["data"]["name"] == "public"
class TestFirewallCreateZone:
@patch("webui.api.firewall.create_zone")
@patch("webui.api.firewall.get_available_zones")
def test_success(self, mock_zones, mock_create, client):
mock_zones.return_value = ["public", "internal"]
mock_create.return_value = None
resp = client.post(
"/api/firewall/zones",
json={"name": "dmz", "target": "default"},
)
assert resp.status_code == 200
data = resp.get_json()
assert data["ok"] is True
def test_missing_name(self, client):
resp = client.post(
"/api/firewall/zones",
json={"target": "default"},
)
assert resp.status_code == 400
data = resp.get_json()
assert data["ok"] is False
class TestFirewallDeleteZone:
@patch("webui.api.firewall.delete_zone")
@patch("webui.api.firewall.get_available_zones")
def test_success(self, mock_zones, mock_delete, client):
mock_zones.return_value = ["public", "dmz"]
mock_delete.return_value = None
resp = client.delete("/api/firewall/zones/dmz")
assert resp.status_code == 200
@patch("webui.api.firewall.get_available_zones")
def test_not_found(self, mock_zones, client):
mock_zones.return_value = ["public"]
resp = client.delete("/api/firewall/zones/dmz")
assert resp.status_code == 404
class TestFirewallRichRules:
@patch("webui.api.firewall.add_rich_rule")
def test_add(self, mock_add, client):
mock_add.return_value = None
resp = client.post(
"/api/firewall/rich-rules",
json={
"zone": "public",
"rule": 'rule family="ipv4" port protocol="tcp" port="443" accept',
},
)
assert resp.status_code == 200
data = resp.get_json()
assert data["ok"] is True
def test_missing_fields(self, client):
resp = client.post("/api/firewall/rich-rules", json={})
assert resp.status_code == 400
@patch("webui.api.firewall.get_rich_rules")
def test_list(self, mock_list, client):
mock_list.return_value = ["rule1", "rule2"]
resp = client.get("/api/firewall/rich-rules/public")
assert resp.status_code == 200
data = resp.get_json()
assert data["data"] == ["rule1", "rule2"]
class TestFirewallServices:
@patch("webui.api.firewall.get_services")
def test_list(self, mock_services, client):
mock_services.return_value = ["ssh", "http", "dns"]
resp = client.get("/api/firewall/services")
assert resp.status_code == 200
assert resp.get_json()["data"] == ["ssh", "http", "dns"]
class TestFirewallInterfaces:
@patch("webui.api.firewall.get_interfaces")
def test_list(self, mock_ifaces, client):
mock_ifaces.return_value = ["eth0", "eth1"]
resp = client.get("/api/firewall/interfaces")
assert resp.status_code == 200
assert resp.get_json()["data"] == ["eth0", "eth1"]
class TestFirewallMasquerade:
@patch("webui.api.firewall.set_masquerade")
def test_enable(self, mock_set, client):
mock_set.return_value = None
resp = client.post(
"/api/firewall/masquerade",
json={"zone": "internal", "enable": True},
)
assert resp.status_code == 200
def test_missing_fields(self, client):
resp = client.post("/api/firewall/masquerade", json={})
assert resp.status_code == 400
class TestFirewallForwardPort:
@patch("webui.api.firewall.add_forward_port")
def test_add(self, mock_add, client):
mock_add.return_value = None
resp = client.post(
"/api/firewall/forward-port",
json={"zone": "public", "port": 443, "proto": "tcp"},
)
assert resp.status_code == 200
def test_missing_fields(self, client):
resp = client.post(
"/api/firewall/forward-port",
json={"zone": "public"},
)
assert resp.status_code == 400
class TestDhcpConfig:
@patch("webui.api.dhcp.get_config")
def test_get(self, mock_get, client):
mock_get.return_value = {"dhcp": {}, "dns": {}}
resp = client.get("/api/dhcp/config")
assert resp.status_code == 200
assert resp.get_json()["ok"] is True
def test_post_invalid_body(self, client):
resp = client.post(
"/api/dhcp/config", data="not json", content_type="text/plain"
)
data = resp.get_json()
assert data is not None
class TestDhcpStaticLease:
@patch("webui.api.dhcp.add_static_lease")
def test_add(self, mock_add, client):
mock_add.return_value = None
resp = client.post(
"/api/dhcp/static-lease",
json={"mac": "AA:BB:CC", "ip": "10.0.0.5"},
)
assert resp.status_code == 200
def test_missing_mac(self, client):
resp = client.post("/api/dhcp/static-lease", json={"ip": "10.0.0.5"})
assert resp.status_code == 400
@patch("webui.api.dhcp.remove_static_lease")
@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
class TestDhcpDnsRecord:
@patch("webui.api.dhcp.add_dns_record")
def test_add(self, mock_add, client):
mock_add.return_value = None
resp = client.post(
"/api/dhcp/dns-record",
json={"name": "host.local", "address": "10.0.0.10"},
)
assert resp.status_code == 200
def test_missing_fields(self, client):
resp = client.post("/api/dhcp/dns-record", json={})
assert resp.status_code == 400
class TestProxyDomains:
@patch("webui.api.proxy.get_domains")
def test_list(self, mock_get, client):
mock_get.return_value = []
resp = client.get("/api/proxy/domains")
assert resp.status_code == 200
@patch("webui.api.proxy.add_domain")
def test_add(self, mock_add, client):
mock_add.return_value = None
resp = client.post(
"/api/proxy/domains",
json={"domain": "ex.com", "backend_host": "10.0.0.1", "backend_port": 80},
)
assert resp.status_code == 200
def test_add_missing_domain(self, client):
resp = client.post("/api/proxy/domains", json={})
assert resp.status_code == 400
class TestProxyApply:
@patch("webui.api.proxy.apply")
def test_apply(self, mock_apply, client):
mock_apply.return_value = None
resp = client.post("/api/proxy/apply")
assert resp.status_code == 200
class TestCertsList:
@patch("webui.api.certs.list_certs")
def test_list(self, mock_list, client):
mock_list.return_value = []
resp = client.get("/api/certs/list")
assert resp.status_code == 200
@patch("webui.api.certs.get_cert_info")
def test_details_not_found(self, mock_info, client):
mock_info.side_effect = ValueError("not found")
resp = client.get("/api/certs/example.com")
assert resp.status_code == 404
class TestCertsIssue:
def test_missing_domain(self, client):
resp = client.post("/api/certs/issue", json={})
assert resp.status_code == 400
class TestCertsEmail:
def test_missing_email(self, client):
resp = client.post("/api/certs/email", json={})
assert resp.status_code == 400
class TestWireguardConfig:
@patch("webui.api.wireguard.get_config")
def test_get(self, mock_get, client):
mock_get.return_value = {
"interface": {"name": "wg0", "private_key": "secret"},
"peers": {},
}
resp = client.get("/api/wireguard/config")
data = resp.get_json()
assert data["ok"] is True
assert "private_key" not in data["data"]["interface"]
@patch("webui.api.wireguard.save_config")
def test_post(self, mock_save, client):
mock_save.return_value = None
resp = client.post("/api/wireguard/config", json={"peers": {}})
assert resp.status_code == 200
class TestWireguardPeers:
@patch("webui.api.wireguard.get_peers")
def test_list(self, mock_get, client):
mock_get.return_value = []
resp = client.get("/api/wireguard/peers")
assert resp.status_code == 200
@patch("webui.api.wireguard.add_peer")
def test_add(self, mock_add, client):
mock_add.return_value = {"public_key": "pub", "private_key": "priv"}
resp = client.post(
"/api/wireguard/add-peer",
json={"name": "client1"},
)
data = resp.get_json()
assert data["ok"] is True
assert "private_key" not in data["data"]
def test_add_missing_name(self, client):
resp = client.post("/api/wireguard/add-peer", json={})
assert resp.status_code == 400
class TestWireguardInitialize:
@patch("webui.api.wireguard.initialize")
def test_initialize(self, mock_init, client):
mock_init.return_value = {
"interface": {"name": "wg0", "private_key": "priv"},
"peers": {},
}
resp = client.post("/api/wireguard/initialize")
data = resp.get_json()
assert data["ok"] is True
assert data["data"] is None
class TestWireguardGenerateClient:
def test_missing_name(self, client):
resp = client.post("/api/wireguard/generate-client", json={})
assert resp.status_code == 400
class TestWireguardStatus:
@patch("webui.api.wireguard.status")
def test_get(self, mock_status, client):
mock_status.return_value = {"up": True, "interface": {}, "peers": []}
resp = client.get("/api/wireguard/status")
assert resp.status_code == 200
class TestResponseHelpers:
@patch("webui.api.firewall.get_active_zones")
@patch("webui.api.firewall.get_available_zones")
def test_error_response_format(self, mock_a, mock_b, client):
mock_a.side_effect = RuntimeError("fail")
resp = client.get("/api/firewall/zones")
data = resp.get_json()
assert "error" in data
assert "ok" in data
assert data["ok"] is False