From c74a8d4499ed492135e85ebd280086c99bf14af0 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 28 Sep 2026 15:19:12 +0000 Subject: [PATCH] test: /v1/image/ answers an invalid q with 400 (closes #134) Route tests: a q that is not a whole number from 1 to 100, an empty one included, is a 400 naming q and the value; a parameter given more than once is a 400 naming it; a query string that cannot be decoded is a 400 showing it. On next each is served, at 85 or at the first value given. Model: opus-5-5 --- internal/handlers/handlers_internal_test.go | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/internal/handlers/handlers_internal_test.go b/internal/handlers/handlers_internal_test.go index bfcb01a..780a46c 100644 --- a/internal/handlers/handlers_internal_test.go +++ b/internal/handlers/handlers_internal_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "database/sql" + "encoding/json" "image" "image/color" "image/jpeg" @@ -291,3 +292,67 @@ func TestHandleImage_InvalidFitMode_Returns400(t *testing.T) { t.Fatalf("status = %d, want %d", status, http.StatusBadRequest) } } + +// TestHandleImage_InvalidQuery_Returns400 verifies that the plain image route +// answers a q that is not a whole number from 1 to 100, an empty one +// included, with 400 naming q and the value, a parameter given more than once +// with 400 naming it, and a query string that cannot be decoded with 400 +// showing it, instead of serving the image at the default quality 85 or at +// the first value given. +func TestHandleImage_InvalidQuery_Returns400(t *testing.T) { + t.Parallel() + + tests := []struct { + query, wantError string + }{ + {"q=banana", `invalid q: not a number, got "banana"`}, + {"q=0", `invalid q: must be from 1 to 100, got "0"`}, + {"q=101", `invalid q: must be from 1 to 100, got "101"`}, + {"q=", `invalid q: not a number, got ""`}, + {"q=80&q=500", `invalid q: given more than once`}, + {"q=80&q=", `invalid q: given more than once`}, + {"fit=cover&fit=contain", `invalid fit: given more than once`}, + {"q=80%", `invalid query string "q=80%": invalid URL escape "%"`}, + { + "q=50;fit=contain", + `invalid query string "q=50;fit=contain": ` + + `invalid semicolon separator in query`, + }, + } + + for _, tt := range tests { + t.Run(tt.query, func(t *testing.T) { + t.Parallel() + + fix := setupTestHandler(t) + + r := chi.NewRouter() + r.Get("/v1/image/*", fix.handler.HandleImage()) + + req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, + "/v1/image/"+fix.goodHost+"/images/photo.jpg/50x50.jpeg?"+tt.query, nil) + rec := httptest.NewRecorder() + + r.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest) + } + + t.Logf("GET %s: %d %s", req.URL, rec.Code, rec.Body) + + var body struct { + Error string `json:"error"` + } + + err := json.NewDecoder(rec.Body).Decode(&body) + if err != nil { + t.Fatalf("decoding response body: %v", err) + } + + if body.Error != tt.wantError { + t.Errorf("error = %q, want %q", body.Error, tt.wantError) + } + }) + } +}