check / check (push) Waiting to run
A /v1/image/ URL whose last segment is a size with no format, such as 800x600 or orig, is served as JPEG XL and signed as jxl, so it shares the signature of the same URL ending in .jxl. An encrypted URL whose token holds no format is served as JPEG XL, as encurl.DefaultFormat is now jxl. The generator page selects JPEG XL by default, and a form with an empty format, or none, makes a URL whose name ends in .jxl. The image processor no longer takes an empty format as orig: both routes give every request a format, so it refuses a request with none instead of keeping a second default. auto still ends with JPEG. Model: opus-5-5
163 lines
4.5 KiB
Go
163 lines
4.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"maps"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/davidbyttow/govips/v2/vips"
|
|
|
|
"sneak.berlin/go/pixa/internal/encurl"
|
|
"sneak.berlin/go/pixa/internal/imgcache"
|
|
"sneak.berlin/go/pixa/internal/signature"
|
|
)
|
|
|
|
// requireJPEGXL requires that rec answers 200 with a JPEG XL image.
|
|
func requireJPEGXL(t *testing.T, rec *httptest.ResponseRecorder) {
|
|
t.Helper()
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body %q",
|
|
rec.Code, http.StatusOK, rec.Body.String())
|
|
}
|
|
|
|
if got := rec.Header().Get("Content-Type"); got != jxlType {
|
|
t.Errorf("Content-Type = %q, want %s", got, jxlType)
|
|
}
|
|
|
|
if got := vips.DetermineImageType(rec.Body.Bytes()); got != vips.ImageTypeJXL {
|
|
t.Errorf("body is %s, want jxl", vips.ImageTypes[got])
|
|
}
|
|
}
|
|
|
|
// TestImageWithoutFormat_ServesJPEGXL verifies that a /v1/image/ URL whose
|
|
// last segment is a size with no format, 50x50 or orig, answers JPEG XL.
|
|
func TestImageWithoutFormat_ServesJPEGXL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
route := newImageRoute(t, newPhotoFetcher(t, allowlistedHost))
|
|
|
|
for _, size := range []string{"50x50", "orig"} {
|
|
target := "/v1/image/" + allowlistedHost + photoPath + "/" + size
|
|
requireJPEGXL(t, sendGet(t, route, target))
|
|
}
|
|
}
|
|
|
|
// TestImageWithoutFormat_SignedAsJXL verifies that a /v1/image/ URL with no
|
|
// format is signed as jxl: the signature made for the URL ending in .jxl is
|
|
// accepted for the same URL without .jxl.
|
|
func TestImageWithoutFormat_SignedAsJXL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
route := newImageRoute(t, newPhotoFetcher(t, signedHost))
|
|
expires := time.Now().Add(time.Hour)
|
|
|
|
sig := signature.New(testSigningKey).Sign(&signature.Request{
|
|
SourceHost: signedHost,
|
|
SourcePath: photoPath,
|
|
Width: 50,
|
|
Height: 50,
|
|
Format: string(imgcache.FormatJXL),
|
|
Quality: encurl.DefaultQuality,
|
|
FitMode: string(imgcache.FitCover),
|
|
Expires: expires,
|
|
})
|
|
query := fmt.Sprintf("?sig=%s&exp=%d", sig, expires.Unix())
|
|
|
|
for _, size := range []string{"50x50.jxl", "50x50"} {
|
|
target := "/v1/image/" + signedHost + photoPath + "/" + size + query
|
|
requireJPEGXL(t, sendGet(t, route, target))
|
|
}
|
|
}
|
|
|
|
// TestImageEncWithoutFormat_ServesJPEGXL verifies that an encrypted URL whose
|
|
// token holds no format answers JPEG XL.
|
|
func TestImageEncWithoutFormat_ServesJPEGXL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h, srv := newSignedHostServer(t, slog.New(slog.DiscardHandler))
|
|
|
|
token, err := h.encGen.Generate(&encurl.Payload{
|
|
SourceHost: signedHost,
|
|
SourcePath: photoPath,
|
|
Width: 50,
|
|
Height: 50,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Generate() error = %v", err)
|
|
}
|
|
|
|
requireJPEGXL(t, getEncToken(srv, token))
|
|
}
|
|
|
|
// TestGeneratorPage_SelectsJPEGXL verifies that the generator page's format
|
|
// choice is JPEG XL until another is chosen.
|
|
func TestGeneratorPage_SelectsJPEGXL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
h, srv := newCSRFTestRouter(t)
|
|
|
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
|
|
req.AddCookie(newSessionCookie(t, h))
|
|
|
|
rec := httptest.NewRecorder()
|
|
srv.ServeHTTP(rec, req)
|
|
|
|
if !strings.Contains(rec.Body.String(), `<option value="jxl" selected>`) {
|
|
t.Errorf("generator page does not select JPEG XL: %s", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestGeneratePost_NoFormat_MakesJPEGXLURL verifies that the generator form
|
|
// sent with an empty format field, or with none, makes a URL whose name ends
|
|
// in .jxl and which answers JPEG XL.
|
|
func TestGeneratePost_NoFormat_MakesJPEGXLURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
photo := url.Values{
|
|
sourceURLField: {"https://" + signedHost + photoPath},
|
|
widthField: {"50"},
|
|
heightField: {"50"},
|
|
}
|
|
|
|
emptyFormat := maps.Clone(photo)
|
|
emptyFormat.Set(formatField, "")
|
|
|
|
for name, form := range map[string]url.Values{
|
|
"empty format field": emptyFormat,
|
|
"no format field": photo,
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, imageSrv := newSignedHostServer(t, slog.New(slog.DiscardHandler))
|
|
|
|
rec := generatePost(t, form)
|
|
|
|
match := generatedURLPattern.FindStringSubmatch(rec.Body.String())
|
|
if match == nil {
|
|
t.Fatalf("generator page shows no URL: %d %s",
|
|
rec.Code, rec.Body.String())
|
|
}
|
|
|
|
t.Logf("generated URL path: %s", match[1])
|
|
|
|
if !strings.HasSuffix(match[1], "/img.jxl") {
|
|
t.Errorf("generated URL %s does not end in /img.jxl", match[1])
|
|
}
|
|
|
|
imageRec := httptest.NewRecorder()
|
|
imageSrv.ServeHTTP(imageRec, httptest.NewRequestWithContext(
|
|
t.Context(), http.MethodGet, match[1], nil))
|
|
|
|
requireJPEGXL(t, imageRec)
|
|
})
|
|
}
|
|
}
|