test(storage): 使用进程内 DNS 夹具消除离线测试依赖 (#689)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Catlaxy AIandClaude Opus 5.5 authored and GitHub committed 2026-10-06 09:24:00 +08:00
1 parent c7aeba24d7
commit b2791a7416
5 files changed
+112 -1

No files matched your search

@@ -0,0 +1,69 @@
// Package outboundtest provides socket-free DNS fixtures for outbound policy tests.
package outboundtest
import (
"context"
"encoding/binary"
"io"
"net"
"strings"
"testing"
"golang.org/x/net/dns/dnsmessage"
)
// PublicDNS maps only the supplied hostnames to a public A record. Other names
// return NXDOMAIN; AAAA queries return no records. No network socket is opened.
// It changes process-global state: callers and their parents must not use
// t.Parallel. Cleanup restores the previous resolver (including nested fixtures).
func PublicDNS(t *testing.T, hosts ...string) {
t.Helper()
DNS(t, [4]byte{1, 1, 1, 1}, hosts...)
}
// DNS maps supplied names to a chosen IPv4 address without opening sockets.
func DNS(t *testing.T, address [4]byte, hosts ...string) {
t.Helper()
allowed := make(map[string]bool, len(hosts))
for _, host := range hosts {
allowed[strings.ToLower(strings.TrimSuffix(host, "."))+"."] = true
}
previous := net.DefaultResolver
net.DefaultResolver = &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, network, resolverAddress string) (net.Conn, error) {
client, server := net.Pipe()
go func() {
defer server.Close()
for {
var size [2]byte
if _, err := io.ReadFull(server, size[:]); err != nil {
return
}
wire := make([]byte, binary.BigEndian.Uint16(size[:]))
if _, err := io.ReadFull(server, wire); err != nil {
return
}
var query dnsmessage.Message
if query.Unpack(wire) != nil || len(query.Questions) != 1 {
return
}
q := query.Questions[0]
response := dnsmessage.Message{Header: dnsmessage.Header{ID: query.ID, Response: true, Authoritative: true, RecursionDesired: query.RecursionDesired, RecursionAvailable: true}, Questions: query.Questions}
if !allowed[strings.ToLower(q.Name.String())] {
response.RCode = dnsmessage.RCodeNameError
} else if q.Type == dnsmessage.TypeA && q.Class == dnsmessage.ClassINET {
response.Answers = []dnsmessage.Resource{{Header: dnsmessage.ResourceHeader{Name: q.Name, Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 60}, Body: &dnsmessage.AResource{A: address}}}
}
answer, err := response.Pack()
if err != nil {
return
}
binary.BigEndian.PutUint16(size[:], uint16(len(answer)))
if _, err := server.Write(append(size[:], answer...)); err != nil {
return
}
}
}()
return client, nil
}}
t.Cleanup(func() { net.DefaultResolver = previous })
}
@@ -0,0 +1,27 @@
package outboundtest
import (
"context"
"net"
"testing"
"time"
)
func TestPublicDNS(t *testing.T) {
previous := net.DefaultResolver
t.Run("fixture", func(t *testing.T) {
PublicDNS(t, "media.example.com")
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", "media.example.com")
if err != nil || len(ips) != 1 || !ips[0].Equal(net.IPv4(1, 1, 1, 1)) {
t.Fatalf("A/AAAA lookup = %v, %v", ips, err)
}
if _, err := net.DefaultResolver.LookupIP(ctx, "ip", "unregistered.example.com"); err == nil {
t.Fatal("unknown host must fail closed")
}
})
if net.DefaultResolver != previous {
t.Fatal("resolver was not restored")
}
}