package zonefile import ( "path/filepath" "slices" "strings" "testing" ) // nameTypeRdata renders every record as "name TYPE rdata", skipping the line // number and the TTL. The shipped examples are edited by hand, so pinning the // exact record set is enough; pinning line numbers would make a comment change // look like a parser bug. func nameTypeRdata(t *testing.T, z *Zone) []string { t.Helper() out := make([]string, 0, len(z.Records)) for _, rec := range z.Records { parts := strings.SplitN(rec.RR.String(), "\t", 5) if len(parts) != 5 { t.Fatalf("record on line %d does not render as 5 tab-separated fields: %q", rec.Line, rec.Text()) } out = append(out, parts[0]+" "+parts[3]+" "+parts[4]) } return out } // TestExampleLocalZoneIsOriginIndependent parses deploy/local.example.zone the // way the product parses it — with the origin taken from the file name, which // for local.example.zone is "local." — and pins that every name in the example // is fully qualified anyway. // // This is not a style test. The origin a master file is read with is its base // name, so a relative owner name silently becomes .local. and the zone // answers for names nobody asked about. The example is what an operator copies, // and it used to mix relative owners with absolute ones: a file that looked // right and matched nothing. func TestExampleLocalZoneIsOriginIndependent(t *testing.T) { path := filepath.Join("..", "..", "deploy", "local.example.zone") if got, want := OriginFromPath(path), "local."; got != want { t.Fatalf("OriginFromPath(%q) = %q, want %q — the example depends on this origin", path, got, want) } z, err := ParseFile(path, OriginFromPath(path)) if err != nil { t.Fatalf("ParseFile(%s): %v", path, err) } got := nameTypeRdata(t, z) want := []string{ "lan. SOA ns1.lan. hostmaster.lan. 2024010101 3600 600 604800 300", "lan. NS ns1.lan.", "lan. NS ns2.lan.", "ns1.lan. A 192.168.1.2", "ns2.lan. A 192.168.1.3", "router.lan. A 192.168.1.1", "nas.lan. A 192.168.1.10", "nas.lan. AAAA fd00::10", "*.lan. A 192.168.1.50", "*.internal.lan. A 10.0.0.50", "_ntp._udp.lan. SRV 0 0 123 ns1.lan.", "_ldap._tcp.lan. SRV 10 0 389 nas.lan.", `_dmarc._domain.lan. TXT "v=DMARC1; p=reject; rua=mailto:dmarc.lan"`, `_lan. TXT "internal network, do not use on the open internet"`, } if !slices.Equal(got, want) { t.Errorf("records mismatch\n got: %q\nwant: %q", got, want) } if z.DefaultTTL != 300 { t.Errorf("DefaultTTL = %d, want the $TTL 300", z.DefaultTTL) } for _, rec := range z.Records { if !strings.HasSuffix(rec.Name, ".") { t.Errorf("record %q is not fully qualified; the example is read with the "+ "origin %q derived from the file name", rec.Name, z.Origin) } } if z.SOA == nil || z.SOA.Serial != 2024010101 { t.Errorf("SOA = %v, want the serial 2024010101", z.SOA) } }