@@ -0,0 +1,185 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user