package netcfg import ( "strings" "testing" "gitea.apointless.space/bsncubed/ipswap/internal/preset" ) func staticPreset() preset.Preset { return preset.Preset{ ID: "test", Name: "Artist frame — control", Adapter: "Ethernet", Mode: preset.ModeStatic, Primary: &preset.Address{ Address: "192.168.42.100", Prefix: 24, Gateway: "192.168.42.1", }, Secondary: []preset.Address{{Address: "10.0.10.50", Prefix: 24}}, DNS: preset.DNS{Mode: preset.ModeStatic, Servers: []string{"192.168.42.1", "1.1.1.1"}}, } } func staticLive() LiveConfig { return LiveConfig{ Adapter: "Ethernet", Addresses: []preset.Address{{Address: "192.168.1.87", Prefix: 24}, {Address: "172.16.0.5", Prefix: 16}}, Gateways: []string{"192.168.1.1"}, DNS: []string{"192.168.1.1"}, } } // The leak this guards against is the reason Plan exists: without a delete for // every live address, secondaries from the previous preset stay attached. func TestPlanDeletesEveryExistingAddress(t *testing.T) { cmds := Plan(staticPreset(), staticLive()) var deletes []string for _, c := range cmds { if len(c.Args) > 3 && c.Args[2] == "delete" && c.Args[3] == "address" { deletes = append(deletes, strings.Join(c.Args, " ")) } } if len(deletes) != 2 { t.Fatalf("expected 2 delete commands, got %d: %v", len(deletes), deletes) } for _, want := range []string{"addr=192.168.1.87", "addr=172.16.0.5"} { found := false for _, d := range deletes { if strings.Contains(d, want) { found = true } } if !found { t.Errorf("no delete command for %s: %v", want, deletes) } } } func TestPlanOrdering(t *testing.T) { cmds := Plan(staticPreset(), staticLive()) // deletes, then set primary, then add secondary, then DNS. var kinds []string for _, c := range cmds { switch { case c.Args[2] == "delete": kinds = append(kinds, "delete") case c.Args[2] == "set" && c.Args[3] == "address": kinds = append(kinds, "set-addr") case c.Args[2] == "add" && c.Args[3] == "address": kinds = append(kinds, "add-addr") case c.Args[3] == "dnsservers": kinds = append(kinds, "dns") } } want := []string{"delete", "delete", "set-addr", "add-addr", "dns", "dns"} if strings.Join(kinds, ",") != strings.Join(want, ",") { t.Errorf("plan order = %v, want %v", kinds, want) } } func TestPlanUsesDottedMaskNotPrefix(t *testing.T) { // netsh rejects "/24" outright. cmds := Plan(staticPreset(), staticLive()) for _, c := range cmds { for _, a := range c.Args { if strings.HasPrefix(a, "/") { t.Errorf("command passes a prefix-notation mask: %v", c.Args) } } } joined := strings.Join(cmds[2].Args, " ") if !strings.Contains(joined, "255.255.255.0") { t.Errorf("set address command lacks a dotted mask: %s", joined) } } func TestPlanDHCPSkipsDeletes(t *testing.T) { p := preset.Preset{ ID: "d", Name: "DHCP", Adapter: "Ethernet", Mode: preset.ModeDHCP, DNS: preset.DNS{Mode: preset.ModeDHCP}, } cmds := Plan(p, staticLive()) if len(cmds) != 2 { t.Fatalf("expected 2 commands for a DHCP preset, got %d: %v", len(cmds), cmds) } for _, c := range cmds { if c.Args[2] == "delete" { t.Errorf("DHCP plan should not delete addresses: %v", c.Args) } } } // A DHCP lease is not deletable with `netsh delete address`, and the static set // replaces it anyway, so a live-DHCP adapter gets no delete commands. func TestPlanSkipsDeletesWhenLiveIsDHCP(t *testing.T) { live := staticLive() live.DHCP = true for _, c := range Plan(staticPreset(), live) { if c.Args[2] == "delete" { t.Errorf("should not delete DHCP-assigned addresses: %v", c.Args) } } } func TestPlanAdapterNameIsNotShellQuoted(t *testing.T) { p := staticPreset() p.Adapter = "Ethernet 3" for _, c := range Plan(p, LiveConfig{Adapter: "Ethernet 3"}) { for _, a := range c.Args { if strings.HasPrefix(a, "name=") && strings.Contains(a, `"`) { t.Errorf("adapter name should not carry quotes through os/exec: %q", a) } } } } func TestMatches(t *testing.T) { p := staticPreset() live := LiveConfig{ Adapter: "Ethernet", Addresses: []preset.Address{{Address: "10.0.10.50", Prefix: 24}, {Address: "192.168.42.100", Prefix: 24}}, Gateways: []string{"192.168.42.1"}, DNS: []string{"192.168.42.1", "1.1.1.1"}, } if !Matches(p, live) { t.Error("preset should match an adapter carrying exactly its addresses") } extra := live extra.Addresses = append(append([]preset.Address{}, live.Addresses...), preset.Address{Address: "10.9.9.9", Prefix: 24}) if Matches(p, extra) { t.Error("a preset that is a subset of the live config is not the active preset") } reordered := live reordered.DNS = []string{"1.1.1.1", "192.168.42.1"} if Matches(p, reordered) { t.Error("DNS order is significant and should not match") } dhcp := live dhcp.DNSFromDHCP = true if Matches(p, dhcp) { t.Error("static-DNS preset should not match a DHCP-DNS adapter") } } func TestConfirmTextShapes(t *testing.T) { got := ConfirmText(staticPreset(), staticLive()) for _, want := range []string{ `Apply preset "Artist frame — control" to adapter "Ethernet"?`, "CURRENT", "NEW", "192.168.1.87/24", "192.168.42.100/24", "+ 10.0.10.50/24", "Gateway: 192.168.42.1", "DNS: 192.168.42.1, 1.1.1.1", } { if !strings.Contains(got, want) { t.Errorf("confirm text missing %q:\n%s", want, got) } } }