package main import ( "net/http" "net/url" "os" "path/filepath" "regexp" "strings" "testing" ) func TestReservationsRoundTrip(t *testing.T) { s := mustParse(t) rows := s.reservations() if len(rows) != 1 || rows[0] != (Reservation{LAN: "main", Name: "nas", MAC: "aa:bb:cc:dd:ee:ff", Address: "192.168.10.5"}) { t.Fatalf("reservations = %+v", rows) } rows = append(rows, Reservation{LAN: "main", Name: "printer", MAC: "AA:BB:CC:00:11:22", Address: "192.168.10.6"}, Reservation{LAN: "main"}) // the empty add row if err := s.applyReservations(rows); err != nil { t.Fatal(err) } if got := str(s, "router", "lans", "main", "dhcp", "reservations", "printer", "mac"); got != "aa:bb:cc:00:11:22" { t.Errorf("mac = %q (should be lower case)", got) } // What else the LAN has survives. out, _ := s.marshal() for _, want := range []string{`"fd00::1/64"`, `"pool"`, `"wireguard"`} { if !strings.Contains(string(out), want) { t.Errorf("%s lost:\n%s", want, out) } } rows[0].Remove = true rows[1].Remove = true if err := s.applyReservations(rows); err != nil { t.Fatal(err) } if got := s.reservations(); len(got) != 0 { t.Errorf("not removed: %+v", got) } if !strings.Contains(string(mustMarshal(t, s)), `"pool"`) { t.Error("the pool went with the reservations") } } func mustMarshal(t *testing.T, s Settings) []byte { t.Helper() out, err := s.marshal() if err != nil { t.Fatal(err) } return out } func TestReservationsAreChecked(t *testing.T) { good := Reservation{LAN: "main", Name: "tv", MAC: "aa:bb:cc:dd:ee:01", Address: "192.168.10.7"} for name, mod := range map[string]func(*Reservation){ "name": func(r *Reservation) { r.Name = "tv room" }, "mac": func(r *Reservation) { r.MAC = "aa:bb:cc" }, "address": func(r *Reservation) { r.Address = "192.168.11.7" }, "router": func(r *Reservation) { r.Address = "192.168.10.1" }, "lan": func(r *Reservation) { r.LAN = "nope" }, "dupmac": func(r *Reservation) { r.MAC = "AA:BB:CC:DD:EE:FF" }, "dupaddr": func(r *Reservation) { r.Address = "192.168.10.5" }, "dupname": func(r *Reservation) { r.Name = "NAS" }, } { s := mustParse(t) r := good mod(&r) rows := append(s.reservations(), r) if err := s.applyReservations(rows); err == nil { t.Errorf("%s: accepted %+v", name, r) } } s := mustParse(t) if err := s.applyReservations(append(s.reservations(), good)); err != nil { t.Errorf("a good reservation was refused: %v", err) } } func TestDHCPPage(t *testing.T) { a, sys := newTestApp(t) h := a.routes() rec := do(h, "POST", "/login", url.Values{"password": {"hunter2hunter2"}}, nil) cookie := rec.Result().Cookies()[0] body := do(h, "GET", "/dhcp?lan=main&mac=aa:bb:cc:00:00:09&address=192.168.10.9&name=cam", nil, cookie).Body.String() for _, want := range []string{`value="nas"`, `value="aa:bb:cc:00:00:09"`, `value="cam"`} { if !strings.Contains(body, want) { t.Errorf("page lacks %s:\n%s", want, body) } } token := regexp.MustCompile(`name="csrf" value="([^"]+)"`).FindStringSubmatch(body)[1] // A lease of an unreserved client is offered for reservation. os.WriteFile(filepath.Join(sys.StateDir, "leases"), []byte( "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"+ "192.168.10.120,aa:bb:cc:00:00:20,,3600,4102444800,1,0,0,phone,0,,0\n"), 0o600) if body := do(h, "GET", "/dhcp", nil, cookie).Body.String(); !strings.Contains(body, "aa:bb:cc:00:00:20") { t.Errorf("the active client is not listed:\n%s", body) } form := url.Values{"csrf": {token}, "rows": {"2"}, "res.0.lan": {"main"}, "res.0.name": {"nas"}, "res.0.mac": {"aa:bb:cc:dd:ee:ff"}, "res.0.address": {"192.168.10.5"}, "res.1.lan": {"main"}, "res.1.name": {"cam"}, "res.1.mac": {"aa:bb:cc:00:00:09"}, "res.1.address": {"192.168.10.300"}} if rec := do(h, "POST", "/dhcp", form, cookie); rec.Code != http.StatusUnprocessableEntity { t.Fatalf("bad address: %d", rec.Code) } form.Set("res.1.address", "192.168.10.9") if rec := do(h, "POST", "/dhcp", form, cookie); rec.Code != http.StatusSeeOther { t.Fatalf("save: %d\n%s", rec.Code, rec.Body) } s, _ := loadSettings(sys.settingsPath()) if str(s, "router", "lans", "main", "dhcp", "reservations", "cam", "address") != "192.168.10.9" { t.Fatalf("not saved: %v", s) } }