186 lines
7.9 KiB
Python
186 lines
7.9 KiB
Python
import json
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
from app import create_app, db
|
|
from app.models import WebUser
|
|
from config import TestingConfig
|
|
|
|
|
|
class NetworkSettingsApiTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self._tmp = tempfile.mkdtemp(prefix="wesp-network-settings-")
|
|
self._net_path = os.path.join(self._tmp, "wesp_network_settings.json")
|
|
self._sec_path = os.path.join(self._tmp, "wesp_security_settings.json")
|
|
self._periph_path = os.path.join(self._tmp, "wesp-peripherals.jsonl")
|
|
os.environ["WESP_NETWORK_SETTINGS_PATH"] = self._net_path
|
|
os.environ["WESP_SECURITY_SETTINGS_PATH"] = self._sec_path
|
|
os.environ["WESP_ADMIN_PERIPHERAL_LOG_PATH"] = self._periph_path
|
|
self._prev_hostname = os.environ.pop("WESP_NETWORK_LOCAL_HOSTNAME", None)
|
|
self._prev_pub = os.environ.pop("WESP_KIOSK_PUBLIC_BASE_URL", None)
|
|
self._prev_mdns = os.environ.pop("WESP_MDNS_ENABLED", None)
|
|
|
|
class C(TestingConfig):
|
|
BASE_DIR = self._tmp
|
|
DATA_DIR = os.path.join(self._tmp, "data")
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(self._tmp, 'recipes_test.db')}"
|
|
SQLALCHEMY_BINDS = {"reports": f"sqlite:///{os.path.join(self._tmp, 'reports_test.db')}"}
|
|
AUTH_LOGIN = "admin"
|
|
AUTH_PASSWORD = "admin-secret"
|
|
SECRET_KEY = "unit-test-secret-key-not-default-32chars"
|
|
|
|
self.app = create_app(C)
|
|
self.app.config["WESP_ADMIN_PERIPHERAL_LOG_PATH"] = self._periph_path
|
|
self.client = self.app.test_client()
|
|
self.ctx = self.app.app_context()
|
|
self.ctx.push()
|
|
os.makedirs(C.DATA_DIR, exist_ok=True)
|
|
db.create_all()
|
|
db.session.add(
|
|
WebUser(
|
|
login="admin",
|
|
password_hash=generate_password_hash("admin-secret"),
|
|
is_superuser=True,
|
|
)
|
|
)
|
|
db.session.commit()
|
|
self._login()
|
|
|
|
def tearDown(self) -> None:
|
|
db.session.remove()
|
|
db.drop_all()
|
|
self.ctx.pop()
|
|
os.environ.pop("WESP_NETWORK_SETTINGS_PATH", None)
|
|
os.environ.pop("WESP_SECURITY_SETTINGS_PATH", None)
|
|
os.environ.pop("WESP_ADMIN_PERIPHERAL_LOG_PATH", None)
|
|
if self._prev_hostname is not None:
|
|
os.environ["WESP_NETWORK_LOCAL_HOSTNAME"] = self._prev_hostname
|
|
else:
|
|
os.environ.pop("WESP_NETWORK_LOCAL_HOSTNAME", None)
|
|
if self._prev_pub is not None:
|
|
os.environ["WESP_KIOSK_PUBLIC_BASE_URL"] = self._prev_pub
|
|
else:
|
|
os.environ.pop("WESP_KIOSK_PUBLIC_BASE_URL", None)
|
|
if self._prev_mdns is not None:
|
|
os.environ["WESP_MDNS_ENABLED"] = self._prev_mdns
|
|
else:
|
|
os.environ.pop("WESP_MDNS_ENABLED", None)
|
|
|
|
def _login(self) -> None:
|
|
r = self.client.post("/api/auth/login", json={"login": "admin", "password": "admin-secret"})
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
|
|
def test_get_network_defaults(self) -> None:
|
|
r = self.client.get("/api/admin/network-settings")
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
body = r.get_json() or {}
|
|
net = body.get("network") or {}
|
|
self.assertEqual(net.get("default_local_hostname"), "komton_srv_1.local")
|
|
self.assertIn("komton_srv_1.local", net.get("local_hostname") or "")
|
|
self.assertIn("detected", net)
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_patch_network_settings_persists_and_applies(self, mock_reload) -> None:
|
|
r = self.client.patch(
|
|
"/api/admin/network-settings",
|
|
json={
|
|
"local_hostname": "komton_srv_2.local",
|
|
"public_base_url": "http://komton_srv_2.local",
|
|
"mdns_enabled": True,
|
|
},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
self.assertEqual(self.app.config.get("KIOSK_PUBLIC_BASE_URL"), "http://komton_srv_2.local")
|
|
self.assertEqual(self.app.config.get("WESP_NETWORK_LOCAL_HOSTNAME"), "komton_srv_2.local")
|
|
self.assertTrue(self.app.config.get("WESP_MDNS_ENABLED"))
|
|
mock_reload.assert_called_once()
|
|
with open(self._net_path, encoding="utf-8") as f:
|
|
stored = json.load(f)
|
|
self.assertEqual(stored.get("local_hostname"), "komton_srv_2.local")
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_migrate_public_url_from_security_file(self, _mock_reload) -> None:
|
|
with open(self._sec_path, "w", encoding="utf-8") as f:
|
|
json.dump({"kiosk_public_base_url": "http://legacy.local:5000"}, f)
|
|
r = self.client.get("/api/admin/network-settings")
|
|
net = (r.get_json() or {}).get("network") or {}
|
|
self.assertEqual(net.get("public_base_url"), "http://legacy.local:5000")
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_patch_writes_network_journal(self, mock_reload) -> None:
|
|
r = self.client.patch(
|
|
"/api/admin/network-settings",
|
|
json={"local_hostname": "komton_srv_1.local", "mdns_enabled": True},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
self.assertTrue(os.path.isfile(self._periph_path))
|
|
with open(self._periph_path, encoding="utf-8") as f:
|
|
body = f.read()
|
|
self.assertIn("network_settings_saved", body)
|
|
ev = self.client.get("/api/admin/peripheral-events?component=network")
|
|
self.assertEqual(ev.status_code, 200)
|
|
events = (ev.get_json() or {}).get("events") or []
|
|
self.assertTrue(any(e.get("code") == "network_settings_saved" for e in events))
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_invalid_hostname_rejected(self, _mock_reload) -> None:
|
|
r = self.client.patch(
|
|
"/api/admin/network-settings",
|
|
json={"local_hostname": "1"},
|
|
)
|
|
self.assertEqual(r.status_code, 400)
|
|
self.assertIn("цифр", (r.get_json() or {}).get("message", ""))
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_patch_public_url_only_skips_mdns_reload(self, mock_reload) -> None:
|
|
r = self.client.patch(
|
|
"/api/admin/network-settings",
|
|
json={"public_base_url": "http://komton_srv_1.local"},
|
|
)
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
mock_reload.assert_not_called()
|
|
|
|
@patch("app.routes.admin.reload_mdns")
|
|
def test_clear_network_journal_only(self, _mock_reload) -> None:
|
|
from app.services.admin_peripheral_monitor import append_peripheral_event
|
|
|
|
append_peripheral_event(
|
|
self.app,
|
|
component="network",
|
|
code="mdns_started",
|
|
message="net event",
|
|
force=True,
|
|
)
|
|
append_peripheral_event(
|
|
self.app,
|
|
component="hx711",
|
|
code="hx711_read_error",
|
|
message="hw event",
|
|
force=True,
|
|
)
|
|
r = self.client.delete("/api/admin/peripheral-events?component=network")
|
|
self.assertEqual(r.status_code, 200, r.get_data(as_text=True))
|
|
ev = self.client.get("/api/admin/peripheral-events?component=network")
|
|
self.assertEqual(ev.status_code, 200)
|
|
net_events = (ev.get_json() or {}).get("events") or []
|
|
self.assertEqual(net_events, [])
|
|
all_ev = self.client.get("/api/admin/peripheral-events?limit=20")
|
|
codes = [e.get("code") for e in (all_ev.get_json() or {}).get("events") or []]
|
|
self.assertIn("hx711_read_error", codes)
|
|
|
|
def test_env_lock_blocks_patch(self) -> None:
|
|
os.environ["WESP_KIOSK_PUBLIC_BASE_URL"] = "http://env.local:5000"
|
|
r = self.client.patch(
|
|
"/api/admin/network-settings",
|
|
json={"public_base_url": "http://other.local:5000"},
|
|
)
|
|
self.assertEqual(r.status_code, 400)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|