# The web UI against a router's modules, without installing anything: one # ordinary test node with the settings module, and a client on the LAN. What # needs an installed system and on-device builds (apply, confirm, rollback) # is tested in apply.nix; this one takes a minute instead of ten. { self, pkgs }: let common = import ./common.nix; settings = pkgs.writeText "settings.json" ( builtins.toJSON { system = { hostName = "router"; stateVersion = "26.11"; }; router = { wan.interface = "eth1"; lans.main = { interface = "eth2"; internet = true; ipv4.address = "192.168.10.1"; dhcp.pool = { start = "192.168.10.100"; end = "192.168.10.199"; }; }; }; } ); in pkgs.testers.runNixOSTest { name = "router-webui"; nodes = { router = { imports = [ self.nixosModules.router self.nixosModules.settings ]; routerSettings.file = settings; virtualisation.vlans = [ 1 2 ]; # What the device has at /var/lib/router/config. systemd.tmpfiles.rules = [ "d /var/lib/router/config 0700 root root -" "C /var/lib/router/config/settings.json 0600 root root - ${settings}" ]; }; client = { lib, ... }: { imports = [ common.tools ]; virtualisation.vlans = [ 2 ]; networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ { address = "192.168.10.50"; prefixLength = 24; } ]; }; }; testScript = '' ui = "https://192.168.10.1" cfg = "/var/lib/router/config/settings.json" def curl(args): return client.succeed(f"curl -sk -b /tmp/jar -c /tmp/jar {args}") def csrf(path): return curl(f"{ui}{path} | grep -o 'name=\"csrf\" value=\"[^\"]*' | head -1 | cut -d'\"' -f4").strip() def post(path, fields): form = " ".join(f"--data-urlencode '{k}={v}'" for k, v in fields.items()) return curl(f"-o /dev/null -w '%{{http_code}}' {form} {ui}{path}") start_all() router.wait_for_unit("router-webui.service") router.succeed("echo hunter2hunter2 | router-webui passwd /var/lib/router/webui-password") router.succeed("systemctl restart router-webui.service") router.wait_for_unit("router-webui.service") with subtest("the web UI is reachable from the LAN and wants the password"): client.wait_until_succeeds(f"curl -sk -o /dev/null {ui}/login", timeout=60) assert curl(f"-o /dev/null -w '%{{http_code}}' -d password=wrong {ui}/login") == "401" assert curl(f"-o /dev/null -w '%{{http_code}}' -d password=hunter2hunter2 {ui}/login") == "303" with subtest("the dashboard shows the uplink, the LAN and pings from the router"): post("/prefs", {"csrf": csrf("/advanced"), "pingTargets": "gateway, 192.168.10.50"}) # The UI pings from its sandbox (raw socket); the client answers. # (Into a file: grep -q would end curl with SIGPIPE, failing the pipe.) client.wait_until_succeeds( f"curl -sk -b /tmp/jar -o /tmp/dash '{ui}/dashboard?range=1h' && grep -q 'Ping 192.168.10.50
[0-9.]* ms' /tmp/dash", timeout=60) page = curl(f"{ui}/") # No DHCP server on the uplink in this test. for text in ["Not connected", "LAN main (eth2)", "Internet (eth1)", "Latency", "CPU and memory", "/static/dashboard.js"]: assert text in page, f"{text!r} missing:\n{page}" assert "