package handlers import ( "context" "fmt" "log/slog" "net/http" "net/http/httptest" "strings" "testing" "sneak.berlin/go/pixa/internal/httpfetcher" "sneak.berlin/go/pixa/internal/imageprocessor" "sneak.berlin/go/pixa/internal/imgcache" ) // TestServerBusyAnswers503 checks that both image routes answer 503 with // a clear error when the image service gives up waiting for a free upstream // connection or processing slot, wrapped as the service wraps them. func TestServerBusyAnswers503(t *testing.T) { t.Parallel() h := &Handlers{log: slog.New(slog.DiscardHandler)} req := &imgcache.ImageRequest{SourceHost: "img.example.com", SourcePath: "/a.jpg"} for _, err := range []error{ fmt.Errorf("upstream fetch failed: %w", httpfetcher.ErrTooManyConnections), fmt.Errorf("image processing failed: %w", imageprocessor.ErrTooManyImages), } { plain := httptest.NewRecorder() h.respondImageError(plain, req, err) encrypted := httptest.NewRecorder() h.handleImageError(encrypted, err) for route, rec := range map[string]*httptest.ResponseRecorder{ "/v1/image/": plain, "/v1/e/": encrypted, } { if rec.Code != http.StatusServiceUnavailable { t.Errorf("%s for %v: status = %d, want %d", route, err, rec.Code, http.StatusServiceUnavailable) } if !strings.Contains(rec.Body.String(), "server busy, try again later") { t.Errorf("%s for %v: body = %q, want the server busy error", route, err, rec.Body.String()) } } } } // TestProcessingTimedOutAnswers503 checks that respondImageError and // handleImageError, which answer the errors of the two image routes, answer // 503 with the server busy error when the image service reports that // processing stopped at the deadline of the request doing it, wrapped as the // service wraps it. A request that waited for that processing gets this error // while its own deadline may have time left. The request's source only goes // into a log line, so it is left empty. func TestProcessingTimedOutAnswers503(t *testing.T) { t.Parallel() h := &Handlers{log: slog.New(slog.DiscardHandler)} err := fmt.Errorf("%w: failed to encode: %w", imgcache.ErrProcessingTimedOut, context.DeadlineExceeded) plain := httptest.NewRecorder() h.respondImageError(plain, &imgcache.ImageRequest{}, err) encrypted := httptest.NewRecorder() h.handleImageError(encrypted, err) for name, rec := range map[string]*httptest.ResponseRecorder{ "respondImageError": plain, "handleImageError": encrypted, } { t.Logf("%s: status = %d, body = %q", name, rec.Code, rec.Body.String()) if rec.Code != http.StatusServiceUnavailable { t.Errorf("%s: status = %d, want %d", name, rec.Code, http.StatusServiceUnavailable) } if !strings.Contains(rec.Body.String(), "server busy, try again later") { t.Errorf("%s: body = %q, want the server busy error", name, rec.Body.String()) } } }