check / check (push) Canceled after 0s
HandleImage parses the path and verifies the signature, and HandleImageEnc decrypts the token. Each then calls serveImage, which chooses the format for auto, gets the image and serves it: the headers, ETag and 304, HEAD, the copy and the log line. respondImageError answers the image service's errors on both routes, and handleImageError is gone. /v1/image/ now answers 504 when the upstream sends no response headers within upstream_fetch_timeout, as README.md documented for /v1/e/. /v1/e/ now logs these errors at error level, as /v1/image/ does. README.md's route tables list 504 for /v1/image/. A new test sends the same upstream fetch errors through both routes. Model: opus-5-5
75 lines
2.4 KiB
Go
75 lines
2.4 KiB
Go
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/imagerequest"
|
|
"sneak.berlin/go/pixa/internal/imgcache"
|
|
)
|
|
|
|
// TestServerBusyAnswers503 checks that respondImageError, which answers the
|
|
// errors of both image routes, answers 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 := &imagerequest.Request{
|
|
Source: imagerequest.Source{Host: "img.example.com", Path: "/a.jpg"},
|
|
}
|
|
|
|
for _, err := range []error{
|
|
fmt.Errorf("upstream fetch failed: %w", httpfetcher.ErrTooManyConnections),
|
|
fmt.Errorf("image processing failed: %w", imageprocessor.ErrTooManyImages),
|
|
} {
|
|
rec := httptest.NewRecorder()
|
|
h.respondImageError(rec, req, err)
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("for %v: status = %d, want %d",
|
|
err, rec.Code, http.StatusServiceUnavailable)
|
|
}
|
|
|
|
if !strings.Contains(rec.Body.String(), "server busy, try again later") {
|
|
t.Errorf("for %v: body = %q, want the server busy error",
|
|
err, rec.Body.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestProcessingTimedOutAnswers503 checks that respondImageError, which
|
|
// answers the errors of both image routes, answers 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)
|
|
|
|
rec := httptest.NewRecorder()
|
|
h.respondImageError(rec, &imagerequest.Request{}, err)
|
|
t.Logf("status = %d, body = %q", rec.Code, rec.Body.String())
|
|
|
|
if rec.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusServiceUnavailable)
|
|
}
|
|
|
|
if !strings.Contains(rec.Body.String(), "server busy, try again later") {
|
|
t.Errorf("body = %q, want the server busy error", rec.Body.String())
|
|
}
|
|
}
|