Files
at-container-registry/pkg/atproto/relays_test.go
T

378 lines
11 KiB
Go

package atproto
import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestKnownRelays(t *testing.T) {
if len(KnownRelays) == 0 {
t.Fatal("KnownRelays should not be empty")
}
seen := make(map[string]bool)
for _, relay := range KnownRelays {
if relay.Name == "" {
t.Error("relay name should not be empty")
}
if relay.URL == "" {
t.Errorf("relay %q has empty URL", relay.Name)
}
u, err := url.Parse(relay.URL)
if err != nil {
t.Errorf("relay %q has invalid URL %q: %v", relay.Name, relay.URL, err)
continue
}
if u.Scheme != "https" {
t.Errorf("relay %q URL scheme = %q, want https", relay.Name, u.Scheme)
}
if u.Host == "" {
t.Errorf("relay %q URL has no host", relay.Name)
}
if seen[relay.URL] {
t.Errorf("duplicate relay URL: %s", relay.URL)
}
seen[relay.URL] = true
}
}
func TestRelayHTTPError(t *testing.T) {
err := &RelayHTTPError{StatusCode: 404}
if err.Error() != "relay returned status 404" {
t.Errorf("Error() = %q, want %q", err.Error(), "relay returned status 404")
}
// Should satisfy errors.As
var target *RelayHTTPError
if !errors.As(err, &target) {
t.Error("errors.As should match *RelayHTTPError")
}
if target.StatusCode != 404 {
t.Errorf("StatusCode = %d, want 404", target.StatusCode)
}
}
func TestRequestCrawl(t *testing.T) {
t.Run("empty endpoint is no-op", func(t *testing.T) {
if err := RequestCrawl("", "https://hold.example.com"); err != nil {
t.Errorf("expected nil error for empty endpoint, got %v", err)
}
})
t.Run("success", func(t *testing.T) {
var gotHostname string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != SyncRequestCrawl {
t.Errorf("path = %q, want %q", r.URL.Path, SyncRequestCrawl)
}
if r.Method != "POST" {
t.Errorf("method = %q, want POST", r.Method)
}
var body map[string]string
json.NewDecoder(r.Body).Decode(&body)
gotHostname = body["hostname"]
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
err := RequestCrawl(srv.URL, "https://hold.example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gotHostname != "hold.example.com" {
t.Errorf("hostname = %q, want %q", gotHostname, "hold.example.com")
}
})
t.Run("relay returns error", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer srv.Close()
err := RequestCrawl(srv.URL, "https://hold.example.com")
if err == nil {
t.Fatal("expected error for 500 response")
}
})
}
func TestCheckRepoStatus(t *testing.T) {
t.Run("success", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != SyncGetRepoStatus {
t.Errorf("path = %q, want %q", r.URL.Path, SyncGetRepoStatus)
}
did := r.URL.Query().Get("did")
if did != "did:web:hold.example.com" {
t.Errorf("did = %q, want %q", did, "did:web:hold.example.com")
}
json.NewEncoder(w).Encode(RepoStatus{
DID: "did:web:hold.example.com",
Active: true,
Rev: "abc123",
})
}))
defer srv.Close()
status, err := CheckRepoStatus(srv.URL, "did:web:hold.example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !status.Active {
t.Error("expected Active = true")
}
if status.Rev != "abc123" {
t.Errorf("Rev = %q, want %q", status.Rev, "abc123")
}
})
t.Run("not found returns RelayHTTPError", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer srv.Close()
_, err := CheckRepoStatus(srv.URL, "did:web:unknown.com")
if err == nil {
t.Fatal("expected error for 404 response")
}
var httpErr *RelayHTTPError
if !errors.As(err, &httpErr) {
t.Fatalf("expected RelayHTTPError, got %T: %v", err, err)
}
if httpErr.StatusCode != 404 {
t.Errorf("StatusCode = %d, want 404", httpErr.StatusCode)
}
})
t.Run("connection failure returns plain error", func(t *testing.T) {
_, err := CheckRepoStatus("http://127.0.0.1:1", "did:web:test")
if err == nil {
t.Fatal("expected error for connection failure")
}
var httpErr *RelayHTTPError
if errors.As(err, &httpErr) {
t.Error("connection failure should not be RelayHTTPError")
}
})
}
func TestCheckHostStatus(t *testing.T) {
t.Run("success", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != SyncGetHostStatus {
t.Errorf("path = %q, want %q", r.URL.Path, SyncGetHostStatus)
}
hostname := r.URL.Query().Get("hostname")
if hostname != "hold.example.com" {
t.Errorf("hostname = %q, want %q", hostname, "hold.example.com")
}
json.NewEncoder(w).Encode(HostStatus{
Hostname: "hold.example.com",
Active: true,
Seq: 42,
})
}))
defer srv.Close()
status, err := CheckHostStatus(srv.URL, "hold.example.com")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !status.Active {
t.Error("expected Active = true")
}
if status.Seq != 42 {
t.Errorf("Seq = %d, want 42", status.Seq)
}
})
t.Run("unknown host returns RelayHTTPError", func(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
defer srv.Close()
_, err := CheckHostStatus(srv.URL, "unknown.example.com")
if err == nil {
t.Fatal("expected error for 400 response")
}
var httpErr *RelayHTTPError
if !errors.As(err, &httpErr) {
t.Fatalf("expected RelayHTTPError, got %T: %v", err, err)
}
if httpErr.StatusCode != 400 {
t.Errorf("StatusCode = %d, want 400", httpErr.StatusCode)
}
})
}
func TestCheckRelayStatus_AllEndpointsSucceed(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case SyncRequestCrawl:
w.WriteHeader(http.StatusBadRequest) // empty hostname = 400
case SyncGetHostStatus:
json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true})
case SyncGetRepoStatus:
json.NewEncoder(w).Encode(RepoStatus{DID: "did:web:hold.example.com", Active: true, Rev: "rev123"})
case SyncListReposByCollection:
json.NewEncoder(w).Encode(map[string]any{"repos": []any{}})
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
status := CheckRelayStatus(srv.URL, "hold.example.com", "did:web:hold.example.com")
if !status.Online {
t.Error("expected Online = true")
}
if status.Error != "" {
t.Errorf("expected no error, got %q", status.Error)
}
if !status.HasRequestCrawl {
t.Error("expected HasRequestCrawl = true")
}
if status.RequestCrawlStatus != http.StatusBadRequest {
t.Errorf("RequestCrawlStatus = %d, want %d", status.RequestCrawlStatus, http.StatusBadRequest)
}
if !status.HasListReposByCollection {
t.Error("expected HasListReposByCollection = true")
}
if status.RepoStatus == nil {
t.Fatal("expected RepoStatus to be set")
}
if !status.RepoStatus.Active {
t.Error("expected RepoStatus.Active = true")
}
if status.HostStatus == nil {
t.Fatal("expected HostStatus to be set")
}
if !status.HostStatus.Active {
t.Error("expected HostStatus.Active = true")
}
}
func TestCheckRelayStatus_OnlineButUnknownHost(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case SyncRequestCrawl:
w.WriteHeader(http.StatusBadRequest) // empty hostname = 400
case SyncGetHostStatus:
w.WriteHeader(http.StatusBadRequest) // relay doesn't know this host
case SyncGetRepoStatus:
w.WriteHeader(http.StatusNotFound) // relay doesn't know this DID
case SyncListReposByCollection:
w.WriteHeader(http.StatusNotFound) // not supported
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
status := CheckRelayStatus(srv.URL, "local.example.com", "did:web:local.example.com")
if !status.Online {
t.Error("expected Online = true (relay responded with HTTP errors)")
}
if status.Error != "" {
t.Errorf("expected no error, got %q", status.Error)
}
if status.HasListReposByCollection {
t.Error("expected HasListReposByCollection = false")
}
if status.RepoStatus != nil {
t.Error("expected RepoStatus = nil")
}
if status.HostStatus != nil {
t.Error("expected HostStatus = nil")
}
}
func TestCheckRelayStatus_Offline(t *testing.T) {
// Use an address that will refuse connections
status := CheckRelayStatus("http://127.0.0.1:1", "hold.example.com", "did:web:hold.example.com")
if status.Online {
t.Error("expected Online = false for unreachable relay")
}
if status.Error == "" {
t.Error("expected error message for offline relay")
}
}
func TestCheckRelayStatus_NoListReposByCollection(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case SyncRequestCrawl:
w.WriteHeader(http.StatusBadRequest) // empty hostname = 400
case SyncGetHostStatus:
json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true})
case SyncGetRepoStatus:
json.NewEncoder(w).Encode(RepoStatus{DID: "did:web:hold.example.com", Active: true, Rev: "r1"})
case SyncListReposByCollection:
w.WriteHeader(http.StatusNotFound) // 404 = not supported
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
status := CheckRelayStatus(srv.URL, "hold.example.com", "did:web:hold.example.com")
if !status.Online {
t.Error("expected Online = true")
}
if status.HasListReposByCollection {
t.Error("expected HasListReposByCollection = false (404)")
}
if status.RepoStatus == nil || !status.RepoStatus.Active {
t.Error("expected RepoStatus to be active")
}
}
func TestCheckRelayStatus_AuthRequired(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case SyncRequestCrawl:
w.WriteHeader(http.StatusForbidden) // auth required
case SyncGetHostStatus:
json.NewEncoder(w).Encode(HostStatus{Hostname: "hold.example.com", Active: true})
case SyncGetRepoStatus:
json.NewEncoder(w).Encode(RepoStatus{DID: "did:web:hold.example.com", Active: true, Rev: "r1"})
case SyncListReposByCollection:
json.NewEncoder(w).Encode(map[string]any{"repos": []any{}})
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer srv.Close()
status := CheckRelayStatus(srv.URL, "hold.example.com", "did:web:hold.example.com")
if !status.Online {
t.Error("expected Online = true")
}
if status.HasRequestCrawl {
t.Error("expected HasRequestCrawl = false (auth required)")
}
if status.RequestCrawlStatus != http.StatusForbidden {
t.Errorf("RequestCrawlStatus = %d, want %d", status.RequestCrawlStatus, http.StatusForbidden)
}
if !status.HasListReposByCollection {
t.Error("expected HasListReposByCollection = true")
}
if status.RepoStatus == nil || !status.RepoStatus.Active {
t.Error("expected RepoStatus to be active")
}
}