mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-11-04 16:40:21 +00:00
Merge pull request #1237 from 77-QiQi/master
Improve: Delete the corresponding DNS record if the IPv4/IPv6 address is empty.
This commit is contained in:
2 files changed
+331
-55
No files matched your search
+91
-53
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/libdns/libdns"
|
"github.com/libdns/libdns"
|
||||||
@@ -21,9 +22,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
prefix string
|
|
||||||
zone string
|
|
||||||
|
|
||||||
DDNSProfile *model.DDNSProfile
|
DDNSProfile *model.DDNSProfile
|
||||||
IPAddrs *model.IP
|
IPAddrs *model.IP
|
||||||
Setter libdns.RecordSetter
|
Setter libdns.RecordSetter
|
||||||
@@ -34,61 +32,81 @@ func (provider *Provider) GetProfileID() uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...string) {
|
func (provider *Provider) UpdateDomain(ctx context.Context, overrideDomains ...string) {
|
||||||
for _, domain := range utils.IfOr(len(overrideDomains) > 0, overrideDomains, provider.DDNSProfile.Domains) {
|
domains := utils.IfOr(len(overrideDomains) > 0, overrideDomains, provider.DDNSProfile.Domains)
|
||||||
for retries := range int(provider.DDNSProfile.MaxRetries) {
|
maxRetries := int(provider.DDNSProfile.MaxRetries)
|
||||||
log.Printf("NEZHA>> Updating DNS Record of domain %s: %d/%d", domain, retries+1, provider.DDNSProfile.MaxRetries)
|
if maxRetries <= 0 {
|
||||||
if err := provider.updateDomain(ctx, domain); err != nil {
|
maxRetries = 1
|
||||||
log.Printf("NEZHA>> Failed to update DNS record of domain %s: %v", domain, err)
|
}
|
||||||
} else {
|
|
||||||
log.Printf("NEZHA>> Update DNS record of domain %s succeeded", domain)
|
for _, domain := range domains {
|
||||||
|
var prefix, zone string
|
||||||
|
var soaErr error
|
||||||
|
|
||||||
|
for retries := 0; retries < maxRetries; retries++ {
|
||||||
|
prefix, zone, soaErr = provider.splitDomainSOA(ctx, domain)
|
||||||
|
if soaErr == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
log.Printf("NEZHA>> Failed to split domain SOA for %s (attempt %d/%d): %v", domain, retries+1, maxRetries, soaErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if soaErr != nil {
|
||||||
|
log.Printf("NEZHA>> Failed to split domain SOA for %s after %d retries, skipping domain", domain, maxRetries)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 独立处理 IPv4 更新或删除
|
||||||
|
if provider.DDNSProfile.EnableIPv4 != nil && *provider.DDNSProfile.EnableIPv4 {
|
||||||
|
for retries := 0; retries < maxRetries; retries++ {
|
||||||
|
log.Printf("NEZHA>> Updating IPv4 record of domain %s: %d/%d", domain, retries+1, maxRetries)
|
||||||
|
var ipv4Err error
|
||||||
|
if provider.IPAddrs.IPv4Addr == "" {
|
||||||
|
ipv4Err = provider.deleteDomainRecord(ctx, prefix, zone, "A")
|
||||||
|
} else {
|
||||||
|
ipv4Err = provider.addDomainRecord(ctx, prefix, zone, "A", provider.IPAddrs.IPv4Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4Err != nil {
|
||||||
|
log.Printf("NEZHA>> Failed to update IPv4 record of domain %s: %v", domain, ipv4Err)
|
||||||
|
} else {
|
||||||
|
log.Printf("NEZHA>> Update IPv4 record of domain %s succeeded", domain)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 独立处理 IPv6 更新或删除
|
||||||
|
if provider.DDNSProfile.EnableIPv6 != nil && *provider.DDNSProfile.EnableIPv6 {
|
||||||
|
for retries := 0; retries < maxRetries; retries++ {
|
||||||
|
log.Printf("NEZHA>> Updating IPv6 record of domain %s: %d/%d", domain, retries+1, maxRetries)
|
||||||
|
var ipv6Err error
|
||||||
|
if provider.IPAddrs.IPv6Addr == "" {
|
||||||
|
ipv6Err = provider.deleteDomainRecord(ctx, prefix, zone, "AAAA")
|
||||||
|
} else {
|
||||||
|
ipv6Err = provider.addDomainRecord(ctx, prefix, zone, "AAAA", provider.IPAddrs.IPv6Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6Err != nil {
|
||||||
|
log.Printf("NEZHA>> Failed to update IPv6 record of domain %s: %v", domain, ipv6Err)
|
||||||
|
} else {
|
||||||
|
log.Printf("NEZHA>> Update IPv6 record of domain %s succeeded", domain)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) updateDomain(ctx context.Context, domain string) error {
|
func (provider *Provider) addDomainRecord(ctx context.Context, prefix, zone, recType, addr string) error {
|
||||||
var err error
|
|
||||||
provider.prefix, provider.zone, err = provider.splitDomainSOA(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 独立处理 IPv4 更新
|
|
||||||
if *provider.DDNSProfile.EnableIPv4 {
|
|
||||||
if provider.IPAddrs.IPv4Addr == "" {
|
|
||||||
log.Printf("NEZHA>> Skip IPv4 update for domain %s: IPv4 address is empty", domain)
|
|
||||||
} else {
|
|
||||||
if err = provider.addDomainRecord(ctx, "A", provider.IPAddrs.IPv4Addr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 独立处理 IPv6 更新
|
|
||||||
if *provider.DDNSProfile.EnableIPv6 {
|
|
||||||
if provider.IPAddrs.IPv6Addr == "" {
|
|
||||||
log.Printf("NEZHA>> Skip IPv6 update for domain %s: IPv6 address is empty", domain)
|
|
||||||
} else {
|
|
||||||
if err = provider.addDomainRecord(ctx, "AAAA", provider.IPAddrs.IPv6Addr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) addDomainRecord(ctx context.Context, recType, addr string) error {
|
|
||||||
netipAddr, err := netip.ParseAddr(addr)
|
netipAddr, err := netip.ParseAddr(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse error: %v", err)
|
return fmt.Errorf("parse error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = provider.Setter.SetRecords(ctx, provider.zone,
|
_, err = provider.Setter.SetRecords(ctx, zone,
|
||||||
[]libdns.Record{
|
[]libdns.Record{
|
||||||
libdns.Address{
|
libdns.Address{
|
||||||
Name: provider.prefix,
|
Name: prefix,
|
||||||
IP: netipAddr,
|
IP: netipAddr,
|
||||||
TTL: time.Minute,
|
TTL: time.Minute,
|
||||||
},
|
},
|
||||||
@@ -96,6 +114,28 @@ func (provider *Provider) addDomainRecord(ctx context.Context, recType, addr str
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) deleteDomainRecord(ctx context.Context, prefix, zone, recType string) error {
|
||||||
|
deleter, okDeleter := provider.Setter.(libdns.RecordDeleter)
|
||||||
|
if !okDeleter {
|
||||||
|
log.Printf("NEZHA>> DNS provider does not support RecordDeleter, safely skipping deletion for %s", recType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetRecType := strings.ToUpper(recType)
|
||||||
|
_, err := deleter.DeleteRecords(ctx, zone, []libdns.Record{
|
||||||
|
libdns.RR{
|
||||||
|
Name: prefix,
|
||||||
|
Type: targetRecType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("deleter.DeleteRecords failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("NEZHA>> Successfully deleted %s record for %s.%s", recType, prefix, zone)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (provider *Provider) splitDomainSOA(ctx context.Context, domain string) (prefix string, zone string, err error) {
|
func (provider *Provider) splitDomainSOA(ctx context.Context, domain string) (prefix string, zone string, err error) {
|
||||||
c := &dns.Client{Timeout: dnsTimeOut}
|
c := &dns.Client{Timeout: dnsTimeOut}
|
||||||
|
|
||||||
@@ -103,7 +143,6 @@ func (provider *Provider) splitDomainSOA(ctx context.Context, domain string) (pr
|
|||||||
indexes := dns.Split(domain)
|
indexes := dns.Split(domain)
|
||||||
|
|
||||||
servers := utils.DNSServers
|
servers := utils.DNSServers
|
||||||
|
|
||||||
customDNSServers, _ := ctx.Value(DNSServerKey{}).([]string)
|
customDNSServers, _ := ctx.Value(DNSServerKey{}).([]string)
|
||||||
if len(customDNSServers) > 0 {
|
if len(customDNSServers) > 0 {
|
||||||
servers = customDNSServers
|
servers = customDNSServers
|
||||||
@@ -119,15 +158,14 @@ func (provider *Provider) splitDomainSOA(ctx context.Context, domain string) (pr
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Answer) > 0 {
|
if r != nil && len(r.Answer) > 0 {
|
||||||
if soa, ok := r.Answer[0].(*dns.SOA); ok {
|
if soa, ok := r.Answer[0].(*dns.SOA); ok {
|
||||||
zone := soa.Hdr.Name
|
zoneName := soa.Hdr.Name
|
||||||
prefix := libdns.RelativeName(domain, zone)
|
pfx := libdns.RelativeName(domain, zoneName)
|
||||||
// Convert "@" to empty string for zone apex
|
if pfx == "@" {
|
||||||
if prefix == "@" {
|
pfx = ""
|
||||||
prefix = ""
|
|
||||||
}
|
}
|
||||||
return prefix, zone, nil
|
return pfx, zoneName, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+240
-2
@@ -2,7 +2,15 @@ package ddns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/libdns/libdns"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/nezhahq/nezha/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testSt struct {
|
type testSt struct {
|
||||||
@@ -12,6 +20,48 @@ type testSt struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSplitDomainSOA(t *testing.T) {
|
func TestSplitDomainSOA(t *testing.T) {
|
||||||
|
handler := dns.NewServeMux()
|
||||||
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetReply(r)
|
||||||
|
m.Authoritative = true
|
||||||
|
if len(r.Question) > 0 {
|
||||||
|
qname := r.Question[0].Name
|
||||||
|
var zoneName string
|
||||||
|
if strings.HasSuffix(qname, "example.co.uk.") {
|
||||||
|
zoneName = "example.co.uk."
|
||||||
|
} else if strings.HasSuffix(qname, "example.com.") {
|
||||||
|
zoneName = "example.com."
|
||||||
|
}
|
||||||
|
|
||||||
|
if zoneName != "" {
|
||||||
|
soa := &dns.SOA{
|
||||||
|
Hdr: dns.RR_Header{Name: zoneName, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
|
||||||
|
Ns: "ns.example.com.",
|
||||||
|
Mbox: "admin.example.com.",
|
||||||
|
Serial: 1,
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, soa)
|
||||||
|
m.Ns = append(m.Ns, 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{
|
cases := []testSt{
|
||||||
{
|
{
|
||||||
domain: "www.example.co.uk",
|
domain: "www.example.co.uk",
|
||||||
@@ -30,12 +80,12 @@ func TestSplitDomainSOA(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{"1.1.1.1:53"})
|
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{addr})
|
||||||
provider := &Provider{}
|
provider := &Provider{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
prefix, zone, err := provider.splitDomainSOA(ctx, c.domain)
|
prefix, zone, err := provider.splitDomainSOA(ctx, c.domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error: %s", err)
|
t.Fatalf("Error for domain %s: %s", c.domain, err)
|
||||||
}
|
}
|
||||||
if prefix != c.prefix {
|
if prefix != c.prefix {
|
||||||
t.Fatalf("Expected prefix %s, but got %s", c.prefix, prefix)
|
t.Fatalf("Expected prefix %s, but got %s", c.prefix, prefix)
|
||||||
@@ -45,3 +95,191 @@ func TestSplitDomainSOA(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockSetter struct {
|
||||||
|
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) {
|
||||||
|
m.SetCalled = true
|
||||||
|
m.Records = append(m.Records, records...)
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockSetter) DeleteRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
m.DelCalled = true
|
||||||
|
m.DeletedRecords = append(m.DeletedRecords, records...)
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockUnsupportedSetter struct{}
|
||||||
|
|
||||||
|
func (m *MockUnsupportedSetter) SetRecords(ctx context.Context, zone string, records []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsupportedGetterDeleterSkip(t *testing.T) {
|
||||||
|
provider := &Provider{
|
||||||
|
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
|
||||||
|
IPAddrs: &model.IP{IPv4Addr: ""},
|
||||||
|
Setter: &MockUnsupportedSetter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := provider.deleteDomainRecord(context.Background(), "sub", "example.com", "A")
|
||||||
|
if err != nil {
|
||||||
|
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: "2001:db8::1",
|
||||||
|
},
|
||||||
|
Setter: mockSetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := dns.NewServeMux()
|
||||||
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetReply(r)
|
||||||
|
m.Authoritative = true
|
||||||
|
if len(r.Question) > 0 {
|
||||||
|
soa := &dns.SOA{
|
||||||
|
Hdr: dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300},
|
||||||
|
Ns: "ns.example.com.",
|
||||||
|
Mbox: "admin.example.com.",
|
||||||
|
Serial: 1,
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, soa)
|
||||||
|
m.Ns = append(m.Ns, 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()
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundIPv4 := false
|
||||||
|
foundIPv6 := false
|
||||||
|
for _, rec := range mockSetter.Records {
|
||||||
|
if addrRec, ok := rec.(libdns.Address); ok {
|
||||||
|
if addrRec.IP.String() == "1.1.1.1" {
|
||||||
|
foundIPv4 = true
|
||||||
|
}
|
||||||
|
if addrRec.IP.String() == "2001:db8::1" {
|
||||||
|
foundIPv6 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundIPv4 || !foundIPv6 {
|
||||||
|
t.Fatalf("expected both IPv4 and IPv6 records to be processed independently, got records: %v", mockSetter.Records)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransientSOAFailureAndRetry(t *testing.T) {
|
||||||
|
var attempts int64
|
||||||
|
handler := dns.NewServeMux()
|
||||||
|
handler.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
atomic.AddInt64(&attempts, 1)
|
||||||
|
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()
|
||||||
|
|
||||||
|
var maxRetries uint64 = 3
|
||||||
|
provider := &Provider{
|
||||||
|
DDNSProfile: &model.DDNSProfile{
|
||||||
|
MaxRetries: maxRetries,
|
||||||
|
Domains: []string{"example.com"},
|
||||||
|
EnableIPv4: &[]bool{true}[0],
|
||||||
|
EnableIPv6: &[]bool{false}[0],
|
||||||
|
},
|
||||||
|
IPAddrs: &model.IP{IPv4Addr: "1.1.1.1"},
|
||||||
|
Setter: &MockSetter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), DNSServerKey{}, []string{pc.LocalAddr().String()})
|
||||||
|
provider.UpdateDomain(ctx)
|
||||||
|
|
||||||
|
actualAttempts := atomic.LoadInt64(&attempts)
|
||||||
|
expectedAttempts := int64(maxRetries) * 2
|
||||||
|
if actualAttempts != expectedAttempts {
|
||||||
|
t.Fatalf("expected exact attempt count of %d, got %d", expectedAttempts, actualAttempts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericRecordDeleterContract(t *testing.T) {
|
||||||
|
mockSetter := &MockSetter{}
|
||||||
|
provider := &Provider{
|
||||||
|
DDNSProfile: &model.DDNSProfile{MaxRetries: 1},
|
||||||
|
IPAddrs: &model.IP{IPv4Addr: ""},
|
||||||
|
Setter: mockSetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err := provider.deleteDomainRecord(ctx, "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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user