Update ddns_test.go

This commit is contained in:
77-QiQi authored and GitHub committed 2026-09-14 10:04:38 +08:00
1 parent 19d959c60d
commit f611f0a551
1 file changed
+178 -37
+178 -37
View File
@@ -2,20 +2,97 @@ package ddns
import (
"context"
"net"
"testing"
"time"
"github.com/libdns/he"
"github.com/libdns/libdns"
"github.com/miekg/dns"
"github.com/nezhahq/nezha/model"
)
type testSt struct {
domain string
zone string
prefix string
}
func TestSplitDomainSOA(t *testing.T) {
handler := dns.NewServeMux()
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
if len(r.Question) > 0 {
qname := r.Question[0].Name
soa := &dns.SOA{
Hdr: dns.RR_Header{Name: qname, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
Ns: "ns.example.com.",
Mbox: "admin.example.com.",
Serial: 1,
Refresh: 3600,
Retry: 600,
Expire: 86400,
Minttl: 60,
}
m.Answer = append(m.Answer, soa)
}
_ = w.WriteMsg(m)
})
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
defer pc.Close()
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
go func() {
_ = server.ActivateAndServe()
}()
defer server.Shutdown()
addr := pc.LocalAddr().String()
cases := []testSt{
{
domain: "www.example.co.uk",
zone: "example.co.uk.",
prefix: "www",
},
{
domain: "abc.example.com",
zone: "example.com.",
prefix: "abc",
},
{
domain: "example.com",
zone: "example.com.",
prefix: "",
},
}
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{addr})
provider := &Provider{}
for _, c := range cases {
prefix, zone, err := provider.splitDomainSOA(ctx, c.domain)
if err != nil {
t.Fatalf("Error for domain %s: %s", c.domain, err)
}
if prefix != c.prefix {
t.Fatalf("Expected prefix %s, but got %s", c.prefix, prefix)
}
if zone != c.zone {
t.Fatalf("Expected zone %s, but got %s", c.zone, zone)
}
}
}
type MockSetter struct {
Records []libdns.Record
SetCalled bool
GetCalled bool
DelCalled bool
DeleteError error
Records []libdns.Record
SetCalled bool
DelCalled bool
DeletedRecords []libdns.Record
}
func (m *MockSetter) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
@@ -24,17 +101,9 @@ func (m *MockSetter) SetRecords(ctx context.Context, zone string, records []libd
return records, nil
}
func (m *MockSetter) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
m.GetCalled = true
return m.Records, nil
}
func (m *MockSetter) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
m.DelCalled = true
if m.DeleteError != nil {
return nil, m.DeleteError
}
m.Records = nil
m.DeletedRecords = append(m.DeletedRecords, records...)
return records, nil
}
@@ -53,62 +122,134 @@ func TestUnsupportedGetterDeleterSkip(t *testing.T) {
err := provider.deleteDomainRecord(context.Background(), "sub", "example.com", "A")
if err != nil {
t.Fatalf("expected no error for unsupported setter deletion skip, got %v", err)
t.Fatalf("expected no error for unsupported deleter skip, got %v", err)
}
}
func TestIndependentDualStackProcessing(t *testing.T) {
enableIPv4 := true
enableIPv6 := true
mockSetter := &MockSetter{}
provider := &Provider{
DDNSProfile: &model.DDNSProfile{
MaxRetries: 1,
Domains: []string{"example.com"},
EnableIPv4: &enableIPv4,
EnableIPv6: &enableIPv6,
},
IPAddrs: &model.IP{
IPv4Addr: "1.1.1.1",
IPv6Addr: "",
IPv6Addr: "2001:db8::1",
},
Setter: &MockSetter{},
Setter: mockSetter,
}
if provider.DDNSProfile.EnableIPv4 == nil || !*provider.DDNSProfile.EnableIPv4 {
t.Fatalf("IPv4 should be enabled")
}
}
handler := dns.NewServeMux()
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
if len(r.Question) > 0 {
qname := r.Question[0].Name
soa := &dns.SOA{
Hdr: dns.RR_Header{Name: qname, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
}
m.Answer = append(m.Answer, soa)
}
_ = w.WriteMsg(m)
})
func TestHEDirectDeletionPath(t *testing.T) {
heProvider := &he.Provider{APIKey: "test-key"}
provider := &Provider{
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
IPAddrs: &model.IP{IPv4Addr: ""},
Setter: heProvider,
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
defer pc.Close()
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
go func() {
_ = server.ActivateAndServe()
}()
defer server.Shutdown()
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{pc.LocalAddr().String()})
provider.UpdateDomain(ctx)
if !mockSetter.SetCalled {
t.Fatalf("expected SetRecords to have been called for dual stack updates")
}
err := provider.deleteDomainRecord(context.Background(), "test", "example.com", "A")
if err == nil {
t.Fatalf("expected an error due to invalid test API key in HE direct path, but got nil")
if len(mockSetter.Records) != 2 {
t.Fatalf("expected 2 records (IPv4 and IPv6) to be set, got %d", len(mockSetter.Records))
}
}
func TestTransientSOAFailureAndRetry(t *testing.T) {
attempts := 0
handler := dns.NewServeMux()
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
attempts++
m := new(dns.Msg)
m.SetReply(r)
_ = w.WriteMsg(m)
})
pc, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
defer pc.Close()
server := &dns.Server{PacketConn: pc, Net: "udp", Handler: handler}
go func() {
_ = server.ActivateAndServe()
}()
defer server.Shutdown()
maxRetries := 3
provider := &Provider{
DDNSProfile: &model.DDNSProfile{
MaxRetries: uint64(maxRetries),
Domains: []string{"notfound.invalid"},
Domains: []string{"fail.invalid"},
},
IPAddrs: &model.IP{IPv4Addr: "1.1.1.1"},
Setter: &MockSetter{},
}
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{"192.0.2.1:53"})
start := time.Now()
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{pc.LocalAddr().String()})
provider.UpdateDomain(ctx)
duration := time.Since(start)
if duration > 180*time.Second {
t.Fatalf("SOA retry loop took too long: %v", duration)
if attempts != maxRetries {
t.Fatalf("expected exact attempt count of %d, got %d", maxRetries, attempts)
}
}
func TestGenericRecordDeleterContract(t *testing.T) {
mockSetter := &MockSetter{}
provider := &Provider{
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
IPAddrs: &model.IP{IPv4Addr: ""},
Setter: mockSetter,
}
err := provider.deleteDomainRecord(context.Background(), "sub", "example.com", "A")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !mockSetter.DelCalled {
t.Fatalf("expected DeleteRecords to be called")
}
if len(mockSetter.DeletedRecords) != 1 {
t.Fatalf("expected 1 record sent for deletion, got %d", len(mockSetter.DeletedRecords))
}
rr, ok := mockSetter.DeletedRecords[0].(libdns.RR)
if !ok {
t.Fatalf("expected deletion payload to be libdns.RR")
}
if rr.Name != "sub" || rr.Type != "A" {
t.Fatalf("unexpected RR fields: Name=%s, Type=%s", rr.Name, rr.Type)
}
}