# Evaluation-time tests: broken configs must fail with exactly the expected # assertion messages, and a valid one with none. { self, pkgs }: let inherit (pkgs) lib; evalRouter = router: (import "${pkgs.path}/nixos/lib/eval-config.nix" { system = "x86_64-linux"; modules = [ self.nixosModules.router { boot.loader.grub.enable = false; fileSystems."/" = { device = "x"; fsType = "ext4"; }; system.stateVersion = "25.05"; router = { enable = true; wan.interface = "eth0"; } // router; } ]; }).config; lan = overrides: lib.recursiveUpdate { interface = "eth1"; ipv4.address = "192.168.10.1"; dhcp.pool = { start = "192.168.10.100"; end = "192.168.10.199"; }; } overrides; failed = router: map (a: a.message) (lib.filter (a: !a.assertion) (evalRouter router).assertions); cases = { valid = { router.lans.main = lan { }; expect = [ ]; }; noManagement = { router.lans.main = lan { allowManagement = false; }; expect = [ "locked out" ]; }; wanIsLan = { router.lans.main = lan { interface = "eth0"; }; expect = [ "WAN interface `eth0` is also used by a LAN" ]; }; sharedInterface = { router.lans = { a = lan { }; b = lan { ipv4.address = "192.168.20.1"; dhcp.pool = { start = "192.168.20.100"; end = "192.168.20.199"; }; }; }; expect = [ "two LANs share the same interface" ]; }; badAddress = { router.lans.main = lan { ipv4.address = "192.168.10.300"; dhcp.enable = false; }; expect = [ "`192.168.10.300` is not a valid IPv4 address" ]; }; poolOutsideSubnet = { router.lans.main = lan { dhcp.pool = { start = "192.168.11.100"; end = "192.168.11.199"; }; }; expect = [ "pool is not inside 192.168.10.0/24" ]; }; poolReversed = { router.lans.main = lan { dhcp.pool = { start = "192.168.10.199"; end = "192.168.10.100"; }; }; expect = [ "starts after it ends" ]; }; poolContainsRouter = { router.lans.main = lan { dhcp.pool = { start = "192.168.10.1"; end = "192.168.10.50"; }; }; expect = [ "contains the router's own address 192.168.10.1" ]; }; overlappingLans = { router.lans = { a = lan { }; b = lan { interface = "eth2"; ipv4.address = "192.168.10.2"; }; }; expect = [ "`a` (192.168.10.0/24) and `b` (192.168.10.0/24) overlap" ]; }; nonOverlappingLans = { router.lans = { a = lan { }; b = lan { interface = "eth2"; ipv4.address = "192.168.20.1"; dhcp.pool = { start = "192.168.20.100"; end = "192.168.20.199"; }; }; }; expect = [ ]; }; }; problems = lib.concatLists ( lib.mapAttrsToList ( name: c: let got = failed c.router; missing = lib.filter (e: !lib.any (m: lib.hasInfix e m) got) c.expect; in lib.optional (missing != [ ] || lib.length got != lib.length c.expect) "${name}: expected ${builtins.toJSON c.expect}, got ${builtins.toJSON got}" ) cases ); subnet = (evalRouter { lans.main = lan { ipv4.prefixLength = 20; ipv4.address = "172.16.37.1"; dhcp.enable = false; }; }).router.lans.main.ipv4.subnet; subnetProblem = lib.optional (subnet != "172.16.32.0/20") "derived subnet: expected 172.16.32.0/20, got ${subnet}"; in pkgs.runCommand "router-assertion-tests" { } ( if problems ++ subnetProblem == [ ] then "touch $out" else '' printf '%s\n' ${lib.escapeShellArgs (problems ++ subnetProblem)} >&2 exit 1 '' )