Files
jarl/internal/dnsname/dnsname_test.go
T
abrown38andClaude Opus 5.5 bf7f99874b Web UI: zone naming and reverse zones, ACL suggestions, record form
- New zone dialog: choose Domain or Reverse zone. Reverse zones are
  created from a network and their name is generated; both kinds show the
  exact zone name and flag likely mistakes (addresses or URLs as names,
  host-like names, duplicates in the view, parent/child zones, networks
  off the octet/nibble boundaries) before anything is submitted.
- Record form: clears every field but the type after an add; shows the
  record exactly as it will be stored and warns about unqualified names.
  In reverse zones it takes an address, defaults to PTR, suggests host
  names of A/AAAA records with that address and warns about a second PTR;
  the record list shows addresses.
- Address match lists (options, view match lists, ACL elements) offer the
  existing ACLs, TSIG keys and built-ins as one-click suggestions and
  flag unknown names.
- Server: in reverse zones a record can be named by its address, and a
  PTR target like nas.example.com without the final dot is read as the
  full host name (a bare label is refused). Warnings noticed while parsing
  record data reach the result again.
- Fixes: brand link hover underline, the dashboard's "New zone" link
  opening the dialog twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:49:43 +00:00

188 lines
5.8 KiB
Go

package dnsname
import (
"net/netip"
"testing"
)
func TestOwner(t *testing.T) {
zone := "example.com."
cases := []struct{ in, want string }{
{"", "example.com."},
{"@", "example.com."},
{"www", "www.example.com."},
{"WWW", "www.example.com."},
{"www.example.com", "www.example.com."},
{"www.example.com.", "www.example.com."},
{"example.com", "example.com."},
{"a.b", "a.b.example.com."},
{"other.org.", "other.org."},
{"_acme-challenge.www", "_acme-challenge.www.example.com."},
{"bücher", "xn--bcher-kva.example.com."},
}
for _, c := range cases {
got, err := Owner(c.in, zone)
if err != nil || got != c.want {
t.Errorf("Owner(%q) = %q, %v; want %q", c.in, got, err, c.want)
}
}
// Not fooled by a zone name that's a suffix of a label.
got, _ := Owner("myexample.com", zone)
if got != "myexample.com.example.com." {
t.Errorf("Owner(myexample.com) = %q", got)
}
}
func TestRelative(t *testing.T) {
cases := []struct{ name, zone, want string }{
{"example.com.", "example.com.", "@"},
{"www.example.com.", "example.com.", "www"},
{"a.b.example.com.", "example.com.", "a.b"},
{"other.org.", "example.com.", "other.org."},
{"x.", ".", "x."},
}
for _, c := range cases {
if got := Relative(c.name, c.zone); got != c.want {
t.Errorf("Relative(%q,%q) = %q want %q", c.name, c.zone, got, c.want)
}
}
}
func TestCheckOwner(t *testing.T) {
ok := []string{".", "example.com.", "*.example.com.", "_dmarc.example.com.", "0/26.2.0.192.in-addr.arpa.", "a-b.c."}
for _, n := range ok {
if err := CheckOwner(n); err != nil {
t.Errorf("CheckOwner(%q) = %v", n, err)
}
}
bad := []string{"a..b.", "x.*.example.com.", "foo*.example.com.", "Upper.example.com.", "sp ace.com.", "example.com"}
for _, n := range bad {
if err := CheckOwner(n); err == nil {
t.Errorf("CheckOwner(%q) succeeded, want error", n)
}
}
long := ""
for i := 0; i < 64; i++ {
long += "a"
}
if CheckOwner(long+".com.") == nil {
t.Error("64 octet label accepted")
}
if CheckZoneName("*.example.com.") == nil {
t.Error("wildcard zone accepted")
}
}
func TestIsHostname(t *testing.T) {
cases := []struct {
name string
wildcard bool
want bool
}{
{"www.example.com.", false, true},
{"1host.example.com.", false, true},
{"foo_bar.example.com.", false, false},
{"-x.example.com.", false, false},
{"x-.example.com.", false, false},
{"*.example.com.", true, true},
{"*.example.com.", false, false},
{".", false, false},
{"a.b.", false, true},
}
for _, c := range cases {
if got := IsHostname(c.name, c.wildcard); got != c.want {
t.Errorf("IsHostname(%q,%v) = %v", c.name, c.wildcard, got)
}
}
}
func TestMailbox(t *testing.T) {
mb, err := MailboxFromEmail("first.last@example.com")
if err != nil || mb != `first\.last.example.com.` {
t.Fatalf("MailboxFromEmail = %q %v", mb, err)
}
if !IsMailbox(mb) {
t.Error("IsMailbox false for escaped local part")
}
if got := EmailFromMailbox(mb); got != "first.last@example.com" {
t.Errorf("EmailFromMailbox = %q", got)
}
if mb, _ := MailboxFromEmail("hostmaster.example.com."); mb != "hostmaster.example.com." {
t.Errorf("domain form changed: %q", mb)
}
if IsMailbox("hostmaster.bad_domain.com.") {
t.Error("bad domain part accepted")
}
}
func TestReverse(t *testing.T) {
a := netip.MustParseAddr("192.0.2.10")
if got := ReverseName(a); got != "10.2.0.192.in-addr.arpa." {
t.Errorf("ReverseName v4 = %q", got)
}
a6 := netip.MustParseAddr("2001:db8::1")
r6 := ReverseName(a6)
if r6 != "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa." {
t.Errorf("ReverseName v6 = %q", r6)
}
for _, name := range []string{"10.2.0.192.in-addr.arpa.", r6} {
back, ok := AddrFromReverse(name)
if !ok {
t.Errorf("AddrFromReverse(%q) failed", name)
continue
}
if ReverseName(back) != name {
t.Errorf("round trip %q -> %v", name, back)
}
}
z, err := ReverseZone(netip.MustParsePrefix("192.168.1.0/24"))
if err != nil || z != "1.168.192.in-addr.arpa." {
t.Errorf("ReverseZone /24 = %q %v", z, err)
}
z, err = ReverseZone(netip.MustParsePrefix("10.0.0.0/8"))
if err != nil || z != "10.in-addr.arpa." {
t.Errorf("ReverseZone /8 = %q %v", z, err)
}
if _, err := ReverseZone(netip.MustParsePrefix("192.168.1.0/26")); err == nil {
t.Error("ReverseZone /26 accepted")
}
z, err = ReverseZone(netip.MustParsePrefix("2001:db8::/32"))
if err != nil || z != "8.b.d.0.1.0.0.2.ip6.arpa." {
t.Errorf("ReverseZone v6 = %q %v", z, err)
}
if !IsReverse("1.168.192.in-addr.arpa.") || IsReverse("example.com.") {
t.Error("IsReverse")
}
}
func TestMisc(t *testing.T) {
if Parent("www.example.com.") != "example.com." || Parent("com.") != "." || Parent(".") != "" {
t.Error("Parent")
}
if CountLabels(".") != 0 || CountLabels("a.b.c.") != 3 {
t.Error("CountLabels")
}
if BestZone("a.lab.example.com.", []string{"example.com.", "lab.example.com.", "other.com."}) != "lab.example.com." {
t.Error("BestZone")
}
if FileSafe("0/26.2.0.192.in-addr.arpa.") != "0%2f26.2.0.192.in-addr.arpa" || FileSafe(".") != "root" {
t.Errorf("FileSafe = %q", FileSafe("0/26.2.0.192.in-addr.arpa."))
}
if !IsSubdomain("a.example.com.", "example.com.") || IsSubdomain("aexample.com.", "example.com.") || !IsSubdomain("x.", ".") {
t.Error("IsSubdomain")
}
}
func TestOwnerAddressInReverseZone(t *testing.T) {
for _, tc := range []struct{ in, zone, want string }{
{"10.10.0.20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."},
{"20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."},
{"2001:db8::1", "8.b.d.0.1.0.0.2.ip6.arpa.", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."},
{"10.10.0.20", "adbraun.lol.", "10.10.0.20.adbraun.lol."}, // not special in forward zones
} {
if got, err := Owner(tc.in, tc.zone); err != nil || got != tc.want {
t.Errorf("Owner(%q, %q) = %q, %v; want %q", tc.in, tc.zone, got, err, tc.want)
}
}
}