check / check (push) Canceled after 0s
The new package internal/imagerequest holds the request type, its two parts Source and Transform, the Format and Fit types, the dimension and fit checks, the /v1/image/ path parsing and the variant key; it imports no other package of pixa. imgcache.ParsedURL, imgcache.ImageRequest, signature.Request, imageprocessor.Request, the second Size, format and fit types and the conversions between them are gone. The image processor takes a Transform only. encurl keeps a private struct with the token's short CBOR field names. VariantKey moves with the function that makes it. Tests follow the moved and renamed code; the tests of the removed conversions go with them. Model: opus-5-5
164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"maps"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/pixa/internal/encurl"
|
|
"sneak.berlin/go/pixa/internal/imagerequest"
|
|
)
|
|
|
|
// Generator form field names, and a value that is not a number.
|
|
const (
|
|
widthField = "width"
|
|
heightField = "height"
|
|
qualityField = "quality"
|
|
ttlField = "ttl"
|
|
fitField = "fit"
|
|
notANumber = "abc"
|
|
)
|
|
|
|
// generatePost submits the /generate form with a valid session and CSRF token
|
|
// plus the caller's extra fields, returning the recorder.
|
|
func generatePost(
|
|
t *testing.T, extra url.Values,
|
|
) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
|
|
h, srv := newCSRFTestRouter(t)
|
|
|
|
sessionCookie := newSessionCookie(t, h)
|
|
cookies, token := csrfCredentials(t, srv, []*http.Cookie{sessionCookie})
|
|
cookies = append(cookies, sessionCookie)
|
|
|
|
form := url.Values{
|
|
sourceURLField: {testSourceURL},
|
|
csrfTokenField: {token},
|
|
}
|
|
maps.Copy(form, extra)
|
|
|
|
return postForm(srv, "/generate", cookies, form)
|
|
}
|
|
|
|
// TestGeneratePostRejectsNonNumericWidth verifies that a non-numeric width is
|
|
// rejected with 400 naming the field rather than being coerced to 0 and
|
|
// minting a 0-width token.
|
|
func TestGeneratePostRejectsNonNumericWidth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rec := generatePost(t, url.Values{"width": {"abc"}})
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
|
|
if strings.Contains(rec.Body.String(), "/v1/e/") {
|
|
t.Error("a token was generated for non-numeric width")
|
|
}
|
|
}
|
|
|
|
// TestGeneratePostRejectsOverLimitWidth verifies that a width beyond
|
|
// MaxDimension is rejected at generation time so an unusable token cannot be
|
|
// minted.
|
|
func TestGeneratePostRejectsOverLimitWidth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rec := generatePost(t, url.Values{"width": {"100000"}})
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
|
|
if strings.Contains(rec.Body.String(), "/v1/e/") {
|
|
t.Error("a token was generated for an over-limit width")
|
|
}
|
|
}
|
|
|
|
// TestGeneratePostRejectsBadField verifies that each generator field whose
|
|
// value is not a number, out of range, or unrecognized is rejected with 400,
|
|
// mints no token, and is named in the error shown on the page.
|
|
func TestGeneratePostRejectsBadField(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
field, value, wantError string
|
|
}{
|
|
{widthField, notANumber, "invalid width: not a number"},
|
|
{widthField, "-1", "width is negative"},
|
|
{widthField, "8193", "width is above 8192"},
|
|
{heightField, notANumber, "invalid height: not a number"},
|
|
{heightField, "8193", "height is above 8192"},
|
|
{qualityField, notANumber, "invalid quality: not a number"},
|
|
{qualityField, "0", "invalid quality: must be from 1 to 100"},
|
|
{qualityField, "101", "invalid quality: must be from 1 to 100"},
|
|
{ttlField, notANumber, "invalid ttl: not a number"},
|
|
{ttlField, "-1", "invalid ttl: must be from 0 to"},
|
|
{ttlField, "10000000000", "invalid ttl: must be from 0 to"},
|
|
{fitField, "bogus", "invalid fit mode: bogus"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.field+"="+tt.value, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rec := generatePost(t, url.Values{tt.field: {tt.value}})
|
|
body := rec.Body.String()
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
|
|
if strings.Contains(body, "/v1/e/") {
|
|
t.Error("a token was generated")
|
|
}
|
|
|
|
if !strings.Contains(body, tt.wantError) {
|
|
t.Errorf("page does not show %q", tt.wantError)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBuildGenerateRequestDefaultAndLimits verifies that an empty quality
|
|
// takes the default, and that the largest accepted width, height, quality and
|
|
// ttl are accepted with an expiry still in the future.
|
|
func TestBuildGenerateRequestDefaultAndLimits(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
parsed, err := url.Parse(testSourceURL)
|
|
if err != nil {
|
|
t.Fatalf("url.Parse() error = %v", err)
|
|
}
|
|
|
|
req, _, _, err := buildGenerateRequest(parsed, url.Values{})
|
|
if err != nil {
|
|
t.Fatalf("empty form: error = %v", err)
|
|
}
|
|
|
|
if req.Transform.Quality != encurl.DefaultQuality {
|
|
t.Errorf("empty quality gave %d, want %d",
|
|
req.Transform.Quality, encurl.DefaultQuality)
|
|
}
|
|
|
|
_, expiresAt, _, err := buildGenerateRequest(parsed, url.Values{
|
|
widthField: {strconv.Itoa(imagerequest.MaxDimension)},
|
|
heightField: {strconv.Itoa(imagerequest.MaxDimension)},
|
|
qualityField: {strconv.Itoa(maxQuality)},
|
|
ttlField: {strconv.Itoa(maxTTL)},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("largest accepted values: error = %v", err)
|
|
}
|
|
|
|
if !expiresAt.After(time.Now()) {
|
|
t.Errorf("ttl %d gave expiry %v, want a time in the future",
|
|
maxTTL, expiresAt)
|
|
}
|
|
}
|