100 lines
3.6 KiB
Python
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()) |