Files
wago-modbustcp-simulator-py/scripts/hostnet_smoke.py
T
2026-10-07 07:40:56 +02:00

100 lines
3.6 KiB
Python

"""Live smoke test of the §15 host network settings page.
Runs the real server + web UI with BOTH gates forced on and an
in-process fake agent (the Windows-dev path), then probes the page in
a real browser: tab visibility, interface cards, an add-address
mutation, the audit view, and the whitelist refusal banner.
"""
from __future__ import annotations
import os
os.environ["WAGO_SIM_HOSTNET"] = "1"
os.environ["WAGO_SIM_IN_DOCKER"] = "1"
os.environ["WAGO_SIM_HOSTNET_SOCKET"] = "unused" # we inject in-process
import asyncio
from wago_sim.hostnet import (FakeBackend, HostNetAgent, HostNetService,
InProcessClient)
from wago_sim.webui.app import build_webui
async def main() -> None:
agent = HostNetAgent(
backend=FakeBackend({
"eth0": ["192.168.1.10/24", "192.168.1.11/24"],
"eth1": ["10.0.0.5/8"],
"lo": ["127.0.0.1/8"],
}),
whitelist=["eth0", "eth1"],
)
service = HostNetService(InProcessClient(agent))
from wago_sim.app import build_app
from wago_sim.modules import DI_8, DI_16, DO_8, DO_16, AI_2, AO_2
sim = build_app(modules=[DI_8, DI_16, DO_8, DO_16, AI_2, AO_2])
webui = build_webui(sim, user="admin", password="simu",
hostnet_service=service)
import uvicorn
config = uvicorn.Config(webui, host="127.0.0.1", port=8099,
log_level="warning")
server = uvicorn.Server(config)
task = asyncio.create_task(server.serve())
for _ in range(100):
if server.started or task.done():
break
await asyncio.sleep(0.02)
assert server.started, "web UI failed to start"
# --- API probe (no browser needed for the data layer) -------------
import httpx
async with httpx.AsyncClient(base_url="http://127.0.0.1:8099") as c:
await c.post("/login", json={"user": "admin", "password": "simu"})
r = await c.get("/api/hostnet")
assert r.status_code == 200, r.text
data = r.json()
assert data["status"]["available"] is True
assert data["status"]["agent"]["whitelist"] == ["eth0", "eth1"]
names = {i["name"] for i in data["interfaces"]}
print("interfaces:", sorted(names))
r = await c.post("/api/hostnet/interfaces/eth0/addresses",
json={"address": "192.168.1.33", "prefix": 24})
assert r.status_code == 200, r.text
print("added 192.168.1.33 to eth0")
r = await c.get("/api/hostnet/audit")
assert "192.168.1.33" in "\n".join(r.json()["lines"])
print("audit:", r.json()["lines"][-2:])
# whitelist refusal surfaces as 403 with a clean message
r = await c.post("/api/hostnet/interfaces/lo/addresses",
json={"address": "127.0.0.2", "prefix": 8})
assert r.status_code == 403, r.text
print("refused lo mutation:", r.json()["detail"][:60], "…")
# restore walks the add back
r = await c.post("/api/hostnet/interfaces/eth0/restore")
assert r.status_code == 200
r = await c.get("/api/hostnet")
eth0 = next(i for i in r.json()["interfaces"] if i["name"] == "eth0")
addrs = [a["address"] for a in eth0["addresses"]]
assert "192.168.1.33" not in addrs
print("restored eth0 to:", addrs)
print("ALL SMOKE CHECKS PASSED")
if os.environ.get("HOSTNET_SMOKE_PERSIST"):
print("PERSIST mode: serving on http://127.0.0.1:8099 until Ctrl+C")
await asyncio.Event().wait()
server.should_exit = True
await asyncio.wait_for(task, timeout=5)
if __name__ == "__main__":
asyncio.run(main())