Test that a size whose computed side exceeds the maximum is refused (part of #241)
Failing tests first. Through both image routes, 0x8192 of a 3000x600 source and 8192x0 of a 600x3000 source must answer 400 with the JSON error body; orig, a size with both sides given and a 0 side whose computed side stays within 8192 must be served as before, after the refusals. In the image processor, a 0 side computed to exactly 8192 is kept and one above is refused with ErrOutputTooLarge, which does not exist yet. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
package imageprocessor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestImageProcessor_RefusesComputedSideAboveMaximum verifies that a size
|
||||||
|
// with one side 0, which Process computes from the input's proportions, is
|
||||||
|
// served when that side comes out at MaxOutputDimension and refused with
|
||||||
|
// ErrOutputTooLarge when it would be above. A 4096x1 or 1x4096 input given
|
||||||
|
// 2 for its short side has 8192 for its long side.
|
||||||
|
func TestImageProcessor_RefusesComputedSideAboveMaximum(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputWidth, inputHeight int
|
||||||
|
size Size
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{"width at the maximum", 4096, 1, Size{Height: 2}, nil},
|
||||||
|
{"width above the maximum", 4096, 1, Size{Height: 3}, ErrOutputTooLarge},
|
||||||
|
{"height at the maximum", 1, 4096, Size{Width: 2}, nil},
|
||||||
|
{"height above the maximum", 1, 4096, Size{Width: 3}, ErrOutputTooLarge},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
input := createTestJPEG(t, tt.inputWidth, tt.inputHeight)
|
||||||
|
|
||||||
|
result, err := New(Params{}).Process(t.Context(),
|
||||||
|
bytes.NewReader(input),
|
||||||
|
&Request{Size: tt.size, Format: FormatJPEG, FitMode: FitCover})
|
||||||
|
if result != nil {
|
||||||
|
t.Logf("Process() made %dx%d", result.Width, result.Height)
|
||||||
|
|
||||||
|
if max(result.Width, result.Height) != MaxOutputDimension {
|
||||||
|
t.Errorf("Process() made %dx%d, want a side of %d",
|
||||||
|
result.Width, result.Height, MaxOutputDimension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, tt.wantErr) {
|
||||||
|
t.Errorf("Process() error = %v, want %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"image/jpeg"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/pixa/internal/encurl"
|
||||||
|
"sneak.berlin/go/pixa/internal/imgcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestImageProxyRefusesComputedSideAboveMaximum requests sizes of a 3000x600
|
||||||
|
// and a 600x3000 PNG through both image routes. A size with one side 0, whose
|
||||||
|
// side computed from the source's proportions would be above 8192, is refused
|
||||||
|
// with 400 and the JSON error body. The sizes after those refusals are served
|
||||||
|
// as before, which also shows that a refusal is not cached as a failed fetch of
|
||||||
|
// the source: orig, a size with both sides given, and a size with one side 0
|
||||||
|
// whose computed side stays within 8192.
|
||||||
|
func TestImageProxyRefusesComputedSideAboveMaximum(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// The upstream paths of the two sources
|
||||||
|
const (
|
||||||
|
widePath = "/wide.png"
|
||||||
|
tallPath = "/tall.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
wide := encodeTestPNG(t, 3000, 600)
|
||||||
|
tall := encodeTestPNG(t, 600, 3000)
|
||||||
|
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
|
||||||
|
if r.URL.Path == tallPath {
|
||||||
|
_, _ = w.Write(tall)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = w.Write(wide)
|
||||||
|
}))
|
||||||
|
t.Cleanup(upstream.Close)
|
||||||
|
|
||||||
|
s, _, _ := startImageProxy(t, upstream)
|
||||||
|
|
||||||
|
generator, err := encurl.NewGenerator(testSigningKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encurl.NewGenerator: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
// size is the size of the /v1/image/ URL, and width and height are
|
||||||
|
// those of the encrypted URL
|
||||||
|
size string
|
||||||
|
width, height int
|
||||||
|
// wantWidth and wantHeight are the served image's, 0 for a refusal
|
||||||
|
wantWidth, wantHeight int
|
||||||
|
}{
|
||||||
|
{widePath, "0x8192", 0, 8192, 0, 0},
|
||||||
|
{tallPath, "8192x0", 8192, 0, 0, 0},
|
||||||
|
{widePath, "orig", 0, 0, 3000, 600},
|
||||||
|
{widePath, "300x200", 300, 200, 300, 200},
|
||||||
|
{widePath, "0x300", 0, 300, 1500, 300},
|
||||||
|
{tallPath, "300x0", 300, 0, 300, 1500},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
token, err := generator.Generate(&encurl.Payload{
|
||||||
|
SourceHost: upstreamHost, SourcePath: tt.path,
|
||||||
|
Width: tt.width, Height: tt.height, Format: imgcache.FormatJPEG,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encurl Generate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range []string{
|
||||||
|
"/v1/image/" + upstreamHost + tt.path + "/" + tt.size + ".jpeg",
|
||||||
|
"/v1/e/" + token + "/img.jpg",
|
||||||
|
} {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
s.ServeHTTP(rec, httptest.NewRequestWithContext(
|
||||||
|
t.Context(), http.MethodGet, target, nil))
|
||||||
|
t.Logf("GET %s: %d, Content-Type %s",
|
||||||
|
target, rec.Code, rec.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
if tt.wantWidth == 0 {
|
||||||
|
checkSizeRefused(t, rec)
|
||||||
|
} else {
|
||||||
|
checkServedSize(t, rec, tt.wantWidth, tt.wantHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSizeRefused checks that rec answers 400 with the JSON error body that
|
||||||
|
// refuses a size whose side computed from the source's proportions is above
|
||||||
|
// 8192.
|
||||||
|
func checkSizeRefused(t *testing.T, rec *httptest.ResponseRecorder) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
const wantError = "size too large for the source: " +
|
||||||
|
"the side computed from its proportions is above 8192"
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(rec.Body.Bytes(), &body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("status %d, body is not JSON: %v", rec.Code, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rec.Code != http.StatusBadRequest || body.Status != http.StatusBadRequest ||
|
||||||
|
body.Error != wantError {
|
||||||
|
t.Errorf("status %d, body %+v, want %d and the error %q",
|
||||||
|
rec.Code, body, http.StatusBadRequest, wantError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkServedSize checks that rec answers 200 with a JPEG of wantWidth by
|
||||||
|
// wantHeight.
|
||||||
|
func checkServedSize(
|
||||||
|
t *testing.T, rec *httptest.ResponseRecorder, wantWidth, wantHeight int,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Errorf("status %d, want %d; body %s",
|
||||||
|
rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
served, err := jpeg.DecodeConfig(bytes.NewReader(rec.Body.Bytes()))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("decoding the served JPEG: %v", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if served.Width != wantWidth || served.Height != wantHeight {
|
||||||
|
t.Errorf("served %dx%d, want %dx%d",
|
||||||
|
served.Width, served.Height, wantWidth, wantHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user