package whois import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "sync/atomic" "testing" "time" ) func TestRDAPClientQueryDomain(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/rdap/domain/example.com" { http.NotFound(w, r) return } w.Header().Set("Content-Type", "application/rdap+json") _, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.com"}`)) })) defer srv.Close() boot := &RDAPBootstrap{ Version: "1.0", Services: []RDAPService{ { TLDs: []string{"com"}, URLs: []string{srv.URL + "/rdap/"}, }, }, } c, err := NewRDAPClientWithBootstrap(boot) if err != nil { t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err) } resp, err := c.QueryDomain(context.Background(), "example.com") if err != nil { t.Fatalf("QueryDomain() error: %v", err) } if resp.StatusCode != http.StatusOK { t.Fatalf("unexpected status: %d", resp.StatusCode) } if !strings.Contains(string(resp.Body), `"ldhName":"example.com"`) { t.Fatalf("unexpected body: %s", string(resp.Body)) } } func TestRDAPClientQueryDomainJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/domain/example.org" { http.NotFound(w, r) return } _, _ = w.Write([]byte(`{"ldhName":"example.org","status":["active"]}`)) })) defer srv.Close() c, err := NewRDAPClientWithBootstrap(&RDAPBootstrap{ Version: "1.0", Services: []RDAPService{ {TLDs: []string{"org"}, URLs: []string{srv.URL}}, }, }) if err != nil { t.Fatalf("NewRDAPClientWithBootstrap() error: %v", err) } var out map[string]any _, err = c.QueryDomainJSON(context.Background(), "example.org", &out) if err != nil { t.Fatalf("QueryDomainJSON() error: %v", err) } if out["ldhName"] != "example.org" { t.Fatalf("unexpected json field: %#v", out) } } func TestNewRDAPClientWithLayeredBootstrap(t *testing.T) { rdapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/rdap/domain/example.dev" { http.NotFound(w, r) return } _, _ = w.Write([]byte(`{"objectClassName":"domain","ldhName":"example.dev"}`)) })) defer rdapSrv.Close() localRaw := `{"version":"1.0","services":[[["dev"],["` + rdapSrv.URL + `/rdap/"]]]}` localPath := filepath.Join(t.TempDir(), "rdap_local.json") if err := os.WriteFile(localPath, []byte(localRaw), 0644); err != nil { t.Fatalf("write local bootstrap failed: %v", err) } c, err := NewRDAPClientWithLayeredBootstrap(context.Background(), RDAPBootstrapLoadOptions{ LocalFiles: []string{localPath}, }) if err != nil { t.Fatalf("NewRDAPClientWithLayeredBootstrap() error: %v", err) } resp, err := c.QueryDomain(context.Background(), "example.dev") if err != nil { t.Fatalf("QueryDomain() error: %v", err) } if resp.StatusCode != http.StatusOK || !strings.Contains(string(resp.Body), `"example.dev"`) { t.Fatalf("unexpected response: status=%d body=%s", resp.StatusCode, string(resp.Body)) } } func TestNewRDAPClientWithLayeredBootstrapCacheTTL(t *testing.T) { cacheKey := t.Name() + "-cache" ClearRDAPBootstrapLayeredCache(cacheKey) var bootstrapHit int32 bootstrapRaw := `{"version":"2.0","services":[[["dev"],["https://rdap.example.dev/rdap/"]]]}` bootstrapSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt32(&bootstrapHit, 1) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(bootstrapRaw)) })) defer bootstrapSrv.Close() loadOpt := RDAPBootstrapLoadOptions{ RefreshRemote: true, RemoteURL: bootstrapSrv.URL, CacheTTL: time.Minute, CacheKey: cacheKey, } c1, err := NewRDAPClientWithLayeredBootstrap(context.Background(), loadOpt) if err != nil { t.Fatalf("first NewRDAPClientWithLayeredBootstrap() error: %v", err) } if c1 == nil { t.Fatal("first client should not be nil") } c2, err := NewRDAPClientWithLayeredBootstrap(context.Background(), loadOpt) if err != nil { t.Fatalf("second NewRDAPClientWithLayeredBootstrap() error: %v", err) } if c2 == nil { t.Fatal("second client should not be nil") } if got := atomic.LoadInt32(&bootstrapHit); got != 1 { t.Fatalf("expected cached bootstrap to fetch remote once, got=%d", got) } }