import os from pathlib import Path from unittest.mock import patch import pytest @pytest.fixture def client(): with patch("lib.logging.setup_logging"): from webui.server import app app.config["TESTING"] = True return app.test_client() class TestSPARoutes: def test_root_serves_index(self, client): resp = client.get("/") assert resp.status_code == 200 assert b'id="app"' in resp.data def test_spa_catch_all_serves_index(self, client): resp = client.get("/dashboard") assert resp.status_code == 200 assert b"index.html" in resp.data or b'id="app"' in resp.data def test_spa_catch_all_other_page(self, client): resp = client.get("/zones") assert resp.status_code == 200 def test_api_routes_still_work(self, client): resp = client.get("/api/firewall/zones") assert resp.status_code in (200, 502, 503) class TestWsUrlGeneration: def test_ws_url_ipv4_host(self, client): resp = client.get("/", headers={"Host": "192.168.1.1:9090"}) assert b"ws://192.168.1.1:9090/ws" in resp.data def test_ws_url_ipv6_host(self, client): resp = client.get("/", headers={"Host": "[::1]:9090"}) assert b"ws://[::1]:9090/ws" in resp.data class TestApiStatusAll: @patch("webui.server.get") def test_success(self, mock_get, client): mock_get.return_value = {"firewall": {"zones": {}}, "dnsmasq": {}} resp = client.get("/api/status/all") assert resp.status_code == 200 data = resp.get_json() assert data["ok"] is True assert "firewall" in data["data"] @patch("webui.server.get") def test_error(self, mock_get, client): mock_get.side_effect = RuntimeError("connection refused") resp = client.get("/api/status/all") assert resp.status_code == 500 data = resp.get_json() assert data["ok"] is False class TestBlueprintsRegistered: def test_all_blueprints_registered(self, client): from webui.server import BLUEPRINTS assert len(BLUEPRINTS) == 7 names = [name for name, _ in BLUEPRINTS] assert "firewall" in names assert "network" in names assert "dhcp" in names assert "proxy" in names assert "certs" in names assert "wireguard" in names assert "logs" in names class TestGroupWriteHandler: def test_creates_file_with_group_write(self, tmp_path: Path) -> None: """GroupWriteHandler creates new log files with group-write (0o664).""" import contextlib from logging.handlers import RotatingFileHandler log_file = tmp_path / "test.log" old = os.umask(0o022) try: class GroupWriteHandler(RotatingFileHandler): def _open(self): with contextlib.suppress(OSError): os.chmod(self.baseFilename, 0o664) saved = os.umask(0o002) try: fd = os.open( self.baseFilename, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o664, ) finally: os.umask(saved) return os.fdopen(fd, "a", errors="backslashreplace") fh = GroupWriteHandler(str(log_file)) fh.close() finally: os.umask(old) mode = os.stat(log_file).st_mode & 0o777 assert mode == 0o664, f"Expected 0o664, got {oct(mode)}"