mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-21 01:34:16 +00:00
378 lines
11 KiB
Go
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")
|
|
}
|
|
}
|