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/imgcache" ) // 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) } }) } } // TestBuildGeneratePayloadDefaultAndLimits 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 TestBuildGeneratePayloadDefaultAndLimits(t *testing.T) { t.Parallel() parsed, err := url.Parse(testSourceURL) if err != nil { t.Fatalf("url.Parse() error = %v", err) } payload, _, _, err := buildGeneratePayload(parsed, url.Values{}) if err != nil { t.Fatalf("empty form: error = %v", err) } if payload.Quality != encurl.DefaultQuality { t.Errorf("empty quality gave %d, want %d", payload.Quality, encurl.DefaultQuality) } _, expiresAt, _, err := buildGeneratePayload(parsed, url.Values{ widthField: {strconv.Itoa(imgcache.MaxDimension)}, heightField: {strconv.Itoa(imgcache.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) } }