A community based topic aggregation platform built on atproto
at main 231 lines 7.3 kB view raw
1package imageproxy 2 3import ( 4 "context" 5 "errors" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 "time" 10) 11 12func TestPDSFetcher_Fetch_Success(t *testing.T) { 13 // Setup test server that returns blob data 14 expectedData := []byte("test image data") 15 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 // Verify the request path and query parameters 17 if r.URL.Path != "/xrpc/com.atproto.sync.getBlob" { 18 t.Errorf("unexpected path: %s", r.URL.Path) 19 } 20 if r.URL.Query().Get("did") != "did:plc:test123" { 21 t.Errorf("unexpected did: %s", r.URL.Query().Get("did")) 22 } 23 if r.URL.Query().Get("cid") != "bafyreicid123" { 24 t.Errorf("unexpected cid: %s", r.URL.Query().Get("cid")) 25 } 26 w.WriteHeader(http.StatusOK) 27 w.Write(expectedData) 28 })) 29 defer server.Close() 30 31 fetcher := NewPDSFetcher(5 * time.Second, 10) 32 ctx := context.Background() 33 34 data, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 35 if err != nil { 36 t.Fatalf("expected no error, got: %v", err) 37 } 38 if string(data) != string(expectedData) { 39 t.Errorf("expected data %q, got %q", expectedData, data) 40 } 41} 42 43func TestPDSFetcher_Fetch_NotFound(t *testing.T) { 44 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 w.WriteHeader(http.StatusNotFound) 46 })) 47 defer server.Close() 48 49 fetcher := NewPDSFetcher(5 * time.Second, 10) 50 ctx := context.Background() 51 52 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 53 if !errors.Is(err, ErrPDSNotFound) { 54 t.Errorf("expected ErrPDSNotFound, got: %v", err) 55 } 56} 57 58func TestPDSFetcher_Fetch_Timeout(t *testing.T) { 59 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 // Sleep longer than the timeout 61 time.Sleep(200 * time.Millisecond) 62 w.WriteHeader(http.StatusOK) 63 })) 64 defer server.Close() 65 66 // Use a very short timeout 67 fetcher := NewPDSFetcher(50 * time.Millisecond, 10) 68 ctx := context.Background() 69 70 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 71 if !errors.Is(err, ErrPDSTimeout) { 72 t.Errorf("expected ErrPDSTimeout, got: %v", err) 73 } 74} 75 76func TestPDSFetcher_Fetch_NetworkError(t *testing.T) { 77 fetcher := NewPDSFetcher(5 * time.Second, 10) 78 ctx := context.Background() 79 80 // Use an invalid URL that will cause a network error 81 _, err := fetcher.Fetch(ctx, "http://localhost:99999", "did:plc:test123", "bafyreicid123") 82 if !errors.Is(err, ErrPDSFetchFailed) { 83 t.Errorf("expected ErrPDSFetchFailed, got: %v", err) 84 } 85} 86 87func TestPDSFetcher_Fetch_ContextCancellation(t *testing.T) { 88 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 89 // Sleep to allow context cancellation 90 time.Sleep(100 * time.Millisecond) 91 w.WriteHeader(http.StatusOK) 92 })) 93 defer server.Close() 94 95 fetcher := NewPDSFetcher(5 * time.Second, 10) 96 ctx, cancel := context.WithCancel(context.Background()) 97 98 // Cancel the context immediately 99 cancel() 100 101 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 102 if err == nil { 103 t.Error("expected error due to context cancellation") 104 } 105 // Context cancellation should return ErrPDSTimeout 106 if !errors.Is(err, ErrPDSTimeout) { 107 t.Errorf("expected ErrPDSTimeout for context cancellation, got: %v", err) 108 } 109} 110 111func TestPDSFetcher_Fetch_ServerError(t *testing.T) { 112 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 113 w.WriteHeader(http.StatusInternalServerError) 114 })) 115 defer server.Close() 116 117 fetcher := NewPDSFetcher(5 * time.Second, 10) 118 ctx := context.Background() 119 120 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 121 if !errors.Is(err, ErrPDSFetchFailed) { 122 t.Errorf("expected ErrPDSFetchFailed, got: %v", err) 123 } 124} 125 126func TestPDSFetcher_Fetch_URLConstruction(t *testing.T) { 127 var capturedURL string 128 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 129 capturedURL = r.URL.String() 130 w.WriteHeader(http.StatusOK) 131 w.Write([]byte("data")) 132 })) 133 defer server.Close() 134 135 fetcher := NewPDSFetcher(5 * time.Second, 10) 136 ctx := context.Background() 137 138 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:abc123", "bafyreicid456") 139 if err != nil { 140 t.Fatalf("unexpected error: %v", err) 141 } 142 143 expectedPath := "/xrpc/com.atproto.sync.getBlob?cid=bafyreicid456&did=did%3Aplc%3Aabc123" 144 if capturedURL != expectedPath { 145 t.Errorf("expected URL %q, got %q", expectedPath, capturedURL) 146 } 147} 148 149func TestPDSFetcher_Fetch_ImageTooLarge_ContentLength(t *testing.T) { 150 // Server returns Content-Length header indicating size exceeds limit 151 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 152 // Set Content-Length larger than the max (1MB) 153 w.Header().Set("Content-Length", "2097152") // 2MB 154 w.WriteHeader(http.StatusOK) 155 // Don't actually write 2MB of data 156 })) 157 defer server.Close() 158 159 // Use 1MB max size 160 fetcher := NewPDSFetcher(5*time.Second, 1) 161 ctx := context.Background() 162 163 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 164 if !errors.Is(err, ErrImageTooLarge) { 165 t.Errorf("expected ErrImageTooLarge, got: %v", err) 166 } 167} 168 169func TestPDSFetcher_Fetch_ImageTooLarge_StreamingBody(t *testing.T) { 170 // Server doesn't send Content-Length but streams more data than allowed 171 largeData := make([]byte, 2*1024*1024) // 2MB of zeros 172 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 173 w.WriteHeader(http.StatusOK) 174 w.Write(largeData) 175 })) 176 defer server.Close() 177 178 // Use 1MB max size 179 fetcher := NewPDSFetcher(5*time.Second, 1) 180 ctx := context.Background() 181 182 _, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 183 if !errors.Is(err, ErrImageTooLarge) { 184 t.Errorf("expected ErrImageTooLarge, got: %v", err) 185 } 186} 187 188func TestPDSFetcher_Fetch_SizeWithinLimit(t *testing.T) { 189 // Server returns data within the limit 190 testData := make([]byte, 512*1024) // 512KB 191 for i := range testData { 192 testData[i] = byte(i % 256) 193 } 194 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 195 w.WriteHeader(http.StatusOK) 196 w.Write(testData) 197 })) 198 defer server.Close() 199 200 // Use 1MB max size 201 fetcher := NewPDSFetcher(5*time.Second, 1) 202 ctx := context.Background() 203 204 data, err := fetcher.Fetch(ctx, server.URL, "did:plc:test123", "bafyreicid123") 205 if err != nil { 206 t.Fatalf("expected no error, got: %v", err) 207 } 208 if len(data) != len(testData) { 209 t.Errorf("expected %d bytes, got %d", len(testData), len(data)) 210 } 211} 212 213func TestPDSFetcher_Fetch_DefaultMaxSize(t *testing.T) { 214 // Test that 0 for maxSizeMB uses the default 215 fetcher := NewPDSFetcher(5*time.Second, 0) 216 expectedDefault := int64(DefaultMaxSourceSizeMB) * 1024 * 1024 217 218 if fetcher.maxSizeBytes != expectedDefault { 219 t.Errorf("expected default maxSizeBytes %d, got %d", expectedDefault, fetcher.maxSizeBytes) 220 } 221} 222 223func TestPDSFetcher_Fetch_NegativeMaxSize(t *testing.T) { 224 // Test that negative maxSizeMB uses the default 225 fetcher := NewPDSFetcher(5*time.Second, -5) 226 expectedDefault := int64(DefaultMaxSourceSizeMB) * 1024 * 1024 227 228 if fetcher.maxSizeBytes != expectedDefault { 229 t.Errorf("expected default maxSizeBytes %d, got %d", expectedDefault, fetcher.maxSizeBytes) 230 } 231}