package main import ( "net/http" "net/url" "os" "regexp" "strings" "testing" ) func TestDDNSRoundTrip(t *testing.T) { s := mustParse(t) if f := s.ddns(); f.Enabled || f.Protocol != "cloudflare" || f.Username != "token" || f.Interval != "5min" || !f.IPv4 || f.IPv6 { t.Fatalf("defaults = %+v", f) } f := DDNSForm{Enabled: true, Protocol: "Cloudflare", Username: "token", Secret: " s3cret\n", Zone: "example.org", Domains: "Home.example.org, *.k8s.example.org\nvpn.example.org", IPv4: true, IPv6: true, SSL: true, Interval: "10min"} secret, err := s.applyDDNS(f, "/var/lib/router/ddns-secret") if err != nil || secret != "s3cret" { t.Fatalf("secret=%q err=%v", secret, err) } out := string(mustMarshal(t, s)) for _, want := range []string{`"passwordFile": "/var/lib/router/ddns-secret"`, `"home.example.org"`, `"*.k8s.example.org"`, `"ipv6": true`, `"zone": "example.org"`} { if !strings.Contains(out, want) { t.Errorf("%s missing:\n%s", want, out) } } if strings.Contains(out, "s3cret") { t.Error("the secret is in the settings") } got := s.ddns() if !got.SecretSet || got.Domains != "home.example.org\n*.k8s.example.org\nvpn.example.org" || got.Protocol != "cloudflare" { t.Errorf("read back = %+v", got) } // Saving again without a secret keeps the stored one; switching off keeps the rest. f.Secret, f.Enabled = "", false if secret, err := s.applyDDNS(f, "/x"); err != nil || secret != "" { t.Fatalf("secret=%q err=%v", secret, err) } if got := s.ddns(); got.Enabled || !got.SecretSet || got.Zone != "example.org" { t.Errorf("after switching off = %+v", got) } } func TestDDNSIsChecked(t *testing.T) { good := DDNSForm{Enabled: true, Protocol: "dyndns2", Server: "dynupdate.no-ip.com", Username: "u", Secret: "p", Domains: "a.example.org", IPv4: true, SSL: true, Interval: "5min"} for name, mod := range map[string]func(*DDNSForm){ "domains": func(f *DDNSForm) { f.Domains = "not a host!" }, "protocol": func(f *DDNSForm) { f.Protocol = "no way" }, "server": func(f *DDNSForm) { f.Server = "https://x.example" }, "interval": func(f *DDNSForm) { f.Interval = "soon" }, "ipv4": func(f *DDNSForm) { f.IPv4 = false }, "secret": func(f *DDNSForm) { f.Secret = "" }, } { f := good mod(&f) if _, err := mustParse(t).applyDDNS(f, "/x"); err == nil || err.(FormErrors)[name] == "" { t.Errorf("%s accepted: %v", name, err) } } f := good f.Domains = "" if _, err := mustParse(t).applyDDNS(f, "/x"); err == nil { t.Error("enabled without names accepted") } f.Enabled, f.Secret = false, "" if _, err := mustParse(t).applyDDNS(f, "/x"); err != nil { t.Errorf("disabled needs neither names nor a secret: %v", err) } } func TestDDNSPage(t *testing.T) { a, sys := newTestApp(t) h := a.routes() cookie := do(h, "POST", "/login", url.Values{"password": {"hunter2hunter2"}}, nil).Result().Cookies()[0] body := do(h, "GET", "/ddns", nil, cookie).Body.String() if !strings.Contains(body, `name="domains"`) || !strings.Contains(body, "Dynamic DNS") { t.Fatalf("page:\n%s", body) } token := regexp.MustCompile(`name="csrf" value="([^"]+)"`).FindStringSubmatch(body)[1] form := url.Values{"csrf": {token}, "enabled": {"on"}, "protocol": {"cloudflare"}, "username": {"token"}, "zone": {"example.org"}, "domains": {"home.example.org"}, "ipv4": {"on"}, "ssl": {"on"}, "interval": {"5min"}} if rec := do(h, "POST", "/ddns", form, cookie); rec.Code != http.StatusUnprocessableEntity || !strings.Contains(rec.Body.String(), "API token") { t.Fatalf("no secret: %d\n%s", rec.Code, rec.Body) } form.Set("secret", "tok3n") if rec := do(h, "POST", "/ddns", form, cookie); rec.Code != http.StatusSeeOther { t.Fatalf("save: %d\n%s", rec.Code, rec.Body) } data, err := os.ReadFile(sys.ddnsSecretPath()) if err != nil || string(data) != "tok3n" { t.Fatalf("secret file: %q %v", data, err) } if fi, _ := os.Stat(sys.ddnsSecretPath()); fi.Mode().Perm() != 0o600 { t.Errorf("mode %v", fi.Mode()) } s, _ := loadSettings(sys.settingsPath()) if f := s.ddns(); !f.Enabled || f.Domains != "home.example.org" || !f.SecretSet { t.Fatalf("not saved: %+v", f) } if body := do(h, "GET", "/ddns", nil, cookie).Body.String(); strings.Contains(body, "tok3n") || !strings.Contains(body, "leave empty to keep") { t.Errorf("the page shows the secret or lacks the hint:\n%s", body) } }