whoissdk/client_internal_test.go

224 lines
5.4 KiB
Go
Raw Normal View History

2026-03-19 11:53:07 +08:00
package whois
import (
"bufio"
"context"
"errors"
"net"
"strings"
"testing"
"time"
"golang.org/x/text/encoding/simplifiedchinese"
)
func TestGetServerReferralVariants(t *testing.T) {
cases := []struct {
name string
raw string
host string
port string
}{
{
name: "registrar whois server",
raw: "Registrar WHOIS Server: whois.markmonitor.com",
host: "whois.markmonitor.com",
port: "43",
},
{
name: "referral server with scheme and port",
raw: "ReferralServer: whois://whois.example.net:4343",
host: "whois.example.net",
port: "4343",
},
{
name: "refer short key",
raw: "refer: whois.nic.io",
host: "whois.nic.io",
port: "43",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
host, port := getServer(tc.raw + "\n")
if host != tc.host || port != tc.port {
t.Fatalf("unexpected server, got=%s:%s want=%s:%s", host, port, tc.host, tc.port)
}
})
}
}
func TestNormalizeServerListDedupAndTrim(t *testing.T) {
got := normalizeServerList([]string{" whois.a.com ", "", "WHOIS.A.COM", "whois.b.com"})
if len(got) != 2 {
t.Fatalf("unexpected size: %d", len(got))
}
if got[0] != "whois.a.com" || got[1] != "whois.b.com" {
t.Fatalf("unexpected values: %#v", got)
}
}
func TestWhoisWithOptionsContextCanceled(t *testing.T) {
addr, shutdown := startMockWhoisServerWithDelay(t, strings.Join([]string{
"Domain Name: EXAMPLE.COM",
"Registrar: TEST-REG",
"",
}, "\n"), 500*time.Millisecond)
defer shutdown()
c := NewClient().SetTimeout(2 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Millisecond)
defer cancel()
_, err := c.WhoisWithOptionsContext(ctx, "example.com", QueryOptions{
Level: QueryAuto,
OverrideServer: addr,
})
if err == nil {
t.Fatal("expected context timeout error")
}
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("expected context deadline exceeded, got: %v", err)
}
}
func TestWhoisEmptyResponseTypedError(t *testing.T) {
addr, shutdown := startMockWhoisServer(t, "")
defer shutdown()
c := NewClient()
_, err := c.WhoisWithOptions("example.com", QueryOptions{
Level: QueryAuto,
OverrideServer: addr,
})
if err == nil {
t.Fatal("expected empty response error")
}
if !errors.Is(err, ErrEmptyResponse) {
t.Fatalf("expected ErrEmptyResponse, got: %v", err)
}
}
func TestResultMetaAndTypedError(t *testing.T) {
addr, shutdown := startMockWhoisServer(t, strings.Join([]string{
"Domain Name: EXAMPLE.COM",
"",
}, "\n"))
defer shutdown()
c := NewClient()
r, err := c.WhoisWithOptions("example.com", QueryOptions{
Level: QueryAuto,
OverrideServer: addr,
})
if err != nil {
t.Fatalf("WhoisWithOptions() error: %v", err)
}
meta := r.Meta()
if meta.Source != "whois" {
t.Fatalf("unexpected source: %q", meta.Source)
}
if meta.Server == "" || !strings.Contains(meta.Server, ":") {
t.Fatalf("unexpected server: %q", meta.Server)
}
if meta.RawLen <= 0 {
t.Fatalf("unexpected raw length: %d", meta.RawLen)
}
if meta.ReasonCode != ErrorCodeParseWeak {
t.Fatalf("unexpected reason code: %s", meta.ReasonCode)
}
if r.TypedError() == nil || !errors.Is(r.TypedError(), ErrParseWeak) {
t.Fatalf("expected typed parse-weak error, got: %v", r.TypedError())
}
}
func TestAccessDeniedResultReason(t *testing.T) {
addr, shutdown := startMockWhoisServer(t, strings.Join([]string{
"Requests of this client are not permitted. Please use https://www.nic.ch/whois/ for queries.",
"",
}, "\n"))
defer shutdown()
c := NewClient()
r, err := c.WhoisWithOptions("nic.ch", QueryOptions{
Level: QueryAuto,
OverrideServer: addr,
})
if err != nil {
t.Fatalf("WhoisWithOptions() error: %v", err)
}
if r.Meta().ReasonCode != ErrorCodeAccessDenied {
t.Fatalf("unexpected reason code: %s", r.Meta().ReasonCode)
}
if !errors.Is(r.TypedError(), ErrAccessDenied) {
t.Fatalf("expected ErrAccessDenied, got: %v", r.TypedError())
}
}
func TestWhoisDetectLegacyCharsetGBK(t *testing.T) {
raw := strings.Join([]string{
"Domain Name: test.com",
"Registrar: 测试注册商",
"Name Server: ns1.test.com",
"",
}, "\n")
gbkBytes, err := simplifiedchinese.GBK.NewEncoder().Bytes([]byte(raw))
if err != nil {
t.Fatalf("encode GBK failed: %v", err)
}
addr, shutdown := startRawWhoisServer(t, gbkBytes)
defer shutdown()
c := NewClient()
r, err := c.WhoisWithOptions("test.com", QueryOptions{
Level: QueryAuto,
OverrideServer: addr,
})
if err != nil {
t.Fatalf("WhoisWithOptions() error: %v", err)
}
if r.Meta().Charset != "gbk" && r.Meta().Charset != "gb18030" {
t.Fatalf("unexpected charset: %q", r.Meta().Charset)
}
if !strings.Contains(r.RawData(), "测试注册商") {
t.Fatalf("decoded raw data does not contain expected chinese text: %q", r.RawData())
}
}
func startRawWhoisServer(t *testing.T, payload []byte) (addr string, shutdown func()) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen raw whois failed: %v", err)
}
done := make(chan struct{})
go func() {
for {
conn, err := ln.Accept()
if err != nil {
select {
case <-done:
return
default:
return
}
}
go func(c net.Conn) {
defer c.Close()
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
_, _ = bufio.NewReader(c).ReadString('\n')
if len(payload) > 0 {
_, _ = c.Write(payload)
}
}(conn)
}
}()
return ln.Addr().String(), func() {
close(done)
_ = ln.Close()
}
}